From 9bd0dc42c6df7fbf3878187638da8c8276a1254b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 11 Nov 2018 12:20:51 +0100 Subject: [PATCH 01/72] 3.9 develop branch --- CHANGELOG.md | 11 +++++++++++ README_DEVELOP.md => DEVELOP.md | 10 +++++++++- configure | 4 ++-- configure.ac | 4 ++-- docker/Makefile.in | 2 +- 5 files changed, 25 insertions(+), 6 deletions(-) rename README_DEVELOP.md => DEVELOP.md (92%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fdfe034..dccf9718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Clixon Changelog +## 3.9.0 (Preliminary Target: 31 December 2018) + +### Planned new features +* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) + +### Major New features +### API changes on existing features (you may need to change your code) +### Minor changes +### Corrected Bugs +### Known issues + ## 3.8.0 (6 Nov 2018) ### Major New features diff --git a/README_DEVELOP.md b/DEVELOP.md similarity index 92% rename from README_DEVELOP.md rename to DEVELOP.md index 350d3636..fb66e641 100644 --- a/README_DEVELOP.md +++ b/DEVELOP.md @@ -4,6 +4,7 @@ * [How to work in git (branching)](#branching) * [How the meta-configure stuff works](#meta-configure) * [How to debug](#debug) + * [New release](#new-release) ## Documentation How to document the code @@ -92,4 +93,11 @@ EOF valgrind --leak-check=full --show-leak-kinds=all clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang valgrind --tool=callgrind clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang sudo kcachegrind - ``` \ No newline at end of file + ``` + +## New release +What to think about when doing a new release. +* git merge --no-ff develop +* change CLIXON_VERSION in configure.ac +* git tag -a diff --git a/configure b/configure index 27dc733f..9428e91f 100755 --- a/configure +++ b/configure @@ -2161,9 +2161,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu : ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="8" +CLIXON_VERSION_MINOR="9" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific CLIgen version (eg 3.5) or head (3) CLIGEN_VERSION="3" diff --git a/configure.ac b/configure.ac index e6c08194..c46628fb 100644 --- a/configure.ac +++ b/configure.ac @@ -43,9 +43,9 @@ AC_INIT(lib/clixon/clixon.h.in) : ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="8" +CLIXON_VERSION_MINOR="9" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific CLIgen version (eg 3.5) or head (3) CLIGEN_VERSION="3" diff --git a/docker/Makefile.in b/docker/Makefile.in index 23d0a7e8..c18b9fad 100644 --- a/docker/Makefile.in +++ b/docker/Makefile.in @@ -55,7 +55,7 @@ distclean: clean rm -f Makefile *~ .depend docker: - sudo docker build -t $(IMAGE) . + sudo docker build -t $(IMAGE) . # --no-cache echo "cd ../example; make docker to build example application" push: From 9c57902b966e6d8b5f5dec9635341b48d2ea006f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 18 Nov 2018 13:22:08 +0100 Subject: [PATCH 02/72] * XML parser conformance to W3 spec * Names lexically correct (NCName) * Syntactically Correct handling of 'a * b * + * From https://www.w3.org/TR/2009/REC-xml-names-20091208 + * Definitions: + * - XML namespace: is identified by a URI reference [RFC3986]; element and + * attribute names may be placed in an XML namespace using the mechanisms + * described in this specification. + * - Expanded name: is a pair consisting of a namespace name and a local name. + * - Namespace name: For a name N in a namespace identified by a URI I, the + * "namespace name" is I. + * For a name N that is not in a namespace, the "namespace name" has no value. + * - Local name: In either case the "local name" is N. + * It is this combination of the universally managed URI namespace with the + * vocabulary's local names that is effective in avoiding name clashes. */ struct xml{ char *x_name; /* name of node */ char *x_namespace; /* namespace, if any */ +#ifdef notyet + char *x_namespacename; /* namespace name (or NULL) */ + char *x_localname; /* Local name N as defined above */ +#endif struct xml *x_up; /* parent node in hierarchy if any */ struct xml **x_childvec; /* vector of children nodes */ int x_childvec_len;/* length of vector */ @@ -224,7 +240,7 @@ xmlns_check(cxobj *xn, return NULL; } -/*! Check namespace of xml node by searhing recursively among ancestors +/*! Check namespace of xml node by searching recursively among ancestors * @param[in] xn xml node * @param[in] namespace check validity of namespace * @retval 0 Found / validated or no yang spec @@ -1258,15 +1274,18 @@ xmltree2cbuf(cbuf *cb, * @see xml_parse_file * @see xml_parse_string * @see xml_parse_va + * @note special case is empty XML where the parser is not invoked. */ static int -_xml_parse(const char *str, +_xml_parse(const char *str, yang_spec *yspec, cxobj *xt) { int retval = -1; struct xml_parse_yacc_arg ya = {0,}; + if (strlen(str) == 0) + return 0; /* OK */ if (xt == NULL){ clicon_err(OE_XML, errno, "Unexpected NULL XML"); return -1; diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index 4e9b297a..39173634 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -34,6 +34,8 @@ * XML parser * @see https://www.w3.org/TR/2008/REC-xml-20081126 * https://www.w3.org/TR/2009/REC-xml-names-20091208 + * + */ %{ @@ -72,8 +74,22 @@ int clixon_xml_parsewrap(void) return 1; } +/* + * From https://www.w3.org/TR/2008/REC-xml-20081126: + * [4]* NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] ... + * [4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7| + * [5] Name ::= NameStartChar (NameChar)* + * NOTE: From https://www.w3.org/TR/2009/REC-xml-names-20091208: + * [4] NCName ::= Name - (Char* ':' Char*) An XML Name, minus the ":" + * --> namestart and name below is NCNAME + */ + %} +namestart [A-Z_a-z] +namechar [A-Z_a-z\-\.0-9] +ncname {namestart}{namechar}* + %x START %s STATEA %s AMPERSAND @@ -81,36 +97,42 @@ int clixon_xml_parsewrap(void) %s CMNT %s STR %s TEXTDECL +%s PIDECL +%s PIDECL2 %s STRDQ %s STRSQ %% -[0-9A-Za-z_\-]+ { clixon_xml_parselval.string = strdup(yytext); + +[ \t] ; +\n { _YA->ya_linenum++; } + +{ncname} { clixon_xml_parselval.string = strdup(yytext); return NAME; /* rather be catch-all */ } -[ \t]+ ; \: return *clixon_xml_parsetext; \n { _YA->ya_linenum++;} -"<> { return MY_EOF; } +"""/>" { BEGIN(STATEA); return ESLASH; } "" { BEGIN(START); return ECOMMENT; } -\n _YA->ya_linenum++; . encoding return ENC; version return VER; -"=" return *clixon_xml_parsetext; -"?>" { BEGIN(START);return ETEXT;} +standalone return SD; +"=" { return *clixon_xml_parsetext; } +"?>" { BEGIN(START);return EQMARK;} \" { _YA->ya_lex_state =TEXTDECL;BEGIN(STRDQ); return *clixon_xml_parsetext; } \' { _YA->ya_lex_state =TEXTDECL;BEGIN(STRSQ); return *clixon_xml_parsetext; } +. { clixon_xml_parselval.string = yytext; return CHARDATA; /* optimize? */} -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } -[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +{ncname} { clixon_xml_parselval.string = strdup(yytext); + return NAME; /* rather be catch-all */ + } +[ \t] { BEGIN(PIDECL2);} +. { clixon_xml_parselval.string = yytext; return CHARDATA; /* optimize? */} +"?>" { BEGIN(START);return EQMARK;} +[^{?>}]+ { clixon_xml_parselval.string = strdup(yytext); return STRING; } + +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return STRING; } +[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return STRING; } \" { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; } -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } -[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return STRING; } +[^\']+ { clixon_xml_parselval.string = strdup(yytext); return STRING; } \' { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; } %% diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index ef9f78a9..a86c7276 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -39,12 +39,13 @@ char *string; } -%start topxml +%start document -%token NAME CHARDATA -%token VER ENC +%token NAME CHARDATA STRING +%token MY_EOF +%token VER ENC SD %token BSLASH ESLASH -%token BTEXT ETEXT +%token BXMLDCL BQMARK EQMARK %token BCOMMENT ECOMMENT %type attvalue @@ -120,7 +121,8 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, free(ver); return -1; } - free(ver); + if (ver) + free(ver); return 0; } @@ -299,6 +301,11 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, return retval; } +/*! Parse XML attribute + * Special cases: + * - DefaultAttName: xmlns + * - PrefixedAttName: xmlns:NAME + */ static int xml_parse_attr(struct xml_parse_yacc_arg *ya, char *prefix, @@ -308,6 +315,12 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *xa; +#ifdef notyet + if (prefix && strcmp(prefix,"xmlns")==0) + fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); + if (prefix==NULL && strcmp(name,"xmlns")==0) + fprintf(stderr, "DefaultAttName = %s\n", attval); +#endif /* notyet */ if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -327,69 +340,100 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, %} %% - -topxml : list - { clicon_debug(3, "topxml->list ACCEPT"); - YYACCEPT; } - | dcl list - { clicon_debug(3, "topxml->dcl list ACCEPT"); - YYACCEPT; } + /* [1] document ::= prolog element Misc* */ +document : prolog element misclist MY_EOF + { clicon_debug(2, "document->prolog element misc* ACCEPT"); + YYACCEPT; } + | elist MY_EOF + { clicon_debug(2, "document->elist ACCEPT"); /* internal exception*/ + YYACCEPT; } + ; +/* [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? */ +prolog : xmldcl misclist + { clicon_debug(2, "prolog->xmldcl misc*"); } + | misclist + { clicon_debug(2, "prolog->misc*"); } ; -dcl : BTEXT info encode ETEXT { clicon_debug(3, "dcl->info encode"); } +misclist : misclist misc { clicon_debug(2, "misclist->misclist misc"); } + | { clicon_debug(2, "misclist->"); } ; -info : VER '=' '\"' CHARDATA '\"' - { if (xml_parse_version(_YA, $4) <0) YYABORT; } - | VER '=' '\'' CHARDATA '\'' - { if (xml_parse_version(_YA, $4) <0) YYABORT; } +/* [27] Misc ::= Comment | PI | S */ +misc : comment { clicon_debug(2, "misc->comment"); } + | pi { clicon_debug(2, "misc->pi"); } + ; + +xmldcl : BXMLDCL verinfo encodingdecl sddecl EQMARK + { clicon_debug(2, "xmldcl->verinfo encodingdecl? sddecl?"); } + ; + +verinfo : VER '=' '\"' STRING '\"' + { if (xml_parse_version(_YA, $4) <0) YYABORT; + clicon_debug(2, "verinfo->version=\"STRING\"");} + | VER '=' '\'' STRING '\'' + { if (xml_parse_version(_YA, $4) <0) YYABORT; + clicon_debug(2, "verinfo->version='STRING'");} + ; + +encodingdecl : ENC '=' '\"' STRING '\"' {if ($4)free($4);} + | ENC '=' '\'' STRING '\'' {if ($4)free($4);} | ; -encode : ENC '=' '\"' CHARDATA '\"' {free($4);} - | ENC '=' '\'' CHARDATA '\'' {free($4);} +sddecl : SD '=' '\"' STRING '\"' {if ($4)free($4);} + | SD '=' '\'' STRING '\'' {if ($4)free($4);} + | ; - +/* [39] element ::= EmptyElemTag | STag content ETag */ element : '<' qname attrs element1 - { clicon_debug(3, "element -> < qname attrs element1"); } - ; + { clicon_debug(2, "element -> < qname attrs element1"); } + ; qname : NAME { if (xml_parse_unprefixed_name(_YA, $1) < 0) YYABORT; - clicon_debug(3, "qname -> NAME %s", $1);} + clicon_debug(2, "qname -> NAME %s", $1);} | NAME ':' NAME { if (xml_parse_prefixed_name(_YA, $1, $3) < 0) YYABORT; - clicon_debug(3, "qname -> NAME : NAME");} + clicon_debug(2, "qname -> NAME : NAME");} ; element1 : ESLASH {_YA->ya_xelement = NULL; - clicon_debug(3, "element1 -> />");} + clicon_debug(2, "element1 -> />");} | '>' { xml_parse_endslash_pre(_YA); } - list { xml_parse_endslash_mid(_YA); } - etg { xml_parse_endslash_post(_YA); - clicon_debug(3, "element1 -> > list etg");} + elist { xml_parse_endslash_mid(_YA); } + endtag { xml_parse_endslash_post(_YA); + clicon_debug(2, "element1 -> > elist endtag");} ; -etg : BSLASH NAME '>' -{ clicon_debug(3, "etg -> < ", $2); if (xml_parse_bslash1(_YA, $2) < 0) YYABORT; } +endtag : BSLASH NAME '>' + { clicon_debug(2, "endtag -> < "); + if (xml_parse_bslash1(_YA, $2) < 0) YYABORT; } | BSLASH NAME ':' NAME '>' { if (xml_parse_bslash2(_YA, $2, $4) < 0) YYABORT; - clicon_debug(3, "etg -> < "); } + clicon_debug(2, "endtag -> < "); } ; -list : list content { clicon_debug(3, "list -> list content"); } - | content { clicon_debug(3, "list -> content"); } +elist : elist content { clicon_debug(2, "elist -> elist content"); } + | content { clicon_debug(2, "elist -> content"); } ; -content : element { clicon_debug(3, "content -> element"); } - | comment { clicon_debug(3, "content -> comment"); } - | CHARDATA { if (xml_parse_content(_YA, $1) < 0) YYABORT; - clicon_debug(3, "content -> CHARDATA %s", $1); } - | { clicon_debug(3, "content -> "); } +/* Rule 43 */ +content : element { clicon_debug(2, "content -> element"); } + | comment { clicon_debug(2, "content -> comment"); } + | pi { clicon_debug(2, "content -> pi"); } + | CHARDATA { if (xml_parse_content(_YA, $1) < 0) YYABORT; + clicon_debug(2, "content -> CHARDATA %s", $1); } + | { clicon_debug(2, "content -> "); } ; comment : BCOMMENT ECOMMENT ; +pi : BQMARK NAME EQMARK {clicon_debug(2, "pi -> "); free($2); } + | BQMARK NAME STRING EQMARK + {clicon_debug(2, "pi -> "); free($2); free($3);} + ; + attrs : attrs attr | @@ -399,9 +443,9 @@ attr : NAME '=' attvalue { if (xml_parse_attr(_YA, NULL, $1, $3) | NAME ':' NAME '=' attvalue { if (xml_parse_attr(_YA, $1, $3, $5) < 0) YYABORT; } ; -attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */} +attvalue : '\"' STRING '\"' { $$=$2; /* $2 must be consumed */} | '\"' '\"' { $$=strdup(""); /* $2 must be consumed */} - | '\'' CHARDATA '\'' { $$=$2; /* $2 must be consumed */} + | '\'' STRING '\'' { $$=$2; /* $2 must be consumed */} | '\'' '\'' { $$=strdup(""); /* $2 must be consumed */} ; diff --git a/test/lib.sh b/test/lib.sh index 6a5138ac..52e5b119 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -78,6 +78,9 @@ expectfn(){ expect2= fi ret=$($cmd) +# echo "cmd:\"$cmd\"" +# echo "retval:\"$retval\"" +# echo "ret:\"$ret\"" if [ $? -ne $retval ]; then echo -e "\e[31m\nError in Test$testnr [$testname]:" echo -e "\e[0m:" @@ -134,11 +137,15 @@ $input EOF ) r=$? - if [ $r -ne $retval ]; then + if [ $r != $retval ]; then echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" echo -e "\e[0m:" exit -1 fi + # If error dont match output strings + if [ $r != 0 ]; then + return + fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then diff --git a/test/test_xml.sh b/test/test_xml.sh index b2937201..2aa931e3 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -1,5 +1,7 @@ #!/bin/bash # Test: XML parser tests +# @see https://www.w3.org/TR/2008/REC-xml-20081126 +# https://www.w3.org/TR/2009/REC-xml-names-20091208 #PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_xml" PROG=../util/clixon_util_xml @@ -9,6 +11,18 @@ PROG=../util/clixon_util_xml new "xml parse" expecteof "$PROG" 0 "" "^$" +new "xml parse strange names" +expecteof "$PROG" 0 "<_->" "^<_->$" + +new "xml parse name errors" +expecteof "$PROG" 255 "<-a/>" "" + +new "xml parse name errors" +expecteof "$PROG" 255 "<9/>" "" + +new "xml parse name errors" +expecteof "$PROG" 255 "" "" + XML=$(cat <An example of escaped CENDs @@ -52,5 +66,75 @@ expecteof "$PROG" 0 "" '^$' new "Mixed quotes" expecteof "$PROG" 0 "" '^$' +new "XMLdecl version" +expecteof "$PROG" 0 '' '' + +new "XMLdecl version, single quotes" +expecteof "$PROG" 0 "" '' + +new "XMLdecl version no element" +expecteof "$PROG" 255 '' '' + +new "XMLdecl no version" +expecteof "$PROG" 255 '' '' + +new "XMLdecl misspelled version" +expecteof "$PROG" 255 '' '' + +new "XMLdecl version + encoding" +expecteof "$PROG" 0 '' '' + +new "XMLdecl version + misspelled encoding" +expecteof "$PROG" 255 '' '' + +new "XMLdecl version + standalone" +expecteof "$PROG" 0 '' '' + +new "PI - Processing instruction empty" +expecteof "$PROG" 0 '' '' + +new "PI some content" +expecteof "$PROG" 0 '' '' + +new "prolog element misc*" +expecteof "$PROG" 0 '' '' + +# We allow it as an internal necessity for parsing of xml fragments +#new "double element error" +#expecteof "$PROG" 255 '' '' + +new "namespace: DefaultAttName" +expecteof "$PROG" 0 'hello' '^hello$' + +new "namespace: PrefixedAttName" +expecteof "$PROG" 0 'hello' '^hello$' + +new "First example 6.1 from https://www.w3.org/TR/2009/REC-xml-names-20091208" +XML=$(cat < + + + + Frobnostication + Moved to + here. + +EOF +) +expecteof "$PROG" 0 "$XML" "$XML" + +new "Second example 6.1 from https://www.w3.org/TR/2009/REC-xml-names-20091208" +XML=$(cat < + + + Cheaper by the Dozen + 1568491379 + +EOF +) +expecteof "$PROG" 0 "$XML" "$XML" + rm -rf $dir diff --git a/util/Makefile.in b/util/Makefile.in index 4814e91a..5095b612 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -78,7 +78,7 @@ all: $(APPS) @echo "You may want to make clixon_util_stream separately (curl dependency)" clean: - rm -f $(APPS) *.core + rm -f $(APPS) clixon_util_stream *.core # APPS clixon_util_xml: clixon_util_xml.c $(MYLIB) diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 72730000..29734f40 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -48,6 +48,7 @@ #include #include #include +#include #include /* cligen */ @@ -68,21 +69,40 @@ static int usage(char *argv0) { - fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + fprintf(stderr, "usage:%s [options]\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n", + argv0); exit(0); } int -main(int argc, char **argv) +main(int argc, + char **argv) { cxobj *xt = NULL; cxobj *xc; cbuf *cb = cbuf_new(); + int retval = -1; + char c; - if (argc != 1){ - usage(argv[0]); - return 0; - } + clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "hD:")) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv[0]); + break; + default: + usage(argv[0]); + break; + } if (xml_parse_file(0, "", NULL, &xt) < 0){ fprintf(stderr, "xml parse error %s\n", clicon_err_reason); goto done; @@ -90,18 +110,20 @@ main(int argc, char **argv) xc = NULL; while ((xc = xml_child_each(xt, xc, -1)) != NULL) clicon_xml2cbuf(cb, xc, 0, 0); /* print xml */ - fprintf(stdout, "%s\n", cbuf_get(cb)); + fprintf(stdout, "%s", cbuf_get(cb)); + fflush(stdout); #if 0 cbuf_reset(cb); xmltree2cbuf(cb, xt, 0); /* dump data structures */ fprintf(stderr, "%s\n", cbuf_get(cb)); #endif + retval = 0; done: if (xt) xml_free(xt); if (cb) cbuf_free(cb); - return 0; + return retval; } diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index 2ee8d2e8..dcbee236 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -133,13 +133,14 @@ main(int argc, char **argv) clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); optind = 1; opterr = 0; - while ((c = getopt(argc, argv, "hDf:p:i:")) != -1) + while ((c = getopt(argc, argv, "hD:f:p:i:")) != -1) switch (c) { case 'h': usage(argv0); break; case 'D': - debug++; + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv0); break; case 'f': /* XML file */ filename = optarg; From a8f0aad4110f2c0df2507666bcca4f931cee6a3f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 18 Nov 2018 20:55:57 +0100 Subject: [PATCH 03/72] * Yang parser is stricter (see cardinality below) which may break parsing of slack yang specs. * YANG parser cardinality checked (only modules level yet) * See https://github.com/clicon/clixon/issues/48 --- CHANGELOG.md | 8 ++++++ README.md | 1 + example/example.yang | 2 ++ lib/clixon/clixon_yang.h | 5 +++- lib/src/Makefile.in | 1 + lib/src/clixon_yang.c | 44 ++++++++++++++++++++++++++++++ lib/src/clixon_yang_parse.l | 1 + lib/src/clixon_yang_parse.y | 1 + test/lib.sh | 9 +++--- test/test_datastore.sh | 3 ++ test/test_feature.sh | 6 ++-- test/test_leafref.sh | 2 ++ test/test_list.sh | 3 ++ test/test_nacm.sh | 2 ++ test/test_nacm_ext.sh | 2 ++ test/test_netconf.sh | 2 ++ test/test_order.sh | 3 ++ test/test_perf.sh | 3 ++ test/test_restconf.sh | 2 ++ test/test_restconf2.sh | 3 ++ test/test_type.sh | 3 ++ test/test_when_must.sh | 2 ++ test/test_xml.sh | 4 +-- test/test_yang.sh | 8 +++++- test/test_yang_parse.sh | 31 ++++++--------------- util/clixon_util_xml.c | 13 ++++++--- util/clixon_util_yang.c | 39 ++++++++++++++++++++------ yang/clixon-config@2018-10-21.yang | 3 +- 28 files changed, 159 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccf9718..749d361e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,15 @@ ### Major New features ### API changes on existing features (you may need to change your code) +* Yang parser is stricter (see cardinality below) which may break parsing of slack yang specs. ### Minor changes +* YANG parser cardinality checked (only modules level yet) + * See https://github.com/clicon/clixon/issues/48 +* XML parser conformance to W3 spec + * Names lexically correct (NCName) + * Syntactically Correct handling of ' therefore start enumeration with 1. */ enum rfc_6020{ - Y_ACTION = 0, + Y_ACTION = 1, + Y_ANYDATA, Y_ANYXML, Y_ARGUMENT, Y_AUGMENT, @@ -252,6 +254,7 @@ yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); +int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 787c19c8..eefa969f 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -70,6 +70,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_string.c clixon_handle.c \ clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \ clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \ + clixon_yang_cardinality.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 778f2717..13c060c5 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -76,12 +76,17 @@ #include "clixon_options.h" #include "clixon_yang_type.h" #include "clixon_yang_parse.h" +#include "clixon_yang_cardinality.h" /* Size of json read buffer when reading from file*/ #define BUFLEN 1024 +/* + * Local variables + */ /* Mapping between yang keyword string <--> clicon constants */ static const map_str2int ykmap[] = { + {"anydata", Y_ANYDATA}, {"anyxml", Y_ANYXML}, {"argument", Y_ARGUMENT}, {"augment", Y_AUGMENT}, @@ -393,9 +398,11 @@ yn_each(yang_node *yn, * @param[in] yn Yang node, current context node. * @param[in] keyword if 0 match any keyword * @param[in] argument String compare w argument. if NULL, match any. + * @retval ys Yang statement, if any * This however means that if you actually want to match only a yang-stmt with * argument==NULL you cannot, but I have not seen any such examples. * @see yang_find_datanode + * @see yang_match returns number of matches */ yang_stmt * yang_find(yang_node *yn, @@ -420,6 +427,38 @@ yang_find(yang_node *yn, } return match ? ys : NULL; } + +/*! Count number of children that matches keyword and argument + * + * @param[in] yn Yang node, current context node. + * @param[in] keyword if 0 match any keyword + * @param[in] argument String compare w argument. if NULL, match any. + * @retval n Number of matches + * This however means that if you actually want to match only a yang-stmt with + * argument==NULL you cannot, but I have not seen any such examples. + * @see yang_find + */ +int +yang_match(yang_node *yn, + int keyword, + char *argument) +{ + yang_stmt *ys = NULL; + int i; + int match = 0; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + if (keyword == 0 || ys->ys_keyword == keyword){ + if (argument == NULL) + match++; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + match++; + } + } + return match; +} #ifdef NOTYET /*! Prototype more generic than yang_find_datanode and yang_find_schemanode */ @@ -2140,6 +2179,11 @@ yang_parse(clicon_handle h, if (yang_parse_recurse(ymod, dir, ysp) < 0) goto done; + /* Check cardinality maybe this should be done after grouping/augment */ + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_cardinality(h, ysp->yp_stmt[i], ysp->yp_stmt[i]->ys_argument) < 0) + goto done; + /* Step 2: check features: check if enabled and remove disabled features */ for (i=modnr; iyp_len; i++) /* XXX */ if (yang_features(h, ysp->yp_stmt[i]) < 0) diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index d059993a..9b9c7fad 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -112,6 +112,7 @@ clixon_yang_parsewrap(void) /* RFC 6020 keywords */ action { BEGIN(ARGUMENT); return K_ACTION; } +anydata { BEGIN(ARGUMENT); return K_ANYDATA; } anyxml { BEGIN(ARGUMENT); return K_ANYXML; } argument { BEGIN(ARGUMENT); return K_ARGUMENT; } augment { BEGIN(ARGUMENT); return K_AUGMENT; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 78cb172f..0a1fd81c 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -65,6 +65,7 @@ * - Cant use the symbols in this file because yacc needs token definitions */ %token K_ACTION +%token K_ANYDATA %token K_ANYXML %token K_ARGUMENT %token K_AUGMENT diff --git a/test/lib.sh b/test/lib.sh index 52e5b119..ab0a5ed9 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -142,11 +142,10 @@ EOF echo -e "\e[0m:" exit -1 fi - # If error dont match output strings - if [ $r != 0 ]; then - return - fi - + # If error dont match output strings (why not?) +# if [ $r != 0 ]; then +# return +# fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 8427ed9f..27f3b4bf 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -10,6 +10,9 @@ datastore=../datastore/datastore_client cat < $fyang module ietf-ip{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ip; container x { list y { key "a b"; diff --git a/test/test_feature.sh b/test/test_feature.sh index a4a8a532..e5279f6c 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -28,6 +28,8 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; import ietf-routing { prefix rt; @@ -93,7 +95,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' -# This test has been broken up into all differetn modules instead of one large +# This test has been broken up into all different modules instead of one large # reply since the modules change so often new "netconf schema resource, RFC 7895" ret=$($clixon_netconf -qf $cfg -y $fyang< $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; import ietf-interfaces { prefix if; diff --git a/test/test_list.sh b/test/test_list.sh index 69b0f7b0..67c3f953 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -26,6 +26,9 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; container c{ presence true; list a0{ diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 7f98fe4a..586e5538 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -31,6 +31,8 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; import ietf-netconf-acm { prefix nacm; diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 10b01170..ee5ae60b 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -37,6 +37,8 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; container authentication { description "Example code for enabling www basic auth and some example diff --git a/test/test_netconf.sh b/test/test_netconf.sh index b0265345..2d3d2b0c 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -30,6 +30,8 @@ EOF cat < $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; import ietf-interfaces { prefix if; diff --git a/test/test_order.sh b/test/test_order.sh index 53e91b44..da1ecb87 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -41,6 +41,9 @@ EOF cat < $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; container c{ leaf d{ type string; diff --git a/test/test_perf.sh b/test/test_perf.sh index 2e7147fa..e6dde054 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -24,6 +24,9 @@ fconfig=$dir/config cat < $fyang module ietf-ip{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ip; container x { list y { key "a"; diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 107e7f57..260d7eb9 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -30,6 +30,8 @@ EOF cat < $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; import ietf-interfaces { prefix if; diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 663a7c3d..52364f2f 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -22,6 +22,9 @@ EOF cat < $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; container cont1{ list interface{ key name; diff --git a/test/test_type.sh b/test/test_type.sh index e8254d0f..a2126e1c 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -26,6 +26,9 @@ EOF cat < $fyang module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; typedef ab { type string { pattern diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 9f4974e9..da629784 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -27,6 +27,8 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; identity routing-protocol { description diff --git a/test/test_xml.sh b/test/test_xml.sh index 2aa931e3..6416e29f 100755 --- a/test/test_xml.sh +++ b/test/test_xml.sh @@ -79,13 +79,13 @@ new "XMLdecl no version" expecteof "$PROG" 255 '' '' new "XMLdecl misspelled version" -expecteof "$PROG" 255 '' '' +expecteof "$PROG -l o" 255 '' 'yntax error: at or before: v' new "XMLdecl version + encoding" expecteof "$PROG" 0 '' '' new "XMLdecl version + misspelled encoding" -expecteof "$PROG" 255 '' '' +expecteof "$PROG -l o" 255 '' 'syntax error: at or before: e' new "XMLdecl version + standalone" expecteof "$PROG" 0 '' '' diff --git a/test/test_yang.sh b/test/test_yang.sh index 5176490d..a15bf6cb 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -27,7 +27,9 @@ EOF cat < $fyang module $APPNAME{ + yang-version 1.1; prefix ex; + namespace "urn:example:clixon"; extension c-define { description "Example from RFC 6020"; argument "name"; @@ -89,6 +91,8 @@ EOF # This yang definition uses an extension which is not defined. Error when loading cat < $fyangerr module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; prefix ex; extension c-define { description "Example from RFC 6020"; @@ -126,7 +130,9 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" -new "cli not defined extension" +#new "cli not defined extension" +#new "netconf not defined extension" +#expecteof "$clixon_netconf -qf $cfg -l o" 0 "$YANG" "Extension ex:not-defined not found" # This text yields an error, but the test cannot detect the error message yet #expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" 0 "Yang error: Extension ex:not-defined not found" diff --git a/test/test_yang_parse.sh b/test/test_yang_parse.sh index 9cc52da2..c8024a3d 100755 --- a/test/test_yang_parse.sh +++ b/test/test_yang_parse.sh @@ -1,41 +1,26 @@ #!/bin/bash # Test: YANG parser tests # First an example yang, second all openconfig yangs +# Problem with this is that util only parses single file. it should +# call yang_parse(). #PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" PROG=../util/clixon_util_yang OPENCONFIG=~/syssrc/openconfig - +exit 0 # nyi # include err() and new() functions and creates $dir . ./lib.sh -YANG=$(cat < #include #include -#include #include #include #include @@ -72,7 +71,8 @@ usage(char *argv0) fprintf(stderr, "usage:%s [options]\n" "where options are\n" "\t-h \t\tHelp\n" - "\t-D \tDebug\n", + "\t-D \tDebug\n" + "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n", argv0); exit(0); } @@ -86,11 +86,11 @@ main(int argc, cbuf *cb = cbuf_new(); int retval = -1; char c; + int logdst = CLICON_LOG_STDERR; - clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR); optind = 1; opterr = 0; - while ((c = getopt(argc, argv, "hD:")) != -1) + while ((c = getopt(argc, argv, "hD:l:")) != -1) switch (c) { case 'h': usage(argv[0]); @@ -99,10 +99,15 @@ main(int argc, if (sscanf(optarg, "%d", &debug) != 1) usage(argv[0]); break; + case 'l': /* Log destination: s|e|o|f */ + if ((logdst = clicon_log_opt(optarg[0])) < 0) + usage(argv[0]); + break; default: usage(argv[0]); break; } + clicon_log_init("clixon_util_xml", debug?LOG_DEBUG:LOG_INFO, logdst); if (xml_parse_file(0, "", NULL, &xt) < 0){ fprintf(stderr, "xml parse error %s\n", clicon_err_reason); goto done; diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c index 9ebe991f..9232b535 100644 --- a/util/clixon_util_yang.c +++ b/util/clixon_util_yang.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #define __USE_GNU /* strverscmp */ @@ -65,20 +66,42 @@ static int usage(char *argv0) { - fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + fprintf(stderr, "usage:%s [options]\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-l \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n", + argv0); exit(0); } int main(int argc, char **argv) { - yang_spec *yspec = NULL; - - if (argc != 1){ - usage(argv[0]); - return -1; - } - clicon_log_init("clixon_util_yang", LOG_INFO, CLICON_LOG_STDERR); + yang_spec *yspec = NULL; + char c; + int logdst = CLICON_LOG_STDERR; + + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "hD:l:")) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv[0]); + break; + case 'l': /* Log destination: s|e|o|f */ + if ((logdst = clicon_log_opt(optarg[0])) < 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + break; + } + clicon_log_init("clixon_util_yang", debug?LOG_DEBUG:LOG_INFO, logdst); if ((yspec = yspec_new()) == NULL) goto done; if (yang_parse_file(0, "yang test", yspec) == NULL){ diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index fb07efa1..839e16b0 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -1,5 +1,6 @@ module clixon-config { - + yang-version 1.1; + namespace "http://clicon.org"; prefix cc; organization From 43034069577985e2fe591136a67110df77d16b76 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 18 Nov 2018 20:58:18 +0100 Subject: [PATCH 04/72] clixon_yang_cardinality files added --- lib/src/clixon_yang_cardinality.c | 246 ++++++++++++++++++++++++++++++ lib/src/clixon_yang_cardinality.h | 42 +++++ 2 files changed, 288 insertions(+) create mode 100644 lib/src/clixon_yang_cardinality.c create mode 100644 lib/src/clixon_yang_cardinality.h diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c new file mode 100644 index 00000000..ab6a43d6 --- /dev/null +++ b/lib/src/clixon_yang_cardinality.c @@ -0,0 +1,246 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Yang cardinality functions according to RFC 7950 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#define __USE_GNU /* strverscmp */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_err.h" +#include "clixon_yang.h" +#include "clixon_yang_cardinality.h" + +/* + * Types + */ +/* Encode cardinality according to RFC 7950 + * Example: + * 7.20.3.1. The deviation's Substatements + * + * +--------------+----------+-------------+ + * | substatement | section | cardinality | + * +--------------+----------+-------------+ + * | description | 7.21.3 | 0..1 | + * | deviate | 7.20.3.2 | 1..n | + * | reference | 7.21.4 | 0..1 | + * +--------------+----------+-------------+ + * The cardinalities are (and how many time they occur) + * 0..1 149 See ycardmap_01 + * 1..n, 1 + * 0..n 176 (no restrictions) + * 1 10 + */ +struct ycard{ + enum rfc_6020 yc_parent; + enum rfc_6020 yc_child; + int yc_min; + int yc_max; +}; + +/* + * Local variables + */ +/* Yang statements cardinality map + * The cardinalities are (and how many time they occur) + * 1..n, 1 + * 1 10 + * 0..1 149 + * 0..n 176 (no restrictions) + * @note assume array is ordered wrt parent + * @note yang-version is optional in RFC6020 but mandatory in RFC7950, if not given, it defaults to 1. + */ +#define NMAX 1000000 /* Just a large number */ +static const struct ycard yclist[] = { + {Y_MODULE, Y_ANYDATA, 0, NMAX}, + {Y_MODULE, Y_ANYXML, 0, NMAX}, + {Y_MODULE, Y_AUGMENT, 0, NMAX}, + {Y_MODULE, Y_CHOICE, 0, NMAX}, + {Y_MODULE, Y_CONTACT, 0, 1}, + {Y_MODULE, Y_CONTAINER, 0, NMAX}, + {Y_MODULE, Y_DESCRIPTION, 0, 1}, + {Y_MODULE, Y_DEVIATION, 0, NMAX}, + {Y_MODULE, Y_EXTENSION, 0, NMAX}, + {Y_MODULE, Y_FEATURE, 0, NMAX}, + {Y_MODULE, Y_GROUPING, 0, NMAX}, + {Y_MODULE, Y_IDENTITY, 0, NMAX}, + {Y_MODULE, Y_IMPORT, 0, NMAX}, + {Y_MODULE, Y_INCLUDE, 0, NMAX}, + {Y_MODULE, Y_LEAF, 0, NMAX}, + {Y_MODULE, Y_LEAF_LIST, 0, NMAX}, + {Y_MODULE, Y_LIST, 0, NMAX}, + {Y_MODULE, Y_NAMESPACE, 1, 1}, + {Y_MODULE, Y_NOTIFICATION, 0, NMAX}, + {Y_MODULE, Y_ORGANIZATION, 0, 1}, + {Y_MODULE, Y_PREFIX, 1, 1}, + {Y_MODULE, Y_REFERENCE, 0, 1}, + {Y_MODULE, Y_REVISION, 0, NMAX}, + {Y_MODULE, Y_RPC, 0, NMAX}, + {Y_MODULE, Y_TYPEDEF, 0, NMAX}, + {Y_MODULE, Y_USES, 0, NMAX}, + {Y_MODULE, Y_YANG_VERSION, 0, 1}, + {0,} +}; + +/*! Find yang parent and child combination in yang cardinality table + * @param[in] parent Parent Yang spec + * @param[in] child Child yang spec if -1 first + * @param[in] yc Yang cardinality map + * @param[in] p If set, quit as soon as parents dont match + * @retval NULL Not found + * @retval yp Found + */ +static const struct ycard * +ycard_find(enum rfc_6020 parent, + enum rfc_6020 child, + const struct ycard *yclist, + int p) + +{ + const struct ycard *yc; + + for (yc = &yclist[0]; (int)yc->yc_parent; yc++){ + if (yc->yc_parent == parent){ + if (!child || yc->yc_child == child) + return yc; + } + else + if (p) + return NULL; /* premature quit */ + } + return NULL; +} + +/*! Check cardinality, ie if each yang node has the expected nr of children + * @param[in] h Clicon handle + * @param[in] yt Yang statement + * @param[in] modname Name of module (for debug message) + * 1) For all children, if neither in 0..n, 0..1, 1 or 1..n ->ERROR + * 2) For all in 1 and 1..n list, if 0 such children ->ERROR + * 3) For all in 0..1 and 1 list, if >1 such children ->ERROR + * 4) Recurse + * @note always accept UNKNOWN (due to extension) + */ +int +yang_cardinality(clicon_handle h, + yang_stmt *yt, + char *modname) +{ + int retval = -1; + yang_stmt *ys = NULL; + int pk; + int ck; + int i; + int nr; + const struct ycard *ycplist; /* ycard parent table*/ + const struct ycard *yc; + + pk = yt->ys_keyword; + /* 0) Find parent sub-parts of cardinality vector */ + ycplist = ycard_find(pk, 0, yclist, 0); + /* 1) For all children, if neither in 0..n, 0..1, 1 or 1..n ->ERROR */ + i = 0; + while (iys_len){ + ys = yt->ys_stmt[i++]; + ck = ys->ys_keyword; + if (ck == Y_UNKNOWN) /* special case */ + continue; + if ((yc = ycard_find(pk, ck, ycplist, 1)) == NULL){ + clicon_err(OE_YANG, 0, "%s: \"%s\" is child of \"%s\", but should not be", + modname, yang_key2str(ck), yang_key2str(pk)); + goto done; + } + } + /* 2) For all in 1 and 1..n list, if 0 such children ->ERROR */ + for (yc = &ycplist[0]; (int)yc->yc_parent == pk; yc++){ + if (yc->yc_min && + yang_find((yang_node*)yt, yc->yc_child, NULL) == NULL){ + clicon_err(OE_YANG, 0, "%s: \"%s\" is missing but is mandatory child of \"%s\"", + modname, yang_key2str(yc->yc_child), yang_key2str(pk)); + goto done; + } + } + /* 3) For all in 0..1 and 1 list, if >1 such children ->ERROR */ + for (yc = &ycplist[0]; (int)yc->yc_parent == pk; yc++){ + if (yc->yc_maxyc_child, NULL)) > yc->yc_max){ + clicon_err(OE_YANG, 0, "%s: \"%s\" has %d children of type \"%s\", but only %d allowed", + modname, + yang_key2str(pk), + nr, + yang_key2str(yc->yc_child), + yc->yc_max); + goto done; + } + } + + if (0) { /* Notyet */ + /* 4) Recurse */ + i = 0; + while (iys_len){ /* Note, children may be removed */ + ys = yt->ys_stmt[i++]; + if (yang_cardinality(h, ys, modname) < 0) + goto done; + } + } + retval = 0; + done: + return retval; +} + diff --git a/lib/src/clixon_yang_cardinality.h b/lib/src/clixon_yang_cardinality.h new file mode 100644 index 00000000..eb71e8ea --- /dev/null +++ b/lib/src/clixon_yang_cardinality.h @@ -0,0 +1,42 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * Yang cardinality functions according to RFC 7950 + */ +#ifndef _CLIXON_YANG_CARDINALITY_H_ +#define _CLIXON_YANG_CARDINALITY_H_ + +/* + * Prototypes + */ +int yang_cardinality(clicon_handle h, yang_stmt *yt, char *modname); + +#endif /* _CLIXON_YANG_CARDINALITY_H_ */ From 56da97cb5beb7ec9d55819b8b852bea24e7c9618 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Nov 2018 18:24:13 +0100 Subject: [PATCH 05/72] Openconfig yang specs parsed: https://github.com/openconfig/public --- CHANGELOG.md | 10 +- lib/src/clixon_yang.c | 2 +- lib/src/clixon_yang_parse.l | 225 ++--- lib/src/clixon_yang_parse.y | 1679 +++++++++++++++++++---------------- test/test_openconfig.sh | 24 + test/test_yang_parse.sh | 18 +- 6 files changed, 1065 insertions(+), 893 deletions(-) create mode 100755 test/test_openconfig.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 749d361e..a80cdd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,15 @@ * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features +* More complete Yang parser + * YANG parser cardinality checked (only modules level yet) + * See https://github.com/clicon/clixon/issues/84 + * Openconfig yang specs parsed: https://github.com/openconfig/public + ### API changes on existing features (you may need to change your code) -* Yang parser is stricter (see cardinality below) which may break parsing of slack yang specs. +* Yang parser is stricter (see above) which may break parsing of existing yang specs. + ### Minor changes -* YANG parser cardinality checked (only modules level yet) - * See https://github.com/clicon/clixon/issues/48 * XML parser conformance to W3 spec * Names lexically correct (NCName) * Syntactically Correct handling of ': */ if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */ - clicon_log(LOG_WARNING, "%s: Absolute schema nodeid must have prefix", __FUNCTION__); + clicon_log(LOG_WARNING, "%s: Absolute schema nodeid %s must have prefix", __FUNCTION__, schema_nodeid); goto ok; } if ((prefix = strdup(vec[1])) == NULL){ diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index 9b9c7fad..58715c0c 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -33,6 +33,7 @@ * Yang parser. Hopefully useful but not complete * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 +* */ %{ @@ -81,20 +82,20 @@ clixon_yang_parsewrap(void) The argument is a string Example: keyword argument ; keyword ; keyword { keyword argument; } keyword - - STRING0 corresponds to string rule - ARGUMENT corresponds to identifier-arg-str,unique-arg-str,key-arg-str, etc. - more to do here. */ %} +identifier [A-Za-z_][A-Za-z0-9_\-\.]* + %x KEYWORD -%s ARGUMENT -%s STRING0 -%s STRING1 -%s STRING2 +%s BOOLEAN +%s INTEGER +%s STRARG +%s STRING +%s STRINGDQ +%s STRINGSQ %s ESCAPE %s COMMENT1 %s COMMENT2 @@ -102,84 +103,91 @@ clixon_yang_parsewrap(void) %% /* Common tokens */ -[ \t] -<> { return MY_EOF; } -\n { _YY->yy_linenum++; } -"/*" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT1); } -"//" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT2); } +[ \t] +<> { return MY_EOF; } +\n { _YY->yy_linenum++; } +"/*" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT1); } +"//" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT2); } -\} { return *yytext; } - /* RFC 6020 keywords */ -action { BEGIN(ARGUMENT); return K_ACTION; } -anydata { BEGIN(ARGUMENT); return K_ANYDATA; } -anyxml { BEGIN(ARGUMENT); return K_ANYXML; } -argument { BEGIN(ARGUMENT); return K_ARGUMENT; } -augment { BEGIN(ARGUMENT); return K_AUGMENT; } -base { BEGIN(ARGUMENT); return K_BASE; } -belongs-to { BEGIN(ARGUMENT); return K_BELONGS_TO; } -bit { BEGIN(ARGUMENT); return K_BIT; } -case { BEGIN(ARGUMENT); return K_CASE; } -choice { BEGIN(ARGUMENT); return K_CHOICE; } -config { BEGIN(ARGUMENT); return K_CONFIG; } -contact { BEGIN(STRING0); return K_CONTACT; } -container { BEGIN(ARGUMENT); return K_CONTAINER; } -default { BEGIN(STRING0); return K_DEFAULT; } -description { BEGIN(STRING0); return K_DESCRIPTION; } -deviate { BEGIN(ARGUMENT); return K_DEVIATE; } -deviation { BEGIN(ARGUMENT); return K_DEVIATION; } -enum { BEGIN(STRING0); return K_ENUM; } -error-app-tag { BEGIN(STRING0); return K_ERROR_APP_TAG; } -error-message { BEGIN(STRING0); return K_ERROR_MESSAGE; } -extension { BEGIN(ARGUMENT); return K_EXTENSION; } -feature { BEGIN(ARGUMENT); return K_FEATURE; } -fraction-digits { BEGIN(ARGUMENT); return K_FRACTION_DIGITS; } -grouping { BEGIN(ARGUMENT); return K_GROUPING; } -identity { BEGIN(ARGUMENT); return K_IDENTITY; } -if-feature { BEGIN(ARGUMENT); return K_IF_FEATURE; } -import { BEGIN(ARGUMENT); return K_IMPORT; } -include { BEGIN(ARGUMENT); return K_INCLUDE; } -input { BEGIN(ARGUMENT); return K_INPUT; } -key { BEGIN(ARGUMENT); return K_KEY; } -leaf { BEGIN(ARGUMENT); return K_LEAF; } -leaf-list { BEGIN(ARGUMENT); return K_LEAF_LIST; } -length { BEGIN(ARGUMENT); return K_LENGTH; } -list { BEGIN(ARGUMENT); return K_LIST; } -mandatory { BEGIN(ARGUMENT); return K_MANDATORY; } -max-elements { BEGIN(ARGUMENT); return K_MAX_ELEMENTS; } -min-elements { BEGIN(ARGUMENT); return K_MIN_ELEMENTS; } -module { BEGIN(ARGUMENT); return K_MODULE; } -must { BEGIN(STRING0); return K_MUST; } -namespace { BEGIN(ARGUMENT); return K_NAMESPACE; } -notification { BEGIN(ARGUMENT); return K_NOTIFICATION; } -ordered-by { BEGIN(ARGUMENT); return K_ORDERED_BY; } -organization { BEGIN(STRING0); return K_ORGANIZATION; } -output { BEGIN(ARGUMENT); return K_OUTPUT; } -path { BEGIN(ARGUMENT); return K_PATH; } -pattern { BEGIN(STRING0); return K_PATTERN; } -position { BEGIN(ARGUMENT); return K_POSITION; } -prefix { BEGIN(ARGUMENT); return K_PREFIX; } -presence { BEGIN(STRING0); return K_PRESENCE; } -range { BEGIN(ARGUMENT); return K_RANGE; } -reference { BEGIN(STRING0); return K_REFERENCE; } -refine { BEGIN(ARGUMENT); return K_REFINE; } -require-instance { BEGIN(ARGUMENT); return K_REQUIRE_INSTANCE; } -revision { BEGIN(ARGUMENT); return K_REVISION; } -revision-date { BEGIN(ARGUMENT); return K_REVISION_DATE; } -rpc { BEGIN(ARGUMENT); return K_RPC; } -status { BEGIN(ARGUMENT); return K_STATUS; } -submodule { BEGIN(ARGUMENT); return K_SUBMODULE; } -type { BEGIN(ARGUMENT); return K_TYPE; } -typedef { BEGIN(ARGUMENT); return K_TYPEDEF; } -unique { BEGIN(ARGUMENT); return K_UNIQUE; } -units { BEGIN(STRING0); return K_UNITS; } -uses { BEGIN(ARGUMENT); return K_USES; } -value { BEGIN(ARGUMENT); return K_VALUE; } -when { BEGIN(STRING0); return K_WHEN; } -yang-version { BEGIN(ARGUMENT); return K_YANG_VERSION; } -yin-element { BEGIN(ARGUMENT); return K_YIN_ELEMENT; } +input { return K_INPUT; } /* No argument */ +output { return K_OUTPUT;} /* No argument */ + /* RFC 7950 keywords using identifier */ +action { BEGIN(STRARG); return K_ACTION; } +anydata { BEGIN(STRARG); return K_ANYDATA; } +anyxml { BEGIN(STRARG); return K_ANYXML; } +argument { BEGIN(STRARG); return K_ARGUMENT; } +base { BEGIN(STRARG); return K_BASE; } +belongs-to { BEGIN(STRARG); return K_BELONGS_TO; } +bit { BEGIN(STRARG); return K_BIT; } +case { BEGIN(STRARG); return K_CASE; } +choice { BEGIN(STRARG); return K_CHOICE; } +container { BEGIN(STRARG); return K_CONTAINER; } +extension { BEGIN(STRARG); return K_EXTENSION; } +feature { BEGIN(STRARG); return K_FEATURE; } +grouping { BEGIN(STRARG); return K_GROUPING; } +identity { BEGIN(STRARG); return K_IDENTITY; } +import { BEGIN(STRARG); return K_IMPORT; } +include { BEGIN(STRARG); return K_INCLUDE; } +leaf { BEGIN(STRARG); return K_LEAF; } +leaf-list { BEGIN(STRARG); return K_LEAF_LIST; } +list { BEGIN(STRARG); return K_LIST; } +module { BEGIN(STRARG); return K_MODULE; } +notification { BEGIN(STRARG); return K_NOTIFICATION; } +prefix { BEGIN(STRARG); return K_PREFIX; } +refine { BEGIN(STRARG); return K_REFINE; } +rpc { BEGIN(STRARG); return K_RPC; } +submodule { BEGIN(STRARG); return K_SUBMODULE; } +type { BEGIN(STRARG); return K_TYPE; } +typedef { BEGIN(STRARG); return K_TYPEDEF; } +uses { BEGIN(STRARG); return K_USES; } + + /* RFC 7950 keywords using boolean string arguments */ +config { BEGIN(BOOLEAN); return K_CONFIG; } +mandatory { BEGIN(BOOLEAN); return K_MANDATORY; } +require-instance { BEGIN(BOOLEAN); return K_REQUIRE_INSTANCE; } +yin-element { BEGIN(BOOLEAN); return K_YIN_ELEMENT; } + + /* RFC 7950 keywords using integer string argument */ +min-elements { BEGIN(INTEGER); return K_MIN_ELEMENTS; } +position { BEGIN(INTEGER); return K_POSITION; } +value { BEGIN(INTEGER); return K_VALUE; } + + /* RFC 7950 keywords using strings */ +augment { BEGIN(STRING); return K_AUGMENT; } +contact { BEGIN(STRING); return K_CONTACT; } +default { BEGIN(STRING); return K_DEFAULT; } +description { BEGIN(STRING); return K_DESCRIPTION; } +deviate { BEGIN(STRING); return K_DEVIATE; } +deviation { BEGIN(STRING); return K_DEVIATION; } +enum { BEGIN(STRING); return K_ENUM; } +error-app-tag { BEGIN(STRING); return K_ERROR_APP_TAG; } +error-message { BEGIN(STRING); return K_ERROR_MESSAGE; } +fraction-digits { BEGIN(STRING); return K_FRACTION_DIGITS; } +if-feature { BEGIN(STRING); return K_IF_FEATURE; } +key { BEGIN(STRING); return K_KEY; } +length { BEGIN(STRING); return K_LENGTH; } +max-elements { BEGIN(STRING); return K_MAX_ELEMENTS; } +must { BEGIN(STRING); return K_MUST; } +namespace { BEGIN(STRING); return K_NAMESPACE; } +ordered-by { BEGIN(STRING); return K_ORDERED_BY; } +organization { BEGIN(STRING); return K_ORGANIZATION; } +path { BEGIN(STRING); return K_PATH; } +pattern { BEGIN(STRING); return K_PATTERN; } +presence { BEGIN(STRING); return K_PRESENCE; } +unique { BEGIN(STRING); return K_UNIQUE; } +range { BEGIN(STRING); return K_RANGE; } +reference { BEGIN(STRING); return K_REFERENCE; } +revision { BEGIN(STRING); return K_REVISION; } +revision-date { BEGIN(STRING); return K_REVISION_DATE; } +status { BEGIN(STRING); return K_STATUS; } +units { BEGIN(STRING); return K_UNITS; } +when { BEGIN(STRING); return K_WHEN; } +yang-version { BEGIN(STRING); return K_YANG_VERSION; } : { return *yytext; } +\{ { return *yytext; } +\} { return *yytext; } ; { return *yytext; } . { clixon_yang_parselval.string = strdup(yytext); BEGIN(UNKNOWN); return CHAR; } @@ -190,34 +198,43 @@ clixon_yang_parsewrap(void) . { clixon_yang_parselval.string = strdup(yytext); return CHAR; } +true { clixon_yang_parselval.string = strdup(yytext); + return BOOL; } +false { clixon_yang_parselval.string = strdup(yytext); + return BOOL; } +; { BEGIN(KEYWORD); return *yytext; } +\{ { BEGIN(KEYWORD); return *yytext; } +. { return *yytext; } +[0-9][0-9]* { clixon_yang_parselval.string = strdup(yytext); + return INT; } +; { BEGIN(KEYWORD); return *yytext; } +\{ { BEGIN(KEYWORD); return *yytext; } +. { return *yytext; } -; { BEGIN(KEYWORD); return *yytext; } -\{ { BEGIN(KEYWORD); return *yytext; } -\" { _YY->yy_lex_string_state =ARGUMENT; BEGIN(STRING1); return DQ; } -\' { _YY->yy_lex_string_state =ARGUMENT; BEGIN(STRING2); return DQ; } -\+ { return *yytext; /* many arg rules dont like this */ } -: { return *yytext; /* many arg rules dont like this */ } -. { clixon_yang_parselval.string = strdup(yytext); - return CHAR;} +\{ { BEGIN(KEYWORD); return *yytext; } +; { BEGIN(KEYWORD); return *yytext; } +{identifier} { clixon_yang_parselval.string = strdup(yytext); + return IDENTIFIER;} +. { return *yytext; } -\{ { BEGIN(KEYWORD); return *yytext; } -; { BEGIN(KEYWORD); return *yytext; } -\" { _YY->yy_lex_string_state =STRING0; BEGIN(STRING1); return DQ; } -\' { _YY->yy_lex_string_state =STRING0; BEGIN(STRING2); return DQ; } -\+ { return *yytext; } -. { clixon_yang_parselval.string = strdup(yytext); +\{ { BEGIN(KEYWORD); return *yytext; } +; { BEGIN(KEYWORD); return *yytext; } +\" { _YY->yy_lex_string_state =STRING; BEGIN(STRINGDQ); return DQ; } +\' { _YY->yy_lex_string_state =STRING; BEGIN(STRINGSQ); return SQ; } +\+ { return *yytext; } +. { clixon_yang_parselval.string = strdup(yytext); return CHAR;} -\\ { _YY->yy_lex_state = STRING1; BEGIN(ESCAPE); } -\" { BEGIN(_YY->yy_lex_string_state); return DQ; } -\n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} -. { clixon_yang_parselval.string = strdup(yytext); - return CHAR;} +\\ { _YY->yy_lex_state = STRINGDQ; BEGIN(ESCAPE); } +\" { BEGIN(_YY->yy_lex_string_state); return DQ; } +\n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} +. { clixon_yang_parselval.string = strdup(yytext); + return CHAR;} -\' { BEGIN(_YY->yy_lex_string_state); return DQ; } -\n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} -. { clixon_yang_parselval.string = strdup(yytext); +\' { BEGIN(_YY->yy_lex_string_state); return SQ; } +\n { _YY->yy_linenum++; clixon_yang_parselval.string = strdup(yytext); return CHAR;} +. { clixon_yang_parselval.string = strdup(yytext); return CHAR;} . { BEGIN(_YY->yy_lex_state); diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 0a1fd81c..f947c833 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -34,9 +34,23 @@ * Yang parser. Hopefully useful but not complete * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 + * + * How identifiers map + * IDENTIFIER = [A-Za-z_][A-Za-z0-9_\-\.] + * prefix = IDENTIFIER + * identifier_arg = IDENTIFIER + * identifier_ref = prefix : IDENTIFIER + * node_identier = prefix : IDENTIFIER + * + * Missing top-level statements (will break parser if in yang spec): + * - error-app-tag-stmt + * - any-data-stmt + * Missing args (just strings); + * - length-arg-str + * - path-arg-str + * - date-arg-str */ - %start file %union { @@ -46,16 +60,25 @@ %token MY_EOF %token DQ /* Double quote: " */ -%token CHAR +%token SQ /* Single quote: ' */ +%token CHAR +%token IDENTIFIER +%token BOOL +%token INT %type ustring %type qstrings %type qstring %type string -%type id_arg_str -%type config_arg_str -%type integer_value -%type identifier_ref_arg_str +%type integer_value_str +%type identifier_ref +%type abs_schema_nodeid +%type desc_schema_node_str +%type desc_schema_nodeid +%type node_identifier +%type identifier_str +%type identifier_ref_str +%type bool_str /* rfc 6020 keywords @@ -295,31 +318,38 @@ ysp_add_push(struct clicon_yang_yacc_arg *yy, return ys; } -/* identifier-ref-arg-str has a [prefix :] id and prefix_id joins the id with an - optional prefix into a single string */ +/*! Join two string with delimiter. + * @param[in] str1 string 1 (will be freed) (optional) + * @param[in] del delimiter string (not freed) cannot be NULL (but "") + * @param[in] str2 string 2 (will be freed) + */ static char* -prefix_id_join(char *prefix, - char *id) +string_del_join(char *str1, + char *del, + char *str2) { char *str; int len; - if (prefix){ - len = strlen(prefix) + strlen(id) + 2; - if ((str = malloc(len)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - return NULL; - } - snprintf(str, len, "%s:%s", prefix, id); - free(prefix); - free(id); + len = strlen(str2) + 1; + + if (str1) + len += strlen(str1); + len += strlen(del); + if ((str = malloc(len)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + return NULL; + } + if (str1){ + snprintf(str, len, "%s%s%s", str1, del, str2); + free(str1); } else - str = id; + snprintf(str, len, "%s%s", del, str2); + free(str2); return str; } - %} %% @@ -339,20 +369,20 @@ file : module_stmt MY_EOF /* For extensions */ unknown_stmt : ustring ':' ustring ';' - { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("0"); if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("0"); clicon_debug(2,"unknown-stmt -> ustring : ustring"); } | ustring ':' ustring ' ' string ';' - { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("0"); if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("0"); } clicon_debug(2,"unknown-stmt -> ustring : ustring string"); if ($5) free($5); } ; -/* module */ -module_stmt : K_MODULE id_arg_str +/* module identifier-arg-str */ +module_stmt : K_MODULE identifier_str { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); } '{' module_substmts '}' @@ -376,7 +406,7 @@ module_substmt : module_header_stmts { clicon_debug(2,"module-substmt -> module- ; /* submodule */ -submodule_stmt : K_SUBMODULE id_arg_str '{' submodule_substmts '}' +submodule_stmt : K_SUBMODULE identifier_str '{' submodule_substmts '}' { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); clicon_debug(2,"submodule -> id-arg-str { submodule-stmts }"); } @@ -398,6 +428,17 @@ submodule_substmt : submodule_header_stmts | { clicon_debug(2,"submodule-substmt ->");} ; +/* linkage */ +linkage_stmts : linkage_stmts linkage_stmt + { clicon_debug(2,"linkage-stmts -> linkage-stmts linkage-stmt"); } + | linkage_stmt + { clicon_debug(2,"linkage-stmts -> linkage-stmt"); } + ; + +linkage_stmt : import_stmt { clicon_debug(2,"linkage-stmt -> import-stmt"); } + | include_stmt { clicon_debug(2,"linkage-stmt -> include-stmt"); } + ; + /* module-header */ module_header_stmts : module_header_stmts module_header_stmt { clicon_debug(2,"module-header-stmts -> module-header-stmts module-header-stmt"); } @@ -422,22 +463,81 @@ submodule_header_stmt : yang_version_stmt | belongs_to_stmt { clicon_debug(2,"submodule-header-stmt -> belongs-to-stmt"); } ; -/* linkage */ -linkage_stmts : linkage_stmts linkage_stmt - { clicon_debug(2,"linkage-stmts -> linkage-stmts linkage-stmt"); } - | linkage_stmt - { clicon_debug(2,"linkage-stmts -> linkage-stmt"); } +/* yang-version-stmt = yang-version-keyword yang-version-arg-str */ +yang_version_stmt : K_YANG_VERSION string ';' + { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("83"); + clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } ; -linkage_stmt : import_stmt { clicon_debug(2,"linkage-stmt -> import-stmt"); } - | include_stmt { clicon_debug(2,"linkage-stmt -> include-stmt"); } +/* import */ +import_stmt : K_IMPORT identifier_str + { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("81"); } + '{' import_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("82"); + clicon_debug(2,"import-stmt -> IMPORT id-arg-str { import-substmts }");} ; -/* revision */ -revision_stmts : revision_stmts revision_stmt - { clicon_debug(2,"revision-stmts -> revision-stmts revision-stmt"); } - | revision_stmt - { clicon_debug(2,"revision-stmts -> "); } +import_substmts : import_substmts import_substmt + { clicon_debug(2,"import-substmts -> import-substmts import-substm");} + | import_substmt + { clicon_debug(2,"import-substmts ->");} + ; + +import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } + | revision_date_stmt { clicon_debug(2,"import-stmt -> revision-date-stmt"); } + ; + +include_stmt : K_INCLUDE identifier_str ';' + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("97"); + clicon_debug(2,"include-stmt -> id-arg-str"); } + | K_INCLUDE identifier_str '{' revision_date_stmt '}' + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("98"); + clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } + ; + +/* namespace-stmt = namespace-keyword sep uri-str */ +namespace_stmt : K_NAMESPACE string stmtend + { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("99"); + clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } + ; + +prefix_stmt : K_PREFIX identifier_str ';' /* XXX prefix-arg-str */ + { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("100"); + clicon_debug(2,"prefix-stmt -> PREFIX string ;");} + ; + +belongs_to_stmt : K_BELONGS_TO identifier_str ';' + + { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("100"); + clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str ;");} + | K_BELONGS_TO identifier_str '{' prefix_stmt '}' + { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("98"); + clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");} + ; + +organization_stmt: K_ORGANIZATION string ';' + { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("102"); + clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} + ; + +contact_stmt : K_CONTACT string ';' + { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("95"); + clicon_debug(2,"contact-stmt -> CONTACT string"); } + ; + +description_stmt: K_DESCRIPTION string ';' + { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101"); + clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} + ; + +reference_stmt: K_REFERENCE string ';' + { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("105"); + clicon_debug(2,"reference-stmt -> REFERENCE string ;");} + ; + +units_stmt : K_UNITS string ';' + { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("93"); + clicon_debug(2,"units-stmt -> UNITS string"); } ; revision_stmt : K_REVISION string ';' /* XXX date-arg-str */ @@ -463,469 +563,118 @@ revision_substmt : description_stmt { clicon_debug(2,"revision-substmt -> descri ; - -/* meta */ -meta_stmts : meta_stmts meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmts meta-stmt"); } - | meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmt"); } +/* revision */ +revision_stmts : revision_stmts revision_stmt + { clicon_debug(2,"revision-stmts -> revision-stmts revision-stmt"); } + | revision_stmt + { clicon_debug(2,"revision-stmts -> "); } ; -meta_stmt : organization_stmt { clicon_debug(2,"meta-stmt -> organization-stmt"); } - | contact_stmt { clicon_debug(2,"meta-stmt -> contact-stmt"); } - | description_stmt { clicon_debug(2,"meta-stmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"meta-stmt -> reference-stmt"); } +revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ + { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("96"); + clicon_debug(2,"revision-date-stmt -> date;"); } ; -/* body */ -body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts body-stmt"); } - | body_stmt { clicon_debug(2,"body-stmts -> body-stmt");} +/* Extension */ +extension_stmt: K_EXTENSION identifier_str ';' + { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("59"); + clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } + | K_EXTENSION identifier_str + { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } + '{' extension_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("61"); + clicon_debug(2,"extension-stmt -> FEATURE id-arg-str { extension-substmts }"); } + ; + + +/* extension substmts */ +extension_substmts : extension_substmts extension_substmt + { clicon_debug(2,"extension-substmts -> extension-substmts extension-substmt"); } + | extension_substmt + { clicon_debug(2,"extension-substmts -> extension-substmt"); } ; -body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-stmt");} - | feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");} - | identity_stmt { clicon_debug(2,"body-stmt -> identity-stmt");} - | typedef_stmt { clicon_debug(2,"body-stmt -> typedef-stmt");} - | grouping_stmt { clicon_debug(2,"body-stmt -> grouping-stmt");} - | data_def_stmt { clicon_debug(2,"body-stmt -> data-def-stmt");} - | augment_stmt { clicon_debug(2,"body-stmt -> augment-stmt");} - | rpc_stmt { clicon_debug(2,"body-stmt -> rpc-stmt");} - | notification_stmt { clicon_debug(2,"body-stmt -> notification-stmt");} +extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argument-stmt"); } + | status_stmt { clicon_debug(2,"extension-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"extension-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"extension-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"extension-substmt -> unknown-stmt");} + | { clicon_debug(2,"extension-substmt -> "); } ; -data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> container-stmt");} - | leaf_stmt { clicon_debug(2,"data-def-stmt -> leaf-stmt");} - | leaf_list_stmt { clicon_debug(2,"data-def-stmt -> leaf-list-stmt");} - | list_stmt { clicon_debug(2,"data-def-stmt -> list-stmt");} - | choice_stmt { clicon_debug(2,"data-def-stmt -> choice-stmt");} - | anyxml_stmt { clicon_debug(2,"data-def-stmt -> anyxml-stmt");} - | uses_stmt { clicon_debug(2,"data-def-stmt -> uses-stmt");} +argument_stmt : K_ARGUMENT identifier_str ';' { free($2); } + | K_ARGUMENT identifier_str '{' yin_element_stmt1 '}' { free($2); } + ; + +/* Example of optional rule, eg [yin-element-stmt] */ +yin_element_stmt1 : K_YIN_ELEMENT bool_str stmtend {free($2);} + ; + +/* Identity */ +identity_stmt : K_IDENTITY identifier_str ';' + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("65"); + clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } + + | K_IDENTITY identifier_str + { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("66"); } + '{' identity_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("67"); + clicon_debug(2,"identity-stmt -> IDENTITY string { identity-substmts }"); } ; -/* container */ -container_stmt : K_CONTAINER id_arg_str ';' - { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("7"); - clicon_debug(2,"container-stmt -> CONTAINER id-arg-str ;");} - | K_CONTAINER id_arg_str - { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("8"); } - '{' container_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("9"); - clicon_debug(2,"container-stmt -> CONTAINER id-arg-str { container-substmts }");} +identity_substmts : identity_substmts identity_substmt + { clicon_debug(2,"identity-substmts -> identity-substmts identity-substmt"); } + | identity_substmt + { clicon_debug(2,"identity-substmts -> identity-substmt"); } ; -container_substmts : container_substmts container_substmt - | container_substmt +identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } + | status_stmt { clicon_debug(2,"identity-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"identity-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"identity-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"identity-substmt -> unknown-stmt");} + | { clicon_debug(2,"identity-substmt -> "); } ; -container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"container-substmt -> if-feature-stmt"); } - | must_stmt { clicon_debug(2,"container-substmt -> must-stmt"); } - | presence_stmt { clicon_debug(2,"container-substmt -> presence-stmt"); } - | config_stmt { clicon_debug(2,"container-substmt -> config-stmt"); } - | status_stmt { clicon_debug(2,"container-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"container-substmt -> description-stmt");} - | reference_stmt { clicon_debug(2,"container-substmt -> reference-stmt"); } - | typedef_stmt { clicon_debug(2,"container-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"container-substmt -> grouping-stmt"); } - | data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");} - | action_stmt { clicon_debug(2,"container-substmt -> action-stmt");} - | notification_stmt { clicon_debug(2,"container-substmt -> notification-stmt");} - | unknown_stmt { clicon_debug(2,"container-substmt -> unknown-stmt");} - | { clicon_debug(2,"container-substmt ->");} +base_stmt : K_BASE identifier_ref_str ';' + { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("90"); + clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } ; -/* leaf */ -leaf_stmt : K_LEAF id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("10"); - clicon_debug(2,"leaf-stmt -> LEAF id-arg-str ;");} - | K_LEAF id_arg_str - { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("11"); } - '{' leaf_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("12"); - clicon_debug(2,"leaf-stmt -> LEAF id-arg-str { lead-substmts }");} +/* Feature */ +feature_stmt : K_FEATURE identifier_str ';' + { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("62"); + clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } + | K_FEATURE identifier_str + { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } + '{' feature_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("64"); + clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); } ; -leaf_substmts : leaf_substmts leaf_substmt - | leaf_substmt +/* feature substmts */ +feature_substmts : feature_substmts feature_substmt + { clicon_debug(2,"feature-substmts -> feature-substmts feature-substmt"); } + | feature_substmt + { clicon_debug(2,"feature-substmts -> feature-substmt"); } ; -leaf_substmt : when_stmt { clicon_debug(2,"leaf-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"leaf-substmt -> if-feature-stmt"); } - | type_stmt { clicon_debug(2,"leaf-substmt -> type-stmt"); } - | units_stmt { clicon_debug(2,"leaf-substmt -> units-stmt"); } - | must_stmt { clicon_debug(2,"leaf-substmt -> must-stmt"); } - | default_stmt { clicon_debug(2,"leaf-substmt -> default-stmt"); } - | config_stmt { clicon_debug(2,"leaf-substmt -> config-stmt"); } - | mandatory_stmt { clicon_debug(2,"leaf-substmt -> mandatory-stmt"); } - | status_stmt { clicon_debug(2,"leaf-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"leaf-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"leaf-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"leaf-substmt -> unknown-stmt");} - | { clicon_debug(2,"leaf-substmt ->"); } +feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-feature-stmt"); } + | status_stmt { clicon_debug(2,"feature-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"feature-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"feature-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"feature-substmt -> unknown-stmt");} + | { clicon_debug(2,"feature-substmt -> "); } ; -/* leaf-list */ -leaf_list_stmt : K_LEAF_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("13"); - clicon_debug(2,"leaf-list-stmt -> LEAF id-arg-str ;");} - | K_LEAF_LIST id_arg_str - { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("14"); } - '{' leaf_list_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("15"); - clicon_debug(2,"leaf-list-stmt -> LEAF-LIST id-arg-str { lead-substmts }");} +/* if-feature-stmt = if-feature-keyword sep if-feature-expr-str */ +if_feature_stmt : K_IF_FEATURE string stmtend + { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("85"); + clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } ; -leaf_list_substmts : leaf_list_substmts leaf_list_substmt - | leaf_list_substmt - ; - -leaf_list_substmt : when_stmt { clicon_debug(2,"leaf-list-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"leaf-list-substmt -> if-feature-stmt"); } - | type_stmt { clicon_debug(2,"leaf-list-substmt -> type-stmt"); } - | units_stmt { clicon_debug(2,"leaf-list-substmt -> units-stmt"); } - | must_stmt { clicon_debug(2,"leaf-list-substmt -> must-stmt"); } - | config_stmt { clicon_debug(2,"leaf-list-substmt -> config-stmt"); } - | min_elements_stmt { clicon_debug(2,"leaf-list-substmt -> min-elements-stmt"); } - | max_elements_stmt { clicon_debug(2,"leaf-list-substmt -> max-elements-stmt"); } - | ordered_by_stmt { clicon_debug(2,"leaf-list-substmt -> ordered-by-stmt"); } - | status_stmt { clicon_debug(2,"leaf-list-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"leaf-list-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"leaf-list-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"leaf-list-substmt -> unknown-stmt");} - | { clicon_debug(2,"leaf-list-stmt ->"); } - ; - -/* list */ -list_stmt : K_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("16"); - clicon_debug(2,"list-stmt -> LIST id-arg-str ;"); } - | K_LIST id_arg_str - { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("17"); } - '{' list_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("18"); - clicon_debug(2,"list-stmt -> LIST id-arg-str { list-substmts }"); } - ; - -list_substmts : list_substmts list_substmt - { clicon_debug(2,"list-substmts -> list-substmts list-substmt"); } - | list_substmt - { clicon_debug(2,"list-substmts -> list-substmt"); } - ; - -list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"list-substmt -> if-feature-stmt"); } - | must_stmt { clicon_debug(2,"list-substmt -> must-stmt"); } - | key_stmt { clicon_debug(2,"list-substmt -> key-stmt"); } - | unique_stmt { clicon_debug(2,"list-substmt -> unique-stmt"); } - | config_stmt { clicon_debug(2,"list-substmt -> config-stmt"); } - | min_elements_stmt { clicon_debug(2,"list-substmt -> min-elements-stmt"); } - | max_elements_stmt { clicon_debug(2,"list-substmt -> max-elements-stmt"); } - | ordered_by_stmt { clicon_debug(2,"list-substmt -> ordered-by-stmt"); } - | status_stmt { clicon_debug(2,"list-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"list-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"list-substmt -> reference-stmt"); } - | typedef_stmt { clicon_debug(2,"list-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"list-substmt -> grouping-stmt"); } - | data_def_stmt { clicon_debug(2,"list-substmt -> data-def-stmt"); } - | action_stmt { clicon_debug(2,"list-substmt -> action-stmt"); } - | notification_stmt { clicon_debug(2,"list-substmt -> notification-stmt"); } - | unknown_stmt { clicon_debug(2,"list-substmt -> unknown-stmt");} - | { clicon_debug(2,"list-substmt -> "); } - ; - -/* choice */ -choice_stmt : K_CHOICE id_arg_str ';' - { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("19"); - clicon_debug(2,"choice-stmt -> CHOICE id-arg-str ;"); } - | K_CHOICE id_arg_str - { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("20"); } - '{' choice_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("21"); - clicon_debug(2,"choice-stmt -> CHOICE id-arg-str { choice-substmts }"); } - ; - -choice_substmts : choice_substmts choice_substmt - { clicon_debug(2,"choice-substmts -> choice-substmts choice-substmt"); } - | choice_substmt - { clicon_debug(2,"choice-substmts -> choice-substmt"); } - ; - -choice_substmt : when_stmt { clicon_debug(2,"choice-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"choice-substmt -> if-feature-stmt"); } - | default_stmt { clicon_debug(2,"choice-substmt -> default-stmt"); } - | config_stmt { clicon_debug(2,"choice-substmt -> config-stmt"); } - | mandatory_stmt { clicon_debug(2,"choice-substmt -> mandatory-stmt"); } - | status_stmt { clicon_debug(2,"choice-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"choice-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"choice-substmt -> reference-stmt"); } - | short_case_stmt { clicon_debug(2,"choice-substmt -> short-case-stmt");} - | case_stmt { clicon_debug(2,"choice-substmt -> case-stmt");} - | unknown_stmt { clicon_debug(2,"choice-substmt -> unknown-stmt");} - | { clicon_debug(2,"choice-substmt -> "); } - ; - -/* short-case */ -short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> container-stmt"); } - | leaf_stmt { clicon_debug(2,"short-case-substmt -> leaf-stmt"); } - | leaf_list_stmt { clicon_debug(2,"short-case-substmt -> leaf-list-stmt"); } - | list_stmt { clicon_debug(2,"short-case-substmt -> list-stmt"); } - | anyxml_stmt { clicon_debug(2,"short-case-substmt -> anyxml-stmt");} - ; - -/* case */ -case_stmt : K_CASE id_arg_str ';' - { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("22"); - clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } - | K_CASE id_arg_str - { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } - '{' case_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("24"); - clicon_debug(2,"case-stmt -> CASE id-arg-str { case-substmts }"); } - ; - -case_substmts : case_substmts case_substmt - { clicon_debug(2,"case-substmts -> case-substmts case-substmt"); } - | case_substmt - { clicon_debug(2,"case-substmts -> case-substmt"); } - ; - -case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"case-substmt -> if-feature-stmt"); } - | status_stmt { clicon_debug(2,"case-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"case-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"case-substmt -> reference-stmt"); } - | data_def_stmt { clicon_debug(2,"case-substmt -> data-def-stmt");} - | unknown_stmt { clicon_debug(2,"case-substmt -> unknown-stmt");} - | { clicon_debug(2,"case-substmt -> "); } - ; - - -/* anyxml */ -anyxml_stmt : K_ANYXML id_arg_str ';' - { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("25"); - clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } - | K_ANYXML id_arg_str - { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } - '{' anyxml_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("27"); - clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str { anyxml-substmts }"); } - ; - -anyxml_substmts : anyxml_substmts anyxml_substmt - { clicon_debug(2,"anyxml-substmts -> anyxml-substmts anyxml-substmt"); } - | anyxml_substmt - { clicon_debug(2,"anyxml-substmts -> anyxml-substmt"); } - ; - -anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"anyxml-substmt -> if-feature-stmt"); } - | must_stmt { clicon_debug(2,"anyxml-substmt -> must-stmt"); } - | config_stmt { clicon_debug(2,"anyxml-substmt -> config-stmt"); } - | mandatory_stmt { clicon_debug(2,"anyxml-substmt -> mandatory-stmt"); } - | status_stmt { clicon_debug(2,"anyxml-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"anyxml-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"anyxml-substmt -> reference-stmt"); } - | ustring ':' ustring ';' { free($1); free($3); clicon_debug(2,"anyxml-substmt -> anyxml extension"); } - ; - -/* uses */ -uses_stmt : K_USES identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("28"); - clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } - | K_USES identifier_ref_arg_str - { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } - '{' uses_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("30"); - clicon_debug(2,"uses-stmt -> USES id-arg-str { uses-substmts }"); } - ; - -uses_substmts : uses_substmts uses_substmt - { clicon_debug(2,"uses-substmts -> uses-substmts uses-substmt"); } - | uses_substmt - { clicon_debug(2,"uses-substmts -> uses-substmt"); } - ; - -uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"uses-substmt -> if-feature-stmt"); } - | status_stmt { clicon_debug(2,"uses-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"uses-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"uses-substmt -> reference-stmt"); } - | refine_stmt { clicon_debug(2,"uses-substmt -> refine-stmt"); } - | uses_augment_stmt { clicon_debug(2,"uses-substmt -> uses-augment-stmt"); } - | unknown_stmt { clicon_debug(2,"uses-substmt -> unknown-stmt");} - | { clicon_debug(2,"uses-substmt -> "); } - ; - -/* refine XXX need further refining */ -refine_stmt : K_REFINE id_arg_str ';' - { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("31"); - clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } - | K_REFINE id_arg_str - { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } - '{' refine_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("33"); - clicon_debug(2,"refine-stmt -> REFINE id-arg-str { refine-substmts }"); } - ; - -refine_substmts : refine_substmts refine_substmt - { clicon_debug(2,"refine-substmts -> refine-substmts refine-substmt"); } - | refine_substmt - { clicon_debug(2,"refine-substmts -> refine-substmt"); } - ; - -refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } - | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } - | default_stmt { clicon_debug(2,"refine-substmt -> default-stmt"); } - | unknown_stmt { clicon_debug(2,"refine-substmt -> unknown-stmt");} - | { clicon_debug(2,"refine-substmt -> "); } - ; - -/* augment */ -uses_augment_stmt : augment_stmt; - -augment_stmt : K_AUGMENT string - { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } - '{' augment_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("35"); - clicon_debug(2,"augment-stmt -> AUGMENT string { augment-substmts }"); } - ; - -augment_substmts : augment_substmts augment_substmt - { clicon_debug(2,"augment-substmts -> augment-substmts augment-substmt"); } - | augment_substmt - { clicon_debug(2,"augment-substmts -> augment-substmt"); } - ; - -augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-stmt"); } - | if_feature_stmt { clicon_debug(2,"augment-substmt -> if-feature-stmt"); } - | status_stmt { clicon_debug(2,"augment-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"augment-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"augment-substmt -> reference-stmt"); } - | data_def_stmt { clicon_debug(2,"augment-substmt -> data-def-stmt"); } - | case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");} - | action_stmt { clicon_debug(2,"augment-substmt -> action-stmt");} - | notification_stmt { clicon_debug(2,"augment-substmt -> notification-stmt");} - | { clicon_debug(2,"augment-substmt -> "); } - ; - -/* when */ -when_stmt : K_WHEN string ';' - { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("36"); - clicon_debug(2,"when-stmt -> WHEN string ;"); } - | K_WHEN string - { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } - '{' when_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("38"); - clicon_debug(2,"when-stmt -> WHEN string { when-substmts }"); } - ; - -when_substmts : when_substmts when_substmt - { clicon_debug(2,"when-substmts -> when-substmts when-substmt"); } - | when_substmt - { clicon_debug(2,"when-substmts -> when-substmt"); } - ; - -when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"when-substmt -> reference-stmt"); } - | { clicon_debug(2,"when-substmt -> "); } - ; - -/* rpc */ -rpc_stmt : K_RPC id_arg_str ';' - { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("39"); - clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } - | K_RPC id_arg_str - { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } - '{' rpc_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("41"); - clicon_debug(2,"rpc-stmt -> RPC id-arg-str { rpc-substmts }"); } - ; - -rpc_substmts : rpc_substmts rpc_substmt - { clicon_debug(2,"rpc-substmts -> rpc-substmts rpc-substmt"); } - | rpc_substmt - { clicon_debug(2,"rpc-substmts -> rpc-substmt"); } - ; - -rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stmt"); } - | status_stmt { clicon_debug(2,"rpc-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"rpc-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"rpc-substmt -> reference-stmt"); } - | typedef_stmt { clicon_debug(2,"rpc-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"rpc-substmt -> grouping-stmt"); } - | input_stmt { clicon_debug(2,"rpc-substmt -> input-stmt"); } - | output_stmt { clicon_debug(2,"rpc-substmt -> output-stmt"); } - | { clicon_debug(2,"rpc-substmt -> "); } - ; - -/* action */ -action_stmt : K_ACTION id_arg_str ';' - { if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("39"); - clicon_debug(2,"action-stmt -> ACTION id-arg-str ;"); } - | K_ACTION id_arg_str - { if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("40"); } - '{' rpc_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("41"); - clicon_debug(2,"action-stmt -> ACTION id-arg-str { rpc-substmts }"); } - ; - -/* input */ -input_stmt : K_INPUT - { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); } - '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("43"); - clicon_debug(2,"input-stmt -> INPUT { input-substmts }"); } - ; - -input_substmts : input_substmts input_substmt - { clicon_debug(2,"input-substmts -> input-substmts input-substmt"); } - | input_substmt - { clicon_debug(2,"input-substmts -> input-substmt"); } - ; - -input_substmt : typedef_stmt { clicon_debug(2,"input-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"input-substmt -> grouping-stmt"); } - | data_def_stmt { clicon_debug(2,"input-substmt -> data-def-stmt"); } - | { clicon_debug(2,"input-substmt -> "); } - ; - -/* output */ -output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */ - { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("44"); } - '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("45"); - clicon_debug(2,"output-stmt -> OUTPUT { input-substmts }"); } - ; - - -/* notification */ -notification_stmt : K_NOTIFICATION id_arg_str ';' - { if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("46"); - clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str ;"); } - | K_NOTIFICATION id_arg_str - { if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("47"); } - '{' notification_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("48"); - clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str { notification-substmts }"); } - ; - -notification_substmts : notification_substmts notification_substmt - { clicon_debug(2,"notification-substmts -> notification-substmts notification-substmt"); } - | notification_substmt - { clicon_debug(2,"notification-substmts -> notification-substmt"); } - ; - -notification_substmt : if_feature_stmt { clicon_debug(2,"notification-substmt -> if-feature-stmt"); } - | must_stmt { clicon_debug(2,"notification-substmt -> must-stmt"); } - | status_stmt { clicon_debug(2,"notification-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"notification-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"notification-substmt -> reference-stmt"); } - | typedef_stmt { clicon_debug(2,"notification-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"notification-substmt -> grouping-stmt"); } - | data_def_stmt { clicon_debug(2,"notification-substmt -> data-def-stmt"); } - | { clicon_debug(2,"notification-substmt -> "); } - ; - /* Typedef */ -typedef_stmt : K_TYPEDEF id_arg_str +typedef_stmt : K_TYPEDEF identifier_str { if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); } '{' typedef_substmts '}' { if (ystack_pop(_yy) < 0) _YYERROR("47"); @@ -949,10 +698,10 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s ; /* Type */ -type_stmt : K_TYPE identifier_ref_arg_str ';' +type_stmt : K_TYPE identifier_ref_str ';' { if (ysp_add(_yy, Y_TYPE, $2, NULL) == NULL) _YYERROR("48"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");} - | K_TYPE identifier_ref_arg_str + | K_TYPE identifier_ref_str { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49"); } '{' type_body_stmts '}' @@ -966,8 +715,8 @@ type_stmt : K_TYPE identifier_ref_arg_str ';' */ type_body_stmts : type_body_stmts type_body_stmt { clicon_debug(2,"type-body-stmts -> type-body-stmts type-body-stmt"); } - | type_body_stmt - { clicon_debug(2,"type-body-stmts -> type-body-stmt"); } + | + { clicon_debug(2,"type-body-stmts -> "); } ; type_body_stmt/* numerical-restrictions */ @@ -991,31 +740,40 @@ type_body_stmt/* numerical-restrictions */ | type_stmt { clicon_debug(2,"type-body-stmt -> type-stmt"); } ; +/* range-stmt */ +range_stmt : K_RANGE string ';' /* XXX range-arg-str */ + { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("68"); + clicon_debug(2,"range-stmt -> RANGE string ;"); } - -/* Grouping */ -grouping_stmt : K_GROUPING id_arg_str - { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("51"); } - '{' grouping_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("52"); - clicon_debug(2,"grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } + | K_RANGE string + { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("69"); } + '{' range_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("70"); + clicon_debug(2,"range-stmt -> RANGE string { range-substmts }"); } ; -grouping_substmts : grouping_substmts grouping_substmt - { clicon_debug(2,"grouping-substmts -> grouping-substmts grouping-substmt"); } - | grouping_substmt - { clicon_debug(2,"grouping-substmts -> grouping-substmt"); } +range_substmts : range_substmts range_substmt + { clicon_debug(2,"range-substmts -> range-substmts range-substmt"); } + | range_substmt + { clicon_debug(2,"range-substmts -> range-substmt"); } ; -grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"grouping-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"grouping-substmt -> reference-stmt"); } - | typedef_stmt { clicon_debug(2,"grouping-substmt -> typedef-stmt"); } - | grouping_stmt { clicon_debug(2,"grouping-substmt -> grouping-stmt"); } - | data_def_stmt { clicon_debug(2,"grouping-substmt -> data-def-stmt"); } - | action_stmt { clicon_debug(2,"grouping-substmt -> action-stmt"); } - | notification_stmt { clicon_debug(2,"grouping-substmt -> notification-stmt"); } - | { clicon_debug(2,"grouping-substmt -> "); } +range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-message-stmt");} + | description_stmt { clicon_debug(2,"range-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"range-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"range-substmt -> unknown-stmt");} + | { clicon_debug(2,"range-substmt -> "); } + ; + +/* fraction-digits-stmt = fraction-digits-keyword fraction-digits-arg-str */ +fraction_digits_stmt : K_FRACTION_DIGITS string stmtend + { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("84"); + clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } + ; + +/* meta */ +meta_stmts : meta_stmts meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmts meta-stmt"); } + | meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmt"); } ; /* length-stmt */ @@ -1067,113 +825,12 @@ pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> refere | { clicon_debug(2,"pattern-substmt -> "); } ; -/* Extension */ -extension_stmt: K_EXTENSION id_arg_str ';' - { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("59"); - clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } - | K_EXTENSION id_arg_str - { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } - '{' extension_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("61"); - clicon_debug(2,"extension-stmt -> FEATURE id-arg-str { extension-substmts }"); } - ; - - -/* extension substmts */ -extension_substmts : extension_substmts extension_substmt - { clicon_debug(2,"extension-substmts -> extension-substmts extension-substmt"); } - | extension_substmt - { clicon_debug(2,"extension-substmts -> extension-substmt"); } +default_stmt : K_DEFAULT string ';' + { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("94"); + clicon_debug(2,"default-stmt -> DEFAULT string"); } ; -extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argument-stmt"); } - | status_stmt { clicon_debug(2,"extension-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"extension-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"extension-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"extension-substmt -> unknown-stmt");} - | { clicon_debug(2,"extension-substmt -> "); } - ; -argument_stmt : K_ARGUMENT id_arg_str ';' { free($2); } - | K_ARGUMENT id_arg_str '{' '}' { free($2); } - ; - -/* Feature */ -feature_stmt : K_FEATURE id_arg_str ';' - { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("62"); - clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } - | K_FEATURE id_arg_str - { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } - '{' feature_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("64"); - clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); } - ; - -/* feature substmts */ -feature_substmts : feature_substmts feature_substmt - { clicon_debug(2,"feature-substmts -> feature-substmts feature-substmt"); } - | feature_substmt - { clicon_debug(2,"feature-substmts -> feature-substmt"); } - ; - -feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-feature-stmt"); } - | status_stmt { clicon_debug(2,"feature-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"feature-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"feature-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"feature-substmt -> unknown-stmt");} - | { clicon_debug(2,"feature-substmt -> "); } - ; - -/* Identity */ -identity_stmt : K_IDENTITY string ';' /* XXX identifier-arg-str */ - { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("65"); - clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } - - | K_IDENTITY string - { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("66"); } - '{' identity_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("67"); - clicon_debug(2,"identity-stmt -> IDENTITY string { identity-substmts }"); } - ; - -identity_substmts : identity_substmts identity_substmt - { clicon_debug(2,"identity-substmts -> identity-substmts identity-substmt"); } - | identity_substmt - { clicon_debug(2,"identity-substmts -> identity-substmt"); } - ; - -identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } - | status_stmt { clicon_debug(2,"identity-substmt -> status-stmt"); } - | description_stmt { clicon_debug(2,"identity-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"identity-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"identity-substmt -> unknown-stmt");} - | { clicon_debug(2,"identity-substmt -> "); } - ; - -/* range-stmt */ -range_stmt : K_RANGE string ';' /* XXX range-arg-str */ - { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("68"); - clicon_debug(2,"range-stmt -> RANGE string ;"); } - - | K_RANGE string - { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("69"); } - '{' range_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("70"); - clicon_debug(2,"range-stmt -> RANGE string { range-substmts }"); } - ; - -range_substmts : range_substmts range_substmt - { clicon_debug(2,"range-substmts -> range-substmts range-substmt"); } - | range_substmt - { clicon_debug(2,"range-substmts -> range-substmt"); } - ; - -range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-message-stmt");} - | description_stmt { clicon_debug(2,"range-substmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"range-substmt -> reference-stmt"); } - | unknown_stmt { clicon_debug(2,"range-substmt -> unknown-stmt");} - | { clicon_debug(2,"range-substmt -> "); } - ; /* enum-stmt */ enum_stmt : K_ENUM string ';' @@ -1200,11 +857,21 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm | { clicon_debug(2,"enum-substmt -> "); } ; +path_stmt : K_PATH string ';' /* XXX: path-arg-str */ + { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("91"); + clicon_debug(2,"path-stmt -> PATH string"); } + ; + +require_instance_stmt : K_REQUIRE_INSTANCE bool_str ';' + { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("92"); + clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } + ; + /* bit-stmt */ -bit_stmt : K_BIT string ';' +bit_stmt : K_BIT identifier_str ';' { if (ysp_add(_yy, Y_BIT, $2, NULL) == NULL) _YYERROR("74"); clicon_debug(2,"bit-stmt -> BIT string ;"); } - | K_BIT string + | K_BIT identifier_str { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); } '{' bit_substmts '}' { if (ystack_pop(_yy) < 0) _YYERROR("76"); @@ -1224,7 +891,44 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition | { clicon_debug(2,"bit-substmt -> "); } ; -/* mus-stmt */ +/* position-stmt = position-keyword position-value-arg-str */ +position_stmt : K_POSITION integer_value_str stmtend + { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("87"); + clicon_debug(2,"position-stmt -> POSITION integer-value"); } + ; + +/* status-stmt = status-keyword sep status-arg-str XXX: current-keyword*/ +status_stmt : K_STATUS string stmtend + { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("88"); + clicon_debug(2,"status-stmt -> STATUS string"); } + ; + +config_stmt : K_CONFIG bool_str ';' + { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("89"); + clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } + ; + +/* mandatory-stmt = mandatory-keyword mandatory-arg-str */ +mandatory_stmt: K_MANDATORY bool_str ';' + { yang_stmt *ys; + if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("106"); + clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} + ; + +presence_stmt: K_PRESENCE string ';' + { yang_stmt *ys; + if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("107"); + clicon_debug(2,"presence-stmt -> PRESENCE string ;");} + ; + +/* ordered-by-stmt = ordered-by-keyword sep ordered-by-arg-str */ +ordered_by_stmt: K_ORDERED_BY string stmtend + { yang_stmt *ys; + if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("108"); + clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} + ; + +/* must-stmt */ must_stmt : K_MUST string ';' { if (ysp_add(_yy, Y_MUST, $2, NULL) == NULL) _YYERROR("77"); clicon_debug(2,"must-stmt -> MUST string ;"); } @@ -1251,194 +955,571 @@ must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-mes /* error-message-stmt */ error_message_stmt : K_ERROR_MESSAGE string ';' { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("80"); } + ; -/* import */ -import_stmt : K_IMPORT id_arg_str - { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("81"); } - '{' import_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("82"); - clicon_debug(2,"import-stmt -> IMPORT id-arg-str { import-substmts }");} - ; +/* XXX error-app-tag-stmt */ -import_substmts : import_substmts import_substmt - { clicon_debug(2,"import-substmts -> import-substmts import-substm");} - | import_substmt - { clicon_debug(2,"import-substmts ->");} - ; - -import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } - | revision_date_stmt { clicon_debug(2,"import-stmt -> revision-date-stmt"); } - ; - - -/* Simple statements */ -yang_version_stmt : K_YANG_VERSION string ';' /* XXX yang-version-arg-str */ - { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("83"); - clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } - ; - -fraction_digits_stmt : K_FRACTION_DIGITS string ';' /* XXX: fraction-digits-arg-str */ - { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("84"); - clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } - ; - -if_feature_stmt : K_IF_FEATURE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("85"); - clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } - ; - -value_stmt : K_VALUE integer_value ';' - { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("86"); - clicon_debug(2,"value-stmt -> VALUE integer-value"); } - ; - -position_stmt : K_POSITION integer_value ';' - { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("87"); - clicon_debug(2,"position-stmt -> POSITION integer-value"); } - ; - -status_stmt : K_STATUS string ';' /* XXX: status-arg-str */ - { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("88"); - clicon_debug(2,"status-stmt -> STATUS string"); } - ; - -config_stmt : K_CONFIG config_arg_str ';' - { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("89"); - clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } - ; - - -base_stmt : K_BASE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("90"); - clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } - ; - -path_stmt : K_PATH string ';' /* XXX: path-arg-str */ - { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("91"); - clicon_debug(2,"path-stmt -> PATH string"); } - ; - -require_instance_stmt : K_REQUIRE_INSTANCE string ';' /* XXX: require-instance-arg-str */ - { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("92"); - clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } - ; - -units_stmt : K_UNITS string ';' - { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("93"); - clicon_debug(2,"units-stmt -> UNITS string"); } - ; - -default_stmt : K_DEFAULT string ';' - { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("94"); - clicon_debug(2,"default-stmt -> DEFAULT string"); } - ; - -contact_stmt : K_CONTACT string ';' - { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("95"); - clicon_debug(2,"contact-stmt -> CONTACT string"); } - ; - -revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("96"); - clicon_debug(2,"revision-date-stmt -> date;"); } - ; - -include_stmt : K_INCLUDE id_arg_str ';' - { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("97"); - clicon_debug(2,"include-stmt -> id-arg-str"); } - | K_INCLUDE id_arg_str '{' revision_date_stmt '}' - { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("98"); - clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } - ; - -namespace_stmt : K_NAMESPACE string ';' /* XXX uri-str */ - { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("99"); - clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } - ; - -prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */ - { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("100"); - clicon_debug(2,"prefix-stmt -> PREFIX string ;");} - ; - -belongs_to_stmt : K_BELONGS_TO id_arg_str ';' - - { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("100"); - clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str ;");} - | K_BELONGS_TO id_arg_str '{' prefix_stmt '}' - { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("98"); - clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");} - ; - -description_stmt: K_DESCRIPTION string ';' - { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101"); - clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} - ; - -organization_stmt: K_ORGANIZATION string ';' - { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("102"); - clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} - ; - -min_elements_stmt: K_MIN_ELEMENTS integer_value ';' +/* min-elements-stmt = min-elements-keyword min-value-arg-str */ +min_elements_stmt: K_MIN_ELEMENTS integer_value_str stmtend { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2, NULL)== NULL) _YYERROR("103"); clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");} ; -max_elements_stmt: K_MAX_ELEMENTS integer_value ';' +/* max-elements-stmt = max-elements-keyword ("unbounded"|integer-value) + * XXX cannot use integer-value + */ +max_elements_stmt: K_MAX_ELEMENTS string ';' { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2, NULL)== NULL) _YYERROR("104"); clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");} ; -reference_stmt: K_REFERENCE string ';' - { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("105"); - clicon_debug(2,"reference-stmt -> REFERENCE string ;");} +value_stmt : K_VALUE integer_value_str ';' + { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("86"); + clicon_debug(2,"value-stmt -> VALUE integer-value"); } ; -mandatory_stmt: K_MANDATORY string ';' - { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("106"); - clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} +/* Grouping */ +grouping_stmt : K_GROUPING identifier_str + { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("51"); } + '{' grouping_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("52"); + clicon_debug(2,"grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } ; -presence_stmt: K_PRESENCE string ';' - { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("107"); - clicon_debug(2,"presence-stmt -> PRESENCE string ;");} +grouping_substmts : grouping_substmts grouping_substmt + { clicon_debug(2,"grouping-substmts -> grouping-substmts grouping-substmt"); } + | grouping_substmt + { clicon_debug(2,"grouping-substmts -> grouping-substmt"); } ; -ordered_by_stmt: K_ORDERED_BY string ';' - { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("108"); - clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} +grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"grouping-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"grouping-substmt -> reference-stmt"); } + | typedef_stmt { clicon_debug(2,"grouping-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"grouping-substmt -> grouping-stmt"); } + | data_def_stmt { clicon_debug(2,"grouping-substmt -> data-def-stmt"); } + | action_stmt { clicon_debug(2,"grouping-substmt -> action-stmt"); } + | notification_stmt { clicon_debug(2,"grouping-substmt -> notification-stmt"); } + | { clicon_debug(2,"grouping-substmt -> "); } ; -key_stmt : K_KEY id_arg_str ';' /* XXX key_arg_str */ + +/* container */ +container_stmt : K_CONTAINER identifier_str ';' + { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("7"); + clicon_debug(2,"container-stmt -> CONTAINER id-arg-str ;");} + | K_CONTAINER identifier_str + { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("8"); } + '{' container_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("9"); + clicon_debug(2,"container-stmt -> CONTAINER id-arg-str { container-substmts }");} + ; + +container_substmts : container_substmts container_substmt + | container_substmt + ; + +container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"container-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"container-substmt -> must-stmt"); } + | presence_stmt { clicon_debug(2,"container-substmt -> presence-stmt"); } + | config_stmt { clicon_debug(2,"container-substmt -> config-stmt"); } + | status_stmt { clicon_debug(2,"container-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"container-substmt -> description-stmt");} + | reference_stmt { clicon_debug(2,"container-substmt -> reference-stmt"); } + | typedef_stmt { clicon_debug(2,"container-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"container-substmt -> grouping-stmt"); } + | data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");} + | action_stmt { clicon_debug(2,"container-substmt -> action-stmt");} + | notification_stmt { clicon_debug(2,"container-substmt -> notification-stmt");} + | unknown_stmt { clicon_debug(2,"container-substmt -> unknown-stmt");} + | { clicon_debug(2,"container-substmt ->");} + ; + +/* leaf */ +leaf_stmt : K_LEAF identifier_str ';' + { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("10"); + clicon_debug(2,"leaf-stmt -> LEAF id-arg-str ;");} + | K_LEAF identifier_str + { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("11"); } + '{' leaf_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("12"); + clicon_debug(2,"leaf-stmt -> LEAF id-arg-str { lead-substmts }");} + ; + +leaf_substmts : leaf_substmts leaf_substmt + | leaf_substmt + ; + +leaf_substmt : when_stmt { clicon_debug(2,"leaf-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"leaf-substmt -> if-feature-stmt"); } + | type_stmt { clicon_debug(2,"leaf-substmt -> type-stmt"); } + | units_stmt { clicon_debug(2,"leaf-substmt -> units-stmt"); } + | must_stmt { clicon_debug(2,"leaf-substmt -> must-stmt"); } + | default_stmt { clicon_debug(2,"leaf-substmt -> default-stmt"); } + | config_stmt { clicon_debug(2,"leaf-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"leaf-substmt -> mandatory-stmt"); } + | status_stmt { clicon_debug(2,"leaf-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"leaf-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"leaf-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"leaf-substmt -> unknown-stmt");} + | { clicon_debug(2,"leaf-substmt ->"); } + ; + +/* leaf-list */ +leaf_list_stmt : K_LEAF_LIST identifier_str ';' + { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("13"); + clicon_debug(2,"leaf-list-stmt -> LEAF id-arg-str ;");} + | K_LEAF_LIST identifier_str + { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("14"); } + '{' leaf_list_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("15"); + clicon_debug(2,"leaf-list-stmt -> LEAF-LIST id-arg-str { lead-substmts }");} + ; + +leaf_list_substmts : leaf_list_substmts leaf_list_substmt + | leaf_list_substmt + ; + +leaf_list_substmt : when_stmt { clicon_debug(2,"leaf-list-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"leaf-list-substmt -> if-feature-stmt"); } + | type_stmt { clicon_debug(2,"leaf-list-substmt -> type-stmt"); } + | units_stmt { clicon_debug(2,"leaf-list-substmt -> units-stmt"); } + | must_stmt { clicon_debug(2,"leaf-list-substmt -> must-stmt"); } + | config_stmt { clicon_debug(2,"leaf-list-substmt -> config-stmt"); } + | min_elements_stmt { clicon_debug(2,"leaf-list-substmt -> min-elements-stmt"); } + | max_elements_stmt { clicon_debug(2,"leaf-list-substmt -> max-elements-stmt"); } + | ordered_by_stmt { clicon_debug(2,"leaf-list-substmt -> ordered-by-stmt"); } + | status_stmt { clicon_debug(2,"leaf-list-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"leaf-list-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"leaf-list-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"leaf-list-substmt -> unknown-stmt");} + | { clicon_debug(2,"leaf-list-stmt ->"); } + ; + +/* list */ +list_stmt : K_LIST identifier_str ';' + { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("16"); + clicon_debug(2,"list-stmt -> LIST id-arg-str ;"); } + | K_LIST identifier_str + { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("17"); } + '{' list_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("18"); + clicon_debug(2,"list-stmt -> LIST id-arg-str { list-substmts }"); } + ; + +list_substmts : list_substmts list_substmt + { clicon_debug(2,"list-substmts -> list-substmts list-substmt"); } + | list_substmt + { clicon_debug(2,"list-substmts -> list-substmt"); } + ; + +list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"list-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"list-substmt -> must-stmt"); } + | key_stmt { clicon_debug(2,"list-substmt -> key-stmt"); } + | unique_stmt { clicon_debug(2,"list-substmt -> unique-stmt"); } + | config_stmt { clicon_debug(2,"list-substmt -> config-stmt"); } + | min_elements_stmt { clicon_debug(2,"list-substmt -> min-elements-stmt"); } + | max_elements_stmt { clicon_debug(2,"list-substmt -> max-elements-stmt"); } + | ordered_by_stmt { clicon_debug(2,"list-substmt -> ordered-by-stmt"); } + | status_stmt { clicon_debug(2,"list-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"list-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"list-substmt -> reference-stmt"); } + | typedef_stmt { clicon_debug(2,"list-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"list-substmt -> grouping-stmt"); } + | data_def_stmt { clicon_debug(2,"list-substmt -> data-def-stmt"); } + | action_stmt { clicon_debug(2,"list-substmt -> action-stmt"); } + | notification_stmt { clicon_debug(2,"list-substmt -> notification-stmt"); } + | unknown_stmt { clicon_debug(2,"list-substmt -> unknown-stmt");} + | { clicon_debug(2,"list-substmt -> "); } + ; + +/* key-stmt = key-keyword sep key-arg-str */ +key_stmt : K_KEY string stmtend { if (ysp_add(_yy, Y_KEY, $2, NULL)== NULL) _YYERROR("109"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; -unique_stmt : K_UNIQUE id_arg_str ';' /* XXX key_arg_str */ +/* unique-stmt = unique-keyword unique-arg-str */ +unique_stmt : K_UNIQUE string stmtend { if (ysp_add(_yy, Y_UNIQUE, $2, NULL)== NULL) _YYERROR("110"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; -config_arg_str : string { $$=$1; } /* XXX BOOL */ - ; - -integer_value : string { $$=$1; } - ; - -identifier_ref_arg_str : string - { if (($$=prefix_id_join(NULL, $1)) == NULL) _YYERROR("111"); - clicon_debug(2,"identifier-ref-arg-str -> string"); } - | string ':' string - { if (($$=prefix_id_join($1, $3)) == NULL) _YYERROR("112"); - clicon_debug(2,"identifier-ref-arg-str -> prefix : string"); } +/* choice */ +choice_stmt : K_CHOICE identifier_str ';' + { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("19"); + clicon_debug(2,"choice-stmt -> CHOICE id-arg-str ;"); } + | K_CHOICE identifier_str + { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("20"); } + '{' choice_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("21"); + clicon_debug(2,"choice-stmt -> CHOICE id-arg-str { choice-substmts }"); } ; -id_arg_str : string { $$=$1; clicon_debug(2,"id-arg-str -> string"); } - ; +choice_substmts : choice_substmts choice_substmt + { clicon_debug(2,"choice-substmts -> choice-substmts choice-substmt"); } + | choice_substmt + { clicon_debug(2,"choice-substmts -> choice-substmt"); } + ; + +choice_substmt : when_stmt { clicon_debug(2,"choice-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"choice-substmt -> if-feature-stmt"); } + | default_stmt { clicon_debug(2,"choice-substmt -> default-stmt"); } + | config_stmt { clicon_debug(2,"choice-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"choice-substmt -> mandatory-stmt"); } + | status_stmt { clicon_debug(2,"choice-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"choice-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"choice-substmt -> reference-stmt"); } + | short_case_stmt { clicon_debug(2,"choice-substmt -> short-case-stmt");} + | case_stmt { clicon_debug(2,"choice-substmt -> case-stmt");} + | unknown_stmt { clicon_debug(2,"choice-substmt -> unknown-stmt");} + | { clicon_debug(2,"choice-substmt -> "); } + ; + +/* case */ +case_stmt : K_CASE identifier_str ';' + { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("22"); + clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } + | K_CASE identifier_str + { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } + '{' case_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("24"); + clicon_debug(2,"case-stmt -> CASE id-arg-str { case-substmts }"); } + ; + +case_substmts : case_substmts case_substmt + { clicon_debug(2,"case-substmts -> case-substmts case-substmt"); } + | case_substmt + { clicon_debug(2,"case-substmts -> case-substmt"); } + ; + +case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"case-substmt -> if-feature-stmt"); } + | status_stmt { clicon_debug(2,"case-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"case-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"case-substmt -> reference-stmt"); } + | data_def_stmt { clicon_debug(2,"case-substmt -> data-def-stmt");} + | unknown_stmt { clicon_debug(2,"case-substmt -> unknown-stmt");} + | { clicon_debug(2,"case-substmt -> "); } + ; + +anydata_stmt : K_ANYDATA identifier_str ';' + { if (ysp_add(_yy, Y_ANYDATA, $2, NULL) == NULL) _YYERROR("25"); + clicon_debug(2,"anydata-stmt -> ANYDATA id-arg-str ;"); } + | K_ANYDATA identifier_str + { if (ysp_add_push(_yy, Y_ANYDATA, $2) == NULL) _YYERROR("26"); } + '{' anyxml_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("27"); + clicon_debug(2,"anydata-stmt -> ANYDATA id-arg-str { anyxml-substmts }"); } + ; + +/* anyxml */ +anyxml_stmt : K_ANYXML identifier_str ';' + { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("25"); + clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } + | K_ANYXML identifier_str + { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } + '{' anyxml_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("27"); + clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str { anyxml-substmts }"); } + ; + +anyxml_substmts : anyxml_substmts anyxml_substmt + { clicon_debug(2,"anyxml-substmts -> anyxml-substmts anyxml-substmt"); } + | anyxml_substmt + { clicon_debug(2,"anyxml-substmts -> anyxml-substmt"); } + ; + +anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"anyxml-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"anyxml-substmt -> must-stmt"); } + | config_stmt { clicon_debug(2,"anyxml-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"anyxml-substmt -> mandatory-stmt"); } + | status_stmt { clicon_debug(2,"anyxml-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"anyxml-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"anyxml-substmt -> reference-stmt"); } + | ustring ':' ustring ';' { free($1); free($3); clicon_debug(2,"anyxml-substmt -> anyxml extension"); } + ; + +/* uses-stmt = uses-keyword identifier-ref-arg-str */ +uses_stmt : K_USES identifier_ref_str ';' + { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("28"); + clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } + | K_USES identifier_ref_str + { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } + '{' uses_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("30"); + clicon_debug(2,"uses-stmt -> USES id-arg-str { uses-substmts }"); } + ; + +uses_substmts : uses_substmts uses_substmt + { clicon_debug(2,"uses-substmts -> uses-substmts uses-substmt"); } + | uses_substmt + { clicon_debug(2,"uses-substmts -> uses-substmt"); } + ; + +uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"uses-substmt -> if-feature-stmt"); } + | status_stmt { clicon_debug(2,"uses-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"uses-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"uses-substmt -> reference-stmt"); } + | refine_stmt { clicon_debug(2,"uses-substmt -> refine-stmt"); } + | uses_augment_stmt { clicon_debug(2,"uses-substmt -> uses-augment-stmt"); } + | unknown_stmt { clicon_debug(2,"uses-substmt -> unknown-stmt");} + | { clicon_debug(2,"uses-substmt -> "); } + ; + +/* refine-stmt = refine-keyword sep refine-arg-str */ +refine_stmt : K_REFINE desc_schema_node_str ';' + { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("31"); + clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } + | K_REFINE desc_schema_node_str + { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } + '{' refine_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("33"); + clicon_debug(2,"refine-stmt -> REFINE id-arg-str { refine-substmts }"); } + ; + +refine_substmts : refine_substmts refine_substmt + { clicon_debug(2,"refine-substmts -> refine-substmts refine-substmt"); } + | refine_substmt + { clicon_debug(2,"refine-substmts -> refine-substmt"); } + ; + +refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } + | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } + | default_stmt { clicon_debug(2,"refine-substmt -> default-stmt"); } + | unknown_stmt { clicon_debug(2,"refine-substmt -> unknown-stmt");} + | { clicon_debug(2,"refine-substmt -> "); } + ; + +/* uses-augment-stmt = augment-keyword augment-arg-str */ +uses_augment_stmt : K_AUGMENT desc_schema_node_str + { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } + '{' augment_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("35"); + clicon_debug(2,"uses-augment-stmt -> AUGMENT desc-schema-node-str { augment-substmts }"); } + + +/* augment-stmt = augment-keyword sep augment-arg-str + * XXX abs-schema-nodeid-str is too difficult, it needs the + semantics + */ +augment_stmt : K_AUGMENT string + { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } + '{' augment_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("35"); + clicon_debug(2,"augment-stmt -> AUGMENT abs-schema-node-str { augment-substmts }"); } + ; + +augment_substmts : augment_substmts augment_substmt + { clicon_debug(2,"augment-substmts -> augment-substmts augment-substmt"); } + | augment_substmt + { clicon_debug(2,"augment-substmts -> augment-substmt"); } + ; + +augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"augment-substmt -> if-feature-stmt"); } + | status_stmt { clicon_debug(2,"augment-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"augment-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"augment-substmt -> reference-stmt"); } + | data_def_stmt { clicon_debug(2,"augment-substmt -> data-def-stmt"); } + | case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");} + | action_stmt { clicon_debug(2,"augment-substmt -> action-stmt");} + | notification_stmt { clicon_debug(2,"augment-substmt -> notification-stmt");} + | { clicon_debug(2,"augment-substmt -> "); } + ; + +/* when */ +when_stmt : K_WHEN string ';' + { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("36"); + clicon_debug(2,"when-stmt -> WHEN string ;"); } + | K_WHEN string + { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } + '{' when_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("38"); + clicon_debug(2,"when-stmt -> WHEN string { when-substmts }"); } + ; + +when_substmts : when_substmts when_substmt + { clicon_debug(2,"when-substmts -> when-substmts when-substmt"); } + | when_substmt + { clicon_debug(2,"when-substmts -> when-substmt"); } + ; + +when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"when-substmt -> reference-stmt"); } + | { clicon_debug(2,"when-substmt -> "); } + ; + +/* rpc */ +rpc_stmt : K_RPC identifier_str ';' + { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("39"); + clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } + | K_RPC identifier_str + { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } + '{' rpc_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("41"); + clicon_debug(2,"rpc-stmt -> RPC id-arg-str { rpc-substmts }"); } + ; + +rpc_substmts : rpc_substmts rpc_substmt + { clicon_debug(2,"rpc-substmts -> rpc-substmts rpc-substmt"); } + | rpc_substmt + { clicon_debug(2,"rpc-substmts -> rpc-substmt"); } + ; + +rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stmt"); } + | status_stmt { clicon_debug(2,"rpc-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"rpc-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"rpc-substmt -> reference-stmt"); } + | typedef_stmt { clicon_debug(2,"rpc-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"rpc-substmt -> grouping-stmt"); } + | input_stmt { clicon_debug(2,"rpc-substmt -> input-stmt"); } + | output_stmt { clicon_debug(2,"rpc-substmt -> output-stmt"); } + | { clicon_debug(2,"rpc-substmt -> "); } + ; + +/* action */ +action_stmt : K_ACTION identifier_str ';' + { if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("39"); + clicon_debug(2,"action-stmt -> ACTION id-arg-str ;"); } + | K_ACTION identifier_str + { if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("40"); } + '{' rpc_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("41"); + clicon_debug(2,"action-stmt -> ACTION id-arg-str { rpc-substmts }"); } + ; + +/* notification */ +notification_stmt : K_NOTIFICATION identifier_str ';' + { if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("46"); + clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str ;"); } + | K_NOTIFICATION identifier_str + { if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("47"); } + '{' notification_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("48"); + clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str { notification-substmts }"); } + ; + +notification_substmts : notification_substmts notification_substmt + { clicon_debug(2,"notification-substmts -> notification-substmts notification-substmt"); } + | notification_substmt + { clicon_debug(2,"notification-substmts -> notification-substmt"); } + ; + +notification_substmt : if_feature_stmt { clicon_debug(2,"notification-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"notification-substmt -> must-stmt"); } + | status_stmt { clicon_debug(2,"notification-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"notification-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"notification-substmt -> reference-stmt"); } + | typedef_stmt { clicon_debug(2,"notification-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"notification-substmt -> grouping-stmt"); } + | data_def_stmt { clicon_debug(2,"notification-substmt -> data-def-stmt"); } + | { clicon_debug(2,"notification-substmt -> "); } + ; + +/* deviation /oc-sys:system/oc-sys:config/oc-sys:hostname { + deviate not-supported; + } + * XXX abs-schema-nodeid-str is too difficult, it needs the + semantics + +*/ +deviation_stmt : K_DEVIATION string + { if (ysp_add_push(_yy, Y_DEVIATION, $2) == NULL) _YYERROR("47"); } + '{' deviation_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("48"); + clicon_debug(2,"deviation-stmt -> DEVIATION id-arg-str { notification-substmts }"); } + ; + +deviation_substmts : deviation_substmts deviation_substmt + { clicon_debug(2,"deviation-substmts -> deviation-substmts deviation-substmt"); } + | deviation_substmt + { clicon_debug(2,"deviation-substmts -> deviation-substmt"); } + ; + +deviation_substmt : description_stmt { clicon_debug(2,"deviation-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"deviation-substmt -> reference-stmt"); } + | deviate_not_supported_stmt { clicon_debug(2,"deviation-substmt -> deviate-not-supported-stmt"); } + ; + +deviate_not_supported_stmt : K_DEVIATE string { clicon_debug(2,"deviate-not-supported-stmt -> DEVIATE string"); } + ; + +meta_stmt : organization_stmt { clicon_debug(2,"meta-stmt -> organization-stmt"); } + | contact_stmt { clicon_debug(2,"meta-stmt -> contact-stmt"); } + | description_stmt { clicon_debug(2,"meta-stmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"meta-stmt -> reference-stmt"); } + ; + +/* body */ +body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts body-stmt"); } + | body_stmt { clicon_debug(2,"body-stmts -> body-stmt");} + ; + +body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-stmt");} + | feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");} + | identity_stmt { clicon_debug(2,"body-stmt -> identity-stmt");} + | typedef_stmt { clicon_debug(2,"body-stmt -> typedef-stmt");} + | grouping_stmt { clicon_debug(2,"body-stmt -> grouping-stmt");} + | data_def_stmt { clicon_debug(2,"body-stmt -> data-def-stmt");} + | augment_stmt { clicon_debug(2,"body-stmt -> augment-stmt");} + | rpc_stmt { clicon_debug(2,"body-stmt -> rpc-stmt");} + | notification_stmt { clicon_debug(2,"body-stmt -> notification-stmt");} + | deviation_stmt { clicon_debug(2,"body-stmt -> deviation-stmt");} + ; + +data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> container-stmt");} + | leaf_stmt { clicon_debug(2,"data-def-stmt -> leaf-stmt");} + | leaf_list_stmt { clicon_debug(2,"data-def-stmt -> leaf-list-stmt");} + | list_stmt { clicon_debug(2,"data-def-stmt -> list-stmt");} + | choice_stmt { clicon_debug(2,"data-def-stmt -> choice-stmt");} + | anydata_stmt { clicon_debug(2,"data-def-stmt -> anydata-stmt");} + | anyxml_stmt { clicon_debug(2,"data-def-stmt -> anyxml-stmt");} + | uses_stmt { clicon_debug(2,"data-def-stmt -> uses-stmt");} + ; + +/* short-case */ +short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> container-stmt"); } + | leaf_stmt { clicon_debug(2,"short-case-substmt -> leaf-stmt"); } + | leaf_list_stmt { clicon_debug(2,"short-case-substmt -> leaf-list-stmt"); } + | list_stmt { clicon_debug(2,"short-case-substmt -> list-stmt"); } + | anydata_stmt { clicon_debug(2,"short-case-substmt -> anydata-stmt");} + | anyxml_stmt { clicon_debug(2,"short-case-substmt -> anyxml-stmt");} + ; + + +/* input */ +input_stmt : K_INPUT + { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); } + '{' input_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("43"); + clicon_debug(2,"input-stmt -> INPUT { input-substmts }"); } + ; + +input_substmts : input_substmts input_substmt + { clicon_debug(2,"input-substmts -> input-substmts input-substmt"); } + | input_substmt + { clicon_debug(2,"input-substmts -> input-substmt"); } + ; + +input_substmt : typedef_stmt { clicon_debug(2,"input-substmt -> typedef-stmt"); } + | grouping_stmt { clicon_debug(2,"input-substmt -> grouping-stmt"); } + | data_def_stmt { clicon_debug(2,"input-substmt -> data-def-stmt"); } + | { clicon_debug(2,"input-substmt -> "); } + ; + +/* output */ +output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */ + { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("44"); } + '{' input_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("45"); + clicon_debug(2,"output-stmt -> OUTPUT { input-substmts }"); } + ; + + + string : qstrings { $$=$1; clicon_debug(2,"string -> qstrings (%s)", $1); } | ustring { $$=$1; clicon_debug(2,"string -> ustring (%s)", $1); } @@ -1457,7 +1538,8 @@ qstrings : qstrings '+' qstring { $$=$1; clicon_debug(2,"qstrings-> qstring"); } ; -qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \""); } +qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"");} + | SQ ustring SQ { $$=$2; clicon_debug(2,"string-> ' ustring '"); } ; /* unquoted string */ @@ -1473,6 +1555,67 @@ ustring : ustring CHAR ; +abs_schema_nodeid : abs_schema_nodeid '/' node_identifier + { if (($$=string_del_join($1, "/", $3)) == NULL) _YYERROR("0"); + clicon_debug(2,"absolute-schema-nodeid -> absolute-schema-nodeid / node-identifier"); } + | '/' node_identifier + { if (($$=string_del_join(NULL, "/", $2)) == NULL) _YYERROR("0"); + clicon_debug(2,"absolute-schema-nodeid -> / node-identifier"); } + ; + +desc_schema_node_str : desc_schema_nodeid + { $$=$1; clicon_debug(2,"descendant-schema-node-str -> descendant-node"); } + | '"' desc_schema_nodeid '"' + { $$=$2; clicon_debug(2,"descendant-schema-node-str -> descendant-node"); } + ; + +desc_schema_nodeid : node_identifier + { $$= $1; clicon_debug(2,"descendant-schema-nodeid -> node_identifier"); } + | node_identifier abs_schema_nodeid + { if (($$=string_del_join($1, " ", $2)) == NULL) _YYERROR("0");clicon_debug(2,"descendant-schema-nodeid -> node_identifier abs_schema_nodeid"); } + ; + +identifier_str : '"' IDENTIFIER '"' { $$ = $2; + clicon_debug(2,"identifier_str -> \" IDENTIFIER \" ");} + | IDENTIFIER { $$ = $1; + clicon_debug(2,"identifier_str -> IDENTIFIER ");} + ; + +identifier_ref_str : '"' identifier_ref '"' { $$ = $2; + clicon_debug(2,"identifier_ref_str -> \" identifier_ref \" ");} + | identifier_ref { $$ = $1; + clicon_debug(2,"identifier_ref_str -> identifier_ref ");} + ; + +integer_value_str : '"' INT '"' { $$=$2; } + | INT { $$=$1; } + ; + +bool_str : '"' BOOL '"' { $$ = $2; + clicon_debug(2,"bool_str -> \" BOOL \" ");} + | BOOL { $$ = $1; + clicon_debug(2,"bool_str -> BOOL ");} + ; + + +/* node-identifier = [prefix ":"] identifier */ +node_identifier : IDENTIFIER + { $$=$1; clicon_debug(2,"identifier-ref-arg-str -> string"); } + | IDENTIFIER ':' IDENTIFIER + { if (($$=string_del_join($1, ":", $3)) == NULL) _YYERROR("112"); + clicon_debug(2,"identifier-ref-arg-str -> prefix : string"); } + ; + +/* ;;; Basic Rules */ + +/* identifier-ref = [prefix ":"] identifier */ +identifier_ref : node_identifier { $$=$1;} + ; + +stmtend : ';' + | '{' '}' + | '{' unknown_stmt '}' + ; %% diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh new file mode 100755 index 00000000..6ed5423b --- /dev/null +++ b/test/test_openconfig.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Parse yang openconfig tests +#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" +PROG=../util/clixon_util_yang +OPENCONFIG=~/syssrc/openconfig + +# include err() and new() functions and creates $dir +. ./lib.sh + +# Openconfig +# Files not parseable: +# - openconfig-access-points.yang +# - openconfig-access-points.yang +new "Openconfig" +files=$(find $OPENCONFIG -name "*.yang") +for f in $files; do + new "$f" + YANG=$(cat $f) + # NYI + expecteof "$PROG" 0 "$YANG" "module" +done +rm -rf $dir + + diff --git a/test/test_yang_parse.sh b/test/test_yang_parse.sh index c8024a3d..3790645f 100755 --- a/test/test_yang_parse.sh +++ b/test/test_yang_parse.sh @@ -1,27 +1,11 @@ #!/bin/bash # Test: YANG parser tests -# First an example yang, second all openconfig yangs -# Problem with this is that util only parses single file. it should -# call yang_parse(). #PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" PROG=../util/clixon_util_yang -OPENCONFIG=~/syssrc/openconfig -exit 0 # nyi + # include err() and new() functions and creates $dir . ./lib.sh -# Openconfig -# Files not parseable: -# - openconfig-access-points.yang -# - openconfig-access-points.yang -new "Openconfig" -files=$(find $OPENCONFIG -name "*.yang") -for f in $files; do - new "$f" - YANG=$(cat $f) - # NYI - expecteof "$PROG" 0 "$YANG" "module" -done rm -rf $dir From 39a5086218bc90780fb79666cf34e227becd4d97 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 1 Dec 2018 18:17:42 +0100 Subject: [PATCH 06/72] * Yang Support of submodule, include and belongs-to. * Improved unknown handling * Configure option `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list --- CHANGELOG.md | 6 + README.md | 16 +- apps/backend/backend_main.c | 9 +- apps/cli/cli_generate.c | 36 ++- apps/cli/cli_main.c | 3 +- apps/netconf/netconf_main.c | 3 +- apps/netconf/netconf_rpc.c | 10 +- apps/restconf/restconf_main.c | 14 +- configure | 11 +- configure.ac | 7 +- datastore/datastore_client.c | 19 +- datastore/text/clixon_xmldb_text.c | 6 +- doc/FAQ.md | 24 +- doc/Makefile.in | 2 + example/example.xml | 1 + include/clixon_config.h.in | 8 +- lib/clixon/clixon_options.h | 3 - lib/clixon/clixon_yang.h | 14 +- lib/src/clixon_netconf_lib.c | 4 +- lib/src/clixon_options.c | 29 +- lib/src/clixon_yang.c | 424 +++++++++++++++++++---------- lib/src/clixon_yang_cardinality.c | 18 +- lib/src/clixon_yang_module.c | 12 +- lib/src/clixon_yang_parse.y | 394 ++++++++++++++------------- lib/src/clixon_yang_type.c | 51 ++-- test/lib.sh | 16 +- test/test_cli.sh | 1 + test/test_datastore.sh | 4 +- test/test_feature.sh | 1 + test/test_identity.sh | 1 + test/test_leafref.sh | 1 + test/test_list.sh | 1 + test/test_nacm.sh | 1 + test/test_nacm_ext.sh | 1 + test/test_netconf.sh | 2 +- test/test_openconfig.sh | 102 ++++++- test/test_order.sh | 1 + test/test_perf.sh | 7 +- test/test_restconf.sh | 1 + test/test_restconf2.sh | 1 + test/test_startup.sh | 1 + test/test_stream.sh | 3 +- test/test_type.sh | 97 +++++++ test/test_when_must.sh | 1 + test/test_yang.sh | 68 ++++- util/clixon_util_yang.c | 2 + yang/clixon-config@2018-10-21.yang | 9 +- 47 files changed, 977 insertions(+), 469 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a80cdd9a..8ed77983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,16 @@ * More complete Yang parser * YANG parser cardinality checked (only modules level yet) * See https://github.com/clicon/clixon/issues/84 + * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public + * Improved unknown handling + * `CLICON_YANG_DIR` is changed from a single directory to a path of directories + * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list ### API changes on existing features (you may need to change your code) * Yang parser is stricter (see above) which may break parsing of existing yang specs. +* Yang parser functions have changed signatures. Please check the source if you call these functions. +* Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. ### Minor changes * XML parser conformance to W3 spec diff --git a/README.md b/README.md index 9cbb2e32..278374e5 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ support. * [Tests](test/) * [Docker](docker/) * [Roadmap](ROADMAP.md) - * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) + * [Reference manual](#reference) Background ========== @@ -124,6 +124,10 @@ However, the following YANG syntax modules are not implemented: - action - belongs-to +Restrictions on Yang types are as follows: +- The range statement does not support multiple values (RFC7895 sec 9.2.4) +- Submodules cannot re-use a prefix in an import statement that is already used for another imported module in the module that the submodule belongs to. + Netconf ======= Clixon implements the following NETCONF proposals or standards: @@ -217,7 +221,6 @@ The tests outlines an example of three groups (taken from the RFC): admin, limit * limited: Read access (get and get-config) * guest: No access - Runtime ======= @@ -225,3 +228,12 @@ Runtime The figure shows the SDK runtime of Clixon. +Reference +========= +A reference manual can be built using [Doxygen](http://www.doxygen.nl/index.html). You need to install doxygen and graphviz on your system. +Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows: +``` +> cd doc +> make doc +> make graphs # detailed callgraphs +``` \ No newline at end of file diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 3856eb0e..76a5dca9 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -264,7 +264,7 @@ nacm_load_external(clicon_handle h) } if ((yspec = yspec_new()) == NULL) goto done; - if (yang_parse(h, NULL, "ietf-netconf-acm", CLIXON_DATADIR, NULL, yspec, NULL) < 0) + if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec, NULL) < 0) goto done; fd = fileno(f); /* Read configfile */ @@ -753,11 +753,10 @@ main(int argc, /* Load main application yang specification either module or specific file * If -y is given, it overrides main module */ if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0) + if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) goto done; } else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_dir(h), clicon_yang_module_revision(h), yspec, NULL) < 0) goto done; @@ -770,11 +769,11 @@ main(int argc, goto done; /* Load yang Restconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) goto done; /* Load yang Netconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) goto done; /* Set options: database dir and yangspec (could be hidden in connect?)*/ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 22f76883..022e41b8 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -51,7 +51,7 @@ #include #include #include - +#include /* For pow() kludge in cvtype_max2str_dup2 */ /* cligen */ #include @@ -169,6 +169,28 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, cbuf *cb, static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, yang_stmt *ytype, cbuf *cb, char *helptext); +/*! Patched maxstring to account for DEC64 types + * @note kludge to fix overflow error -> Fix the original error in cvtype_max2str + * by adding a fraction_digits argument. + */ +static char * +cvtype_max2str_dup2(enum cv_type type, + int fraction_digits) +{ + int len; + char *str; + + if (type!=CGV_DEC64 || fraction_digits==0) + return cvtype_max2str_dup(type); + if ((len = cvtype_max2str(type, NULL, 0)) < 0) + return NULL; + if ((str = (char *)malloc(len+1)) == NULL) + return NULL; + memset(str, '\0', len+1); + len = snprintf(str, len+1, "%" PRId64 ".0", (INT64_MAX/((int)pow(10,fraction_digits)))); + return str; +} + /*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type * Check for completion (of already existent values), ranges (eg range[min:max]) and * patterns, (eg regexp:"[0.9]*"). @@ -282,10 +304,12 @@ yang2cli_var_sub(clicon_handle h, } snprintf(r, 512, "%d", MAXPATHLEN); } - else if ((r = cvtype_max2str_dup(cvtype)) == NULL){ + else { + if ((r = cvtype_max2str_dup2(cvtype, fraction_digits)) == NULL){ clicon_err(OE_UNIX, errno, "cvtype_max2str"); goto done; } + } } cprintf(cb, "%s]", r); /* range */ free(r); @@ -410,7 +434,7 @@ yang2cli_var(clicon_handle h, cbuf *cb, char *helptext) { - int retval = -1; + int retval = -1; char *origtype; yang_stmt *yrestype; /* resolved type */ char *restype; /* resolved type */ @@ -513,10 +537,12 @@ yang2cli_leaf(clicon_handle h, if (helptext) cprintf(cbuf, "(\"%s\")", helptext); cprintf(cbuf, " "); - yang2cli_var(h, ys, cbuf, helptext); + if (yang2cli_var(h, ys, cbuf, helptext) < 0) + goto done; } else - yang2cli_var(h, ys, cbuf, helptext); + if (yang2cli_var(h, ys, cbuf, helptext) < 0) + goto done; if (callback){ if (cli_callback_generate(h, ys, cbuf) < 0) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index c651127e..5e887543 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -433,11 +433,10 @@ main(int argc, char **argv) /* Load main application yang specification either module or specific file * If -y is given, it overrides main module */ if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, &ymod) < 0) + if (yang_spec_parse_file(h, yang_filename, yspec, &ymod) < 0) goto done; } else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_dir(h), clicon_yang_module_revision(h), yspec, &ymod) < 0) goto done; diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 6f3f30bc..6326c283 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -447,11 +447,10 @@ main(int argc, /* Load main application yang specification either module or specific file * If -y is given, it overrides main module */ if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0) + if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) goto done; } else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_dir(h), clicon_yang_module_revision(h), yspec, NULL) < 0) goto done; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 838f0c73..a8627b66 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -32,7 +32,13 @@ ***** END LICENSE BLOCK ***** * - * Code for handling netconf rpc messages according to RFC 4741 and RFC 5277 + * Code for handling netconf rpc messages according to RFC 4741,5277,6241 + * All NETCONF protocol elements are defined in the following namespace: + * urn:ietf:params:xml:ns:netconf:base:1.0 + * YANG defines an XML namespace for NETCONF operations, + * content, and the element. The name of this + * namespace is "urn:ietf:params:xml:ns:yang:1". + * *****************************************************************************/ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -894,7 +900,7 @@ netconf_application_rpc(clicon_handle h, } cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0) + if (yang_abs_schema_nodeid(yspec, xml_spec(xn), cbuf_get(cb), Y_RPC, &yrpc) < 0) goto done; /* Check if found */ if (yrpc != NULL){ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index ed7fdc43..5909e8db 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -517,7 +517,6 @@ main(int argc, char *sockpath; char *path; clicon_handle h; - char *yangspec=NULL; char *dir; char *tmp; int logdst = CLICON_LOG_SYSLOG; @@ -619,10 +618,6 @@ main(int argc, argc -= optind; argv += optind; - /* Overwrite yang module with -y option */ - if (yangspec) - clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", yangspec); - /* Initialize plugins group */ if ((dir = clicon_restconf_dir(h)) != NULL) if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) @@ -635,12 +630,11 @@ main(int argc, /* Load main application yang specification either module or specific file * If -y is given, it overrides main module */ if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0) + if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) goto done; } else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_dir(h), - clicon_yang_module_revision(h), + clicon_yang_module_revision(h), yspec, NULL) < 0) goto done; @@ -649,10 +643,10 @@ main(int argc, goto done; /* Add system modules */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) goto done; if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) goto done; /* Call start function in all plugins before we go interactive Pass all args after the standard options to plugin_start diff --git a/configure b/configure index 9428e91f..7eba05ec 100755 --- a/configure +++ b/configure @@ -4391,15 +4391,14 @@ fi done -# This is to find clixon system files in the source code and Makefile +# CLIXON_DATADIR is where clixon installs the "system" yang files in yang/Makfile +# This directory should most probably be included in each application, +# so each application designer may need to place CLIXON_DATADIR in their config +# (last in yang dir list): +# $CLIXON_DATADIR CLIXON_DATADIR="${prefix}/share/clixon" -cat >>confdefs.h <<_ACEOF -#define CLIXON_DATADIR "${CLIXON_DATADIR}" -_ACEOF - - # Default location for config file cat >>confdefs.h <<_ACEOF diff --git a/configure.ac b/configure.ac index c46628fb..ebdccaf6 100644 --- a/configure.ac +++ b/configure.ac @@ -199,10 +199,13 @@ AC_CHECK_LIB(dl, dlopen) AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp) -# This is to find clixon system files in the source code and Makefile +# CLIXON_DATADIR is where clixon installs the "system" yang files in yang/Makfile +# This directory should most probably be included in each application, +# so each application designer may need to place CLIXON_DATADIR in their config +# (last in yang dir list): +# $CLIXON_DATADIR AC_SUBST(CLIXON_DATADIR) CLIXON_DATADIR="${prefix}/share/clixon" -AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${CLIXON_DATADIR}", [Clixon data dir for system yang files etc]) # Default location for config file AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${CLIXON_DEFAULT_CONFIG}",[Location for apps to find default config file]) diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 82c0263e..e5c5440c 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -71,7 +71,7 @@ sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src #include /* Command line options to be passed to getopt(3) */ -#define DATASTORE_OPTS "hDd:p:b:y:m:" +#define DATASTORE_OPTS "hDd:p:b:y:" /*! usage */ @@ -85,8 +85,7 @@ usage(char *argv0) "\t-d \t\tDatabase name. Default: running. Alt: candidate,startup\n" "\t-b \tDatabase directory. Mandatory\n" "\t-p \tDatastore plugin. Mandatory\n" - "\t-y \tYang directory (where modules are stored). Mandatory\n" - "\t-m \tYang module. Mandatory\n" + "\t-y \tYang file. Mandatory\n" "and command is either:\n" "\tget []\n" "\tmget []\n" @@ -115,7 +114,6 @@ main(int argc, char **argv) char *plugin = NULL; char *cmd = NULL; yang_spec *yspec = NULL; - char *yangdir = NULL; char *yangmodule = NULL; char *dbdir = NULL; int ret; @@ -158,12 +156,7 @@ main(int argc, char **argv) usage(argv0); dbdir = optarg; break; - case 'y': /* Yang directory */ - if (!optarg) - usage(argv0); - yangdir = optarg; - break; - case 'm': /* Yang module */ + case 'y': /* Yang file */ if (!optarg) usage(argv0); yangmodule = optarg; @@ -188,10 +181,6 @@ main(int argc, char **argv) clicon_err(OE_DB, 0, "Missing dbdir -b option"); goto done; } - if (yangdir == NULL){ - clicon_err(OE_YANG, 0, "Missing yangdir -y option"); - goto done; - } if (yangmodule == NULL){ clicon_err(OE_YANG, 0, "Missing yang module -m option"); goto done; @@ -206,7 +195,7 @@ main(int argc, char **argv) if ((yspec = yspec_new()) == NULL) goto done; /* Parse yang spec from given file */ - if (yang_parse(h, NULL, yangmodule, yangdir, NULL, yspec, NULL) < 0) + if (yang_parse(h, yangmodule, NULL, NULL, yspec, NULL) < 0) goto done; /* Set database directory option */ if (xmldb_setopt(h, "dbdir", dbdir) < 0) diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index bee21bd0..5d1edaf2 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -1367,8 +1367,7 @@ main(int argc, enum operation_type op; char *cmd; char *db; - char *yangdir; - char *yangmod; + char *yangmod; /* yang file */ yang_spec *yspec = NULL; clicon_handle h; @@ -1381,12 +1380,11 @@ main(int argc, } cmd = argv[1]; db = argv[2]; - yangdir = argv[3]; yangmod = argv[4]; db_init(db); if ((yspec = yspec_new()) == NULL) goto done - if (yang_parse(h, NULL, yangmod, yangdir, NULL, yspec) < 0) + if (yang_parse(h, NULL, yangmod, NULL, yspec) < 0) goto done; if (strcmp(cmd, "get")==0){ if (argc < 5) diff --git a/doc/FAQ.md b/doc/FAQ.md index 25727e98..a5539c5a 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -97,13 +97,31 @@ configuration file is /usr/local/etc/clixon.xml. The example configuration file is installed at /usr/local/etc/example.xml. The YANG specification for the configuration file is clixon-config.yang. -You can change where Clixon looks for the configuration FILE as follows: +## How are Clixon configuration files found? + +Clixon by default finds its configuration file at `/usr/local/etc/clixon.xml`. However, you can modify this location as follows: - Provide -f FILE option when starting a program (eg clixon_backend -f FILE) - Provide --with-configfile=FILE when configuring - - Provide --with-sysconfig= when configuring, then FILE is /clixon.xml - - Provide --sysconfig= when configuring then FILE is /etc/clixon.xml + - Provide --with-sysconfig= when configuring. Then FILE is /clixon.xml + - Provide --sysconfig= when configuring. Then FILE is /etc/clixon.xml - FILE is /usr/local/etc/clixon.xml +## How are Yang files found? + +Yang files contain the configuration specification. A Clixon +application loads yang files and clixon itself loads system yang +files. When Yang files are loaded modules are imported and submodules +are included. + +The following configuration file options control the loading of Yang files: +- `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules. +- `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path. +- `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module. + +Note that the special `CLIXON_DATADIR`, by default `/usr/local/share/clixon` should be included in the yang dir path for Clixon system files to be found. + +Application also has a command-line option `-y` to include a single Yang using absolute file path. This is mainly for debugging. + ## How do I enable Yang features? Yang models have features, and parts of a specification can be diff --git a/doc/Makefile.in b/doc/Makefile.in index 7b1ce519..ad0f4203 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -44,11 +44,13 @@ all: $(SUBDIRS) doc echo "Build doxygen doc: make doc" # Regular doxygen documentation +# Need to install doxygen doc: doxygen Doxyfile # generates html dir echo "Build doxygen graphs: make graphs" # doxygen documentation with callgraphs +# Need to install graphviz graphs: doxygen Doxyfile.graphs # generates html dir + call graphs (takes time) diff --git a/example/example.xml b/example/example.xml index c222f6ce..b6b42cdb 100644 --- a/example/example.xml +++ b/example/example.xml @@ -2,6 +2,7 @@ /usr/local/etc/example.xml *:* /usr/local/share/example/yang + /usr/local/share/clixon example example /usr/local/lib/example/backend diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 1cc881df..f8678e33 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -1,8 +1,5 @@ /* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ -/* Clixon data dir for system yang files etc */ -#undef CLIXON_DATADIR - /* Location for apps to find default config file */ #undef CLIXON_DEFAULT_CONFIG @@ -42,6 +39,9 @@ /* Define to 1 if you have the `crypt' library (-lcrypt). */ #undef HAVE_LIBCRYPT +/* Define to 1 if you have the `curl' library (-lcurl). */ +#undef HAVE_LIBCURL + /* Define to 1 if you have the `dl' library (-ldl). */ #undef HAVE_LIBDL @@ -133,4 +133,4 @@ `char[]'. */ #undef YYTEXT_POINTER -#include "clixon_custom.h" +#include diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index f06660f0..d774fe29 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -105,9 +105,6 @@ int clicon_option_del(clicon_handle h, const char *name); static inline char *clicon_configfile(clicon_handle h){ return clicon_option_str(h, "CLICON_CONFIGFILE"); } -static inline char *clicon_yang_dir(clicon_handle h){ - return clicon_option_str(h, "CLICON_YANG_DIR"); -} static inline char *clicon_yang_module_main(clicon_handle h){ return clicon_option_str(h, "CLICON_YANG_MODULE_MAIN"); } diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 5b3b01e2..222f3f91 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -95,6 +95,7 @@ enum rfc_6020{ Y_MANDATORY, Y_MAX_ELEMENTS, Y_MIN_ELEMENTS, + Y_MODIFIER, Y_MODULE, Y_MUST, Y_NAMESPACE, @@ -193,6 +194,8 @@ struct yang_stmt{ char *ys_argument; /* String / argument depending on keyword */ int ys_flags; /* Flags according to YANG_FLAG_* above */ + /*--------------here common for all -------*/ + char *ys_extra; /* For unknown */ cg_var *ys_cv; /* cligen variable. See ys_populate() Following stmts have cv:s: leaf: for default value @@ -200,7 +203,7 @@ struct yang_stmt{ config: boolean true or false mandatory: boolean true or false fraction-digits for fraction-digits - unkown-stmt (argument) + unknown-stmt (argument) */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max @@ -265,11 +268,12 @@ int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); int ys_populate(yang_stmt *ys, void *arg); yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp); int yang_parse(clicon_handle h, const char *filename, - const char *module, const char *dir, + const char *module, const char *revision, yang_spec *ysp, yang_stmt **ymodp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); -int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, +int yang_abs_schema_nodeid(yang_spec *yspec, yang_stmt *ys, + char *schema_nodeid, enum rfc_6020 keyword, yang_stmt **yres); int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, enum rfc_6020 keyword, yang_stmt **yres); @@ -277,8 +281,8 @@ cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); -int yang_spec_parse_module(clicon_handle h, char *module, char *dir, char *revision, yang_spec *yspec, yang_stmt **ymodp); -int yang_spec_parse_file(clicon_handle h, char *filename, char *dir, yang_spec *yspec, yang_stmt **ymodp); +int yang_spec_parse_module(clicon_handle h, char *module, char *revision, yang_spec *yspec, yang_stmt **ymodp); +int yang_spec_parse_file(clicon_handle h, char *filename, yang_spec *yspec, yang_stmt **ymodp); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); int yang_key_match(yang_node *yn, char *name); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index d832ffc9..c2a25f25 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -988,9 +988,9 @@ netconf_module_load(clicon_handle h) yspec = clicon_dbspec_yang(h); /* Load yang spec */ - if (yang_spec_parse_module(h, "ietf-netconf", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec, NULL)< 0) goto done; - if (yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) goto done; if ((xc = clicon_conf_xml(h)) == NULL){ clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded"); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 62202def..5804f182 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -178,8 +178,16 @@ parse_configfile(clicon_handle h, __FUNCTION__, name, body); continue; } + /* hard-coded exceptions for configure options that are leaf-lists (not leaf) + * They must be accessed directly by looping over clicon_conf_xml(h) + */ if (strcmp(name,"CLICON_FEATURE")==0) continue; + if (strcmp(name,"CLICON_YANG_DIR")==0) + continue; + /* Used as an arg to this fn */ + if (strcmp(name,"CLICON_CONFIGFILE")==0) + continue; if (hash_add(copt, name, body, @@ -236,14 +244,25 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } - /* Parse clixon yang spec */ - if (yang_parse(h, NULL, "clixon-config", CLIXON_DATADIR, NULL, yspec, NULL) < 0) - goto done; - /* Read configfile */ + /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; if (xml_rootchild(xconfig, 0, &xconfig) < 0) goto done; + /* Set clixon_conf pointer to handle */ + clicon_conf_xml_set(h, xconfig); + /* Parse clixon yang spec */ + if (yang_parse(h, NULL, "clixon-config", NULL, yspec, NULL) < 0) + goto done; + clicon_conf_xml_set(h, NULL); + if (xconfig) + xml_free(xconfig); + /* Read configfile second time now with check yang spec */ + if (parse_configfile(h, configfile, yspec, &xconfig) < 0) + goto done; + if (xml_rootchild(xconfig, 0, &xconfig) < 0) + goto done; + /* Set clixon_conf pointer to handle */ clicon_conf_xml_set(h, xconfig); /* Specific option handling */ if (clicon_option_bool(h, "CLICON_XML_SORT") == 1) @@ -638,7 +657,7 @@ clicon_conf_xml(clicon_handle h) return NULL; } -/*! Set YANG specification for Clixon system options and features + /*! Set YANG specification for Clixon system options and features * ys must be a malloced pointer */ int diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2afb0b6b..a7033ce2 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -122,6 +122,7 @@ static const map_str2int ykmap[] = { {"mandatory", Y_MANDATORY}, {"max-elements", Y_MAX_ELEMENTS}, {"min-elements", Y_MIN_ELEMENTS}, + {"modifier", Y_MODIFIER}, {"module", Y_MODULE}, {"must", Y_MUST}, {"namespace", Y_NAMESPACE}, @@ -206,6 +207,8 @@ ys_free1(yang_stmt *ys) { if (ys->ys_argument) free(ys->ys_argument); + if (ys->ys_extra) + free(ys->ys_extra); if (ys->ys_cv) cv_free(ys->ys_cv); if (ys->ys_cvec) @@ -233,7 +236,8 @@ ys_free(yang_stmt *ys) return 0; } -/*! Free a yang specification recursively */ +/*! Free a yang specification recursively + */ int yspec_free(yang_spec *yspec) { @@ -294,6 +298,11 @@ ys_cp(yang_stmt *ynew, clicon_err(OE_YANG, errno, "strdup"); goto done; } + if (yold->ys_extra) + if ((ynew->ys_extra = strdup(yold->ys_extra)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } if (yold->ys_cv) if ((ynew->ys_cv = cv_dup(yold->ys_cv)) == NULL){ clicon_err(OE_YANG, errno, "cv_dup"); @@ -672,7 +681,8 @@ yang_find_topnode(yang_spec *ysp, if (yang_nodeid_split(nodeid, &prefix, &id) < 0) goto done; if (prefix){ - if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL){ + if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL || + (ymod = yang_find((yang_node*)ysp, Y_SUBMODULE, prefix)) != NULL){ if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) goto ok; goto done; @@ -771,7 +781,7 @@ yang_order(yang_stmt *y) int j=0; yp = y->ys_parent; - if (yp->yn_keyword == Y_MODULE ||yp->yn_keyword == Y_SUBMODULE){ + if (yp->yn_keyword == Y_MODULE || yp->yn_keyword == Y_SUBMODULE){ ypp = yp->yn_parent; for (i=0; iyn_len; i++){ yn = (yang_node*)ypp->yn_stmt[i]; @@ -800,7 +810,8 @@ yang_key2str(int keyword) return (char*)clicon_int2str(ykmap, keyword); } -/*! Find top module or sub-module given a statement. Ultimate top is yang spec +/*! Find top module or sub-module given a statement. + * Ultimate top is yang spec, dont return that * The routine recursively finds ancestors. * @param[in] ys Any yang statement in a yang tree * @retval ymod The top module or sub-module @@ -811,14 +822,13 @@ ys_module(yang_stmt *ys) { yang_node *yn; -#if 1 if (ys==NULL || ys->ys_keyword==Y_SPEC) return NULL; -#else - if (ys==NULL || ys->ys_keyword==Y_SPEC) + if (ys->ys_keyword == Y_MODULE || ys->ys_keyword == Y_SUBMODULE) return ys; -#endif - while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){ + while (ys != NULL && + ys->ys_keyword != Y_MODULE && + ys->ys_keyword != Y_SUBMODULE){ yn = ys->ys_parent; /* Some extra stuff to ensure ys is a stmt */ if (yn && yn->yn_keyword == Y_SPEC) @@ -973,7 +983,8 @@ yang_find_module_by_prefix(yang_stmt *ys, } yimport = NULL; while ((yimport = yn_each((yang_node*)my_ymod, yimport)) != NULL) { - if (yimport->ys_keyword != Y_IMPORT) + if (yimport->ys_keyword != Y_IMPORT && + yimport->ys_keyword != Y_INCLUDE) continue; if ((yprefix = yang_find((yang_node*)yimport, Y_PREFIX, NULL)) != NULL && strcmp(yprefix->ys_argument, prefix) == 0){ @@ -981,9 +992,10 @@ yang_find_module_by_prefix(yang_stmt *ys, } } if (yimport){ - if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){ + if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL && + (ymod = yang_find((yang_node*)yspec, Y_SUBMODULE, yimport->ys_argument)) == NULL){ clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", - yimport->ys_argument); + prefix); yimport = NULL; goto done; /* unresolved */ } @@ -1411,9 +1423,9 @@ ys_populate_feature(clicon_handle h, cg_var *cv; char *module; char *feature; - cxobj *x1; + cxobj *xc; - /* Eg, when parsing the config xml itself */ + /* get clicon config file in xml form */ if ((x = clicon_conf_xml(h)) == NULL) goto ok; if ((ymod = ys_module(ys)) == NULL){ @@ -1422,14 +1434,14 @@ ys_populate_feature(clicon_handle h, } module = ymod->ys_argument; feature = ys->ys_argument; - x1 = NULL; - while ((x1 = xml_child_each(x, x1, CX_ELMNT)) != NULL && found == 0) { + xc = NULL; + while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL && found == 0) { char *m = NULL; char *f = NULL; - if (strcmp(xml_name(x1), "CLICON_FEATURE") != 0) + if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0) continue; /* get m and f from configuration feature rules */ - if (yang_nodeid_split(xml_body(x1), &m, &f) < 0) + if (yang_nodeid_split(xml_body(xc), &m, &f) < 0) goto done; if (m && f && (strcmp(m,"*")==0 || @@ -1454,6 +1466,51 @@ ys_populate_feature(clicon_handle h, return retval; } +/*! Populate unknown node with extension + */ +static int +ys_populate_unknown(yang_stmt *ys) +{ + int retval = -1; + int cvret; + char *reason = NULL; + yang_stmt *ymod; + char *prefix = NULL; + char *name; + char *extra; + + if ((extra = ys->ys_extra) == NULL) + goto ok; + /* Find extension, if found, store it as unknown, if not, + break for error */ + prefix = yarg_prefix(ys); /* And this its prefix */ + name = yarg_id(ys); /* This is the type to resolve */ + if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) + goto ok; /* shouldnt happen */ + if (yang_find((yang_node*)ymod, Y_EXTENSION, name) == NULL){ + clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, name); + goto done; + } + if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ + clicon_err(OE_YANG, errno, "cv_new"); + goto done; + } + if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ + clicon_err(OE_YANG, errno, "parsing cv"); + goto done; + } + if (cvret == 0){ /* parsing failed */ + clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); + goto done; + } + ok: + retval = 0; + done: + if (prefix) + free(prefix); + return retval; +} + /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * * We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree @@ -1495,6 +1552,10 @@ ys_populate(yang_stmt *ys, if (ys_populate_identity(ys, NULL) < 0) goto done; break; + case Y_UNKNOWN: + if (ys_populate_unknown(ys) < 0) + goto done; + break; default: break; } @@ -1555,12 +1616,11 @@ yang_augment_node(yang_stmt *ys, yang_stmt *yss = NULL; yang_stmt *yc; int i; - + schema_nodeid = ys->ys_argument; clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); - /* Find the target */ - if (yang_abs_schema_nodeid(ysp, schema_nodeid, -1, &yss) < 0) + if (yang_abs_schema_nodeid(ysp, ys, schema_nodeid, -1, &yss) < 0) goto done; if (yss == NULL) goto ok; @@ -1646,14 +1706,15 @@ yang_expand(yang_node *yn) prefix = yarg_prefix(ys); /* And this its prefix */ if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0) goto done; - if (prefix) - free(prefix); + if (ygrouping == NULL){ - clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found", - __FUNCTION__, ys->ys_argument); + clicon_log(LOG_NOTICE, "%s: Yang error : grouping \"%s\" not found in module \"%s\"", + __FUNCTION__, ys->ys_argument, ys_module(ys)->ys_argument); goto done; break; } + if (prefix) + free(prefix); /* XXX move up */ /* Check mark flag to see if this grouping (itself) has been expanded If not, this needs to be done before we can insert it into the 'uses' place */ @@ -1825,48 +1886,69 @@ yang_parse_file(int fd, return ymod; /* top-level (sub)module */ } -/*! No specific revision give. Match a yang file given dir and module +/*! No specific revision give. Match a yang file given module * @param[in] h CLICON handle - * @param[in] yang_dir Directory where all YANG module files reside + * @param[in] dir Directory, if NULL, look in YANG_DIR path * @param[in] module Name of main YANG module. + * @param[in] revision Revision or NULL * @param[out] fbuf Buffer containing filename - * + * @note for bootstrapping, dir may have to be set. * @retval 1 Match found, Most recent entry returned in fbuf * @retval 0 No matching entry found * @retval -1 Error */ static int -yang_parse_find_match(const char *yang_dir, - const char *module, +yang_parse_find_match(clicon_handle h, + const char *module, + const char *revision, cbuf *fbuf) { - int retval = -1; + int retval = -1; struct dirent *dp = NULL; int ndp; cbuf *regex = NULL; + cxobj *x; + cxobj *xc; + char *dir; + /* get clicon config file in xml form */ + if ((x = clicon_conf_xml(h)) == NULL) + goto ok; if ((regex = cbuf_new()) == NULL){ clicon_err(OE_YANG, errno, "cbuf_new"); goto done; } /* RFC 6020: The name of the file SHOULD be of the form: - module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) - revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT - */ - cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$", - module); - if ((ndp = clicon_file_dirent(yang_dir, - &dp, - cbuf_get(regex), - S_IFREG)) < 0) - goto done; - /* Entries are sorted, last entry should be most recent date */ - if (ndp != 0){ - cprintf(fbuf, "%s/%s", yang_dir, dp[ndp-1].d_name); - retval = 1; - } + * module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) + * revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT + */ + if (revision) + cprintf(regex, "^%s@%s(.yang)$", module, revision); else - retval = 0; + cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$", + module); + xc = NULL; + while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) { + if (strcmp(xml_name(xc), "CLICON_YANG_DIR") != 0) + continue; + dir = xml_body(xc); + /* get all matching files in this directory */ + if ((ndp = clicon_file_dirent(dir, + &dp, + cbuf_get(regex), + S_IFREG)) < 0) + goto done; + /* Entries are sorted, last entry should be most recent date + * Found + */ + if (ndp != 0){ + cprintf(fbuf, "%s/%s", dir, dp[ndp-1].d_name); + retval = 1; + goto done; + } + } + ok: + retval = 0; done: if (regex) cbuf_free(regex); @@ -1875,7 +1957,6 @@ yang_parse_find_match(const char *yang_dir, return retval; } - /*! Open a file, read into a string and invoke yang parsing * * Similar to clicon_yang_str(), just read a file first @@ -1903,7 +1984,6 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; - clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -1921,8 +2001,8 @@ yang_parse_filename(const char *filename, } static yang_stmt * -yang_parse_module(const char *module, - const char *dir, +yang_parse_module(clicon_handle h, + const char *module, const char *revision, yang_spec *ysp) { @@ -1934,16 +2014,12 @@ yang_parse_module(const char *module, clicon_err(OE_YANG, errno, "cbuf_new"); goto done; } - if (revision) - cprintf(fbuf, "%s/%s@%s.yang", dir, module, revision); - else{ - /* No specific revision, Match a yang file */ - if ((nr = yang_parse_find_match(dir, module, fbuf)) < 0) - goto done; - if (nr == 0){ - clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, dir); - goto done; - } + /* Match a yang file with or without revision in yang-dir list */ + if ((nr = yang_parse_find_match(h, module, revision, fbuf)) < 0) + goto done; + if (nr == 0){ + clicon_err(OE_YANG, errno, "No yang files found matching \"%s\" in the list of CLICON_YANG_DIRs", module); + goto done; } if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL) goto done; @@ -1953,10 +2029,9 @@ yang_parse_module(const char *module, return ymod; /* top-level (sub)module */ } -/*! Parse one yang module then go through (sub)modules and parse them recursively +/*! Given a (sub)module, parse all (sub)modules in turn recursively * * @param[in] h CLICON handle - * @param[in] yang_dir Directory where all YANG module files reside * @param[in] module Name of main YANG module. Or absolute file name. * @param[in] revision Module revision date or NULL * @param[in] ysp Yang specification. Should have been created by caller using yspec_new @@ -1972,8 +2047,8 @@ yang_parse_module(const char *module, * clixon_yang_parseparse # Actual yang parsing using yacc */ static int -yang_parse_recurse(yang_stmt *ymod, - const char *dir, +yang_parse_recurse(clicon_handle h, + yang_stmt *ymod, yang_spec *ysp) { int retval = -1; @@ -1982,21 +2057,29 @@ yang_parse_recurse(yang_stmt *ymod, char *submodule; char *subrevision; yang_stmt *subymod; + enum rfc_6020 keyw; - /* go through all import statements of ysp (or its module) */ + /* go through all import (modules) and include(submodules) of ysp */ while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){ - if (yi->ys_keyword != Y_IMPORT) + keyw = yi->ys_keyword; + if (keyw != Y_IMPORT && keyw != Y_INCLUDE) continue; + /* common part */ submodule = yi->ys_argument; + /* Is there a specific revision (or just latest)? */ if ((yrev = yang_find((yang_node*)yi, Y_REVISION_DATE, NULL)) != NULL) subrevision = yrev->ys_argument; else subrevision = NULL; - if (yang_find((yang_node*)ysp, Y_MODULE, submodule) == NULL){ + /* if already loaded, ignore, else parse the file */ + if (yang_find((yang_node*)ysp, + keyw=Y_IMPORT?Y_MODULE:Y_SUBMODULE, + submodule) == NULL){ /* recursive call */ - if ((subymod = yang_parse_module(submodule, dir, subrevision, ysp)) == NULL) + if ((subymod = yang_parse_module(h, submodule, subrevision, ysp)) == NULL) goto done; - if (yang_parse_recurse(subymod, dir, ysp) < 0){ + /* Go through its sub-modules recursively */ + if (yang_parse_recurse(h, subymod, ysp) < 0){ ymod = NULL; goto done; } @@ -2019,7 +2102,8 @@ ys_schemanode_check(yang_stmt *ys, yp = ys->ys_parent; switch (ys->ys_keyword){ case Y_AUGMENT: - if (yp->yn_keyword == Y_MODULE) /* Not top-level */ + if (yp->yn_keyword == Y_MODULE || /* Not top-level */ + yp->yn_keyword == Y_SUBMODULE) break; /* fallthru */ case Y_REFINE: @@ -2035,7 +2119,7 @@ ys_schemanode_check(yang_stmt *ys, break; case Y_DEVIATION: yspec = ys_spec(ys); - if (yang_abs_schema_nodeid(yspec, ys->ys_argument, -1, &yres) < 0) + if (yang_abs_schema_nodeid(yspec, ys, ys->ys_argument, -1, &yres) < 0) goto done; if (yres == NULL){ clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument); @@ -2127,10 +2211,69 @@ yang_features(clicon_handle h, return retval; } +/*! Merge yang submodule into the module it belongs to + * Skip submodule header fields + * @param[in] h Clicon handle + * @param[in] yspec Yang spec + * @param[in] ysubm Yang submodule + */ +static int +yang_merge_submodules(clicon_handle h, + yang_spec *yspec, + yang_stmt *ysubm) +{ + int retval = -1; + yang_stmt *yb; /* belongs-to */ + yang_stmt *ymod; /* parent yang module */ + yang_stmt *yc; /* yang child */ + char *modname; + int i; + + assert(ysubm->ys_keyword == Y_SUBMODULE); + /* Get parent name (via belongs-to) and find parent module */ + if ((yb = yang_find((yang_node*)ysubm, Y_BELONGS_TO, NULL)) == NULL){ + clicon_err(OE_YANG, ENOENT, "submodule %s does not have a mandatory belongs-to statement", ysubm->ys_argument); + goto done; + } + modname = yb->ys_argument; + if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, modname)) == NULL){ + clicon_err(OE_YANG, ENOENT, "Module %s which submodule %s belongs to is not found", modname, ysubm->ys_argument); + goto done; + } + /* Move sub-module statements to modules + * skip belongs-to, revision, organization, reference, yang-version) + * since main module has its own and may only have one + * XXX: use queue,... + */ + for (i=0; iys_len; i++){ + yc = ysubm->ys_stmt[i]; + if (yc->ys_keyword == Y_BELONGS_TO || + yc->ys_keyword == Y_CONTACT || + yc->ys_keyword == Y_DESCRIPTION || + yc->ys_keyword == Y_ORGANIZATION || + yc->ys_keyword == Y_REVISION || + yc->ys_keyword == Y_REFERENCE || + yc->ys_keyword == Y_YANG_VERSION) + ys_free(yc); + else{ + if (yn_insert((yang_node*)ymod, yc) < 0) + goto done; + } + } + if (ysubm->ys_stmt){ + free(ysubm->ys_stmt); + ysubm->ys_stmt = NULL; + } + ysubm->ys_len = 0; + ys_free(ysubm); + retval = 0; + done: + return retval; +} + /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * @param[in] h CLICON handle - * @param[in] yang_dir Directory where all YANG module files reside (except mainfile) * @param[in] filename File name containing Yang specification. Overrides module * @param[in] module Name of main YANG module. Or absolute file name. * @param[in] revision Main module revision date string or NULL @@ -2154,7 +2297,6 @@ int yang_parse(clicon_handle h, const char *filename, const char *module, - const char *dir, const char *revision, yang_spec *ysp, yang_stmt **ymodp) @@ -2164,63 +2306,89 @@ yang_parse(clicon_handle h, int i; int modnr; /* Existing number of modules */ + /* Apply steps 2.. on new modules, ie ones after modnr. */ modnr = ysp->yp_len; if (filename){ if ((ymod = yang_parse_filename(filename, ysp)) == NULL) goto done; } else - if ((ymod = yang_parse_module(module, dir, revision, ysp)) == NULL) + if ((ymod = yang_parse_module(h, module, revision, ysp)) == NULL) goto done; - /* From here on, apply actions on new modules, ie ones after modnr. */ - /* Step 1: parse from text to yang parse-tree. */ + /* 1: Parse from text to yang parse-tree. */ /* Iterate through modules */ - if (yang_parse_recurse(ymod, dir, ysp) < 0) + if (yang_parse_recurse(h, ymod, ysp) < 0) goto done; - /* Check cardinality maybe this should be done after grouping/augment */ + /* 2. Check cardinality maybe this should be done after grouping/augment */ for (i=modnr; iyp_len; i++) /* XXX */ if (yang_cardinality(h, ysp->yp_stmt[i], ysp->yp_stmt[i]->ys_argument) < 0) goto done; - /* Step 2: check features: check if enabled and remove disabled features */ + /* 3: Merge sub-modules with modules - after this step, no submodules exist + * In the merge, remove submodule headers + */ + for (i=modnr; iyp_len; i++){ + if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE) + continue; + } + i = 0; + while (iyp_len){ + int j; + if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE){ + i++; + continue; + } + if (yang_merge_submodules(h, ysp, ysp->yp_stmt[i]) < 0) + goto done; + /* shift down one step */ + for (j=i; jyp_len-1; j++) + ysp->yp_stmt[j] = ysp->yp_stmt[j+1]; + ysp->yp_len--; + } + + /* 4: Check features: check if enabled and remove disabled features */ for (i=modnr; iyp_len; i++) /* XXX */ if (yang_features(h, ysp->yp_stmt[i]) < 0) goto done; - /* Step 3: Go through parse tree and populate it with cv types */ + /* 5: Go through parse tree and populate it with cv types */ for (i=modnr; iyp_len; i++) if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0) goto done; - - /* Step 4: Resolve all types: populate type caches. Requires eg length/range cvecs - * from ys_populate step + /* 6: Resolve all types: populate type caches. Requires eg length/range cvecs + * from ys_populate step. + * Must be done using static binding. */ for (i=modnr; iyp_len; i++) - yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL); + if (yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0) + goto done; - /* Up to here resolving is made in the context they are defined, rather than the - context they are used. Like static scoping. After this we expand all - grouping/uses and unfold all macros into a single tree as they are used. - */ + /* Up to here resolving is made in the context they are defined, rather + * than the context they are used (except for submodules being merged w + * modules). Like static scoping. + * After this we expand all grouping/uses and unfold all macros into a + *single tree as they are used. + */ - /* Step 5: Macro expansion of all grouping/uses pairs. Expansion needs marking */ + /* 7: Macro expansion of all grouping/uses pairs. Expansion needs marking */ for (i=modnr; iyp_len; i++){ if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0) goto done; yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); } - /* Step 6: Top-level augmentation of all modules XXX: only new modules? */ + /* 8: Top-level augmentation of all modules XXX: only new modules? */ if (yang_augment_spec(ysp) < 0) goto done; - /* Step 7: sanity check of schemanode references, need more here */ + /* 9: sanity check of schemanode references, need more here */ for (i=modnr; iyp_len; i++) if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) goto done; + /* Return main module parsed in step 1 */ if (ymodp) *ymodp = ymod; retval = 0; @@ -2367,6 +2535,7 @@ schema_nodeid_vec(yang_node *yn, /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec * @param[in] yspec Yang specification. + * @param[in] yn Original yang stmt (where call is made) if any * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b * @param[in] keyword A schemode of this type, or -1 if any * @param[out] yres Result yang statement node, or NULL if not found @@ -2382,6 +2551,7 @@ schema_nodeid_vec(yang_node *yn, */ int yang_abs_schema_nodeid(yang_spec *yspec, + yang_stmt *yn, char *schema_nodeid, enum rfc_6020 keyword, yang_stmt **yres) @@ -2389,7 +2559,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, int retval = -1; char **vec = NULL; int nvec; - yang_stmt *ymod; + yang_stmt *ymod = NULL; char *id; char *prefix = NULL; yang_stmt *yprefix; @@ -2421,16 +2591,20 @@ yang_abs_schema_nodeid(yang_spec *yspec, } prefix[id-vec[1]] = '\0'; id++; - ymod = NULL; - while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { - if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) == 0){ - break; + if (yn) /* Find module using local prefix definition */ + ymod = yang_find_module_by_prefix(yn, prefix); + if (ymod == NULL){ /* Try (global) prefix the module itself uses */ + ymod = NULL; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && + strcmp(yprefix->ys_argument, prefix) == 0){ + break; + } } } - if (ymod == NULL){ /* Try with topnode */ + if (ymod == NULL){ /* Try find id from topnode without prefix XXX remove?*/ if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); + clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); goto done; } if ((ymod = ys_module(ys)) == NULL){ @@ -2439,7 +2613,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, } if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && strcmp(yprefix->ys_argument, prefix) != 0){ - clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); + clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); goto done; } } @@ -2546,12 +2720,7 @@ ys_parse_sub(yang_stmt *ys, char *extra) { int retval = -1; - int cvret; - char *reason = NULL; - yang_stmt *ymod; uint8_t fd; - char *prefix = NULL; - char *name; switch (ys->ys_keyword){ case Y_FRACTION_DIGITS: @@ -2563,40 +2732,17 @@ ys_parse_sub(yang_stmt *ys, goto done; } break; - case Y_UNKNOWN: + case Y_UNKNOWN: /* XXX This code assumes ymod already loaded + but it may not be */ if (extra == NULL) break; - /* Find extension, if found, store it as unknown, if not, - break for error */ - prefix = yarg_prefix(ys); /* And this its prefix */ - name = yarg_id(ys); /* This is the type to resolve */ - if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) - goto ok; /* shouldnt happen */ - if (yang_find((yang_node*)ymod, Y_EXTENSION, name) == NULL){ - clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, name); - goto done; - } - if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ - clicon_err(OE_YANG, errno, "cv_new"); - goto done; - } - if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ - clicon_err(OE_YANG, errno, "parsing cv"); - goto done; - } - if (cvret == 0){ /* parsing failed */ - clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); - goto done; - } + ys->ys_extra = extra; break; default: break; } - ok: retval = 0; done: - if (prefix) - free(prefix); return retval; } @@ -2654,7 +2800,6 @@ yang_config(yang_stmt *ys) int yang_spec_parse_module(clicon_handle h, char *module, - char *dir, char *revision, yang_spec *yspec, yang_stmt **ymodp) @@ -2674,11 +2819,7 @@ yang_spec_parse_module(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang module illegal format"); goto done; } - if (dir == NULL){ - clicon_err(OE_YANG, EINVAL, "yang dir not set"); - goto done; - } - if (yang_parse(h, NULL, module, dir, revision, yspec, ymodp) < 0) + if (yang_parse(h, NULL, module, revision, yspec, ymodp) < 0) goto done; retval = 0; done: @@ -2698,7 +2839,6 @@ yang_spec_parse_module(clicon_handle h, int yang_spec_parse_file(clicon_handle h, char *filename, - char *dir, yang_spec *yspec, yang_stmt **ymodp) { @@ -2708,11 +2848,7 @@ yang_spec_parse_file(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang spec is NULL"); goto done; } - if (dir == NULL){ - clicon_err(OE_YANG, EINVAL, "yang dir not set"); - goto done; - } - if (yang_parse(h, filename, NULL, dir, NULL, yspec, ymodp) < 0) + if (yang_parse(h, filename, NULL, NULL, yspec, ymodp) < 0) goto done; retval = 0; done: diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index ab6a43d6..60964c86 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -193,7 +193,8 @@ yang_cardinality(clicon_handle h, pk = yt->ys_keyword; /* 0) Find parent sub-parts of cardinality vector */ - ycplist = ycard_find(pk, 0, yclist, 0); + if ((ycplist = ycard_find(pk, 0, yclist, 0)) == NULL) + goto ok; /* skip */ /* 1) For all children, if neither in 0..n, 0..1, 1 or 1..n ->ERROR */ i = 0; while (iys_len){ @@ -231,14 +232,15 @@ yang_cardinality(clicon_handle h, } if (0) { /* Notyet */ - /* 4) Recurse */ - i = 0; - while (iys_len){ /* Note, children may be removed */ - ys = yt->ys_stmt[i++]; - if (yang_cardinality(h, ys, modname) < 0) - goto done; - } + /* 4) Recurse */ + i = 0; + while (iys_len){ /* Note, children may be removed */ + ys = yt->ys_stmt[i++]; + if (yang_cardinality(h, ys, modname) < 0) + goto done; + } } + ok: retval = 0; done: return retval; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index cf2f3208..539126df 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -77,7 +77,6 @@ * * Load RFC7895 yang spec, module-set-id, etc. * @param[in] h Clicon handle - * @note CLIXON_DATADIR is hardcoded */ int yang_modules_init(clicon_handle h) @@ -94,7 +93,7 @@ yang_modules_init(clicon_handle h) goto done; } /* Ensure revision exists is set */ - if (yang_spec_parse_module(h, "ietf-yang-library", CLIXON_DATADIR, NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec, NULL)< 0) goto done; /* Find revision */ if (yang_modules_revision(h) == NULL){ @@ -121,7 +120,8 @@ yang_modules_revision(clicon_handle h) char *revision = NULL; yspec = clicon_dbspec_yang(h); - if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, "ietf-yang-library")) != NULL){ + if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, "ietf-yang-library")) != NULL || + (ymod = yang_find((yang_node*)yspec, Y_SUBMODULE, "ietf-yang-library")) != NULL){ if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL){ revision = yrev->ys_argument; } @@ -172,7 +172,8 @@ yang_modules_state_get(clicon_handle h, char *module = "ietf-yang-library"; module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID"); - if ((ylib = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL){ + if ((ylib = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL && + (ylib = yang_find((yang_node*)yspec, Y_SUBMODULE, module)) == NULL){ clicon_err(OE_YANG, 0, "%s not found", module); goto done; } @@ -189,7 +190,8 @@ yang_modules_state_get(clicon_handle h, ymod = NULL; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { - if (ymod->ys_keyword != Y_MODULE) + if (ymod->ys_keyword != Y_MODULE && + ymod->ys_keyword != Y_SUBMODULE) continue; cprintf(cb,""); cprintf(cb,"%s", ymod->ys_argument); diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index f947c833..0af79687 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -42,9 +42,6 @@ * identifier_ref = prefix : IDENTIFIER * node_identier = prefix : IDENTIFIER * - * Missing top-level statements (will break parser if in yang spec): - * - error-app-tag-stmt - * - any-data-stmt * Missing args (just strings); * - length-arg-str * - path-arg-str @@ -124,6 +121,7 @@ %token K_MANDATORY %token K_MAX_ELEMENTS %token K_MIN_ELEMENTS +%token K_MODIFIER %token K_MODULE %token K_MUST %token K_NAMESPACE @@ -369,29 +367,28 @@ file : module_stmt MY_EOF /* For extensions */ unknown_stmt : ustring ':' ustring ';' - { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("0"); - if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("0"); + { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt"); + if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt"); clicon_debug(2,"unknown-stmt -> ustring : ustring"); } | ustring ':' ustring ' ' string ';' - { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("0"); - if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("0"); } + { char *id; if ((id=string_del_join($1, ":", $3)) == NULL) _YYERROR("unknwon_stmt"); + if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("unknwon_stmt"); } clicon_debug(2,"unknown-stmt -> ustring : ustring string"); - if ($5) free($5); } ; /* module identifier-arg-str */ module_stmt : K_MODULE identifier_str - { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); + { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("module_stmt"); } '{' module_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("2"); - clicon_debug(2,"module_stmt -> id-arg-str { module-substmts }");} + { if (ystack_pop(_yy) < 0) _YYERROR("module_stmt"); + clicon_debug(2,"module_stmt -> id-arg-str { module-substmts }");} ; module_substmts : module_substmts module_substmt - { clicon_debug(2,"module-substmts -> module-substmts module-substm");} + {clicon_debug(2,"module-substmts -> module-substmts module-substm");} | module_substmt { clicon_debug(2,"module-substmts ->");} ; @@ -406,10 +403,11 @@ module_substmt : module_header_stmts { clicon_debug(2,"module-substmt -> module- ; /* submodule */ -submodule_stmt : K_SUBMODULE identifier_str '{' submodule_substmts '}' - { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); - clicon_debug(2,"submodule -> id-arg-str { submodule-stmts }"); - } +submodule_stmt : K_SUBMODULE identifier_str + { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("submodule_stmt"); } + '{' submodule_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("submodule_stmt"); + clicon_debug(2,"submodule_stmt -> id-arg-str { submodule-substmts }");} ; submodule_substmts : submodule_substmts submodule_substmt @@ -464,16 +462,16 @@ submodule_header_stmt : yang_version_stmt ; /* yang-version-stmt = yang-version-keyword yang-version-arg-str */ -yang_version_stmt : K_YANG_VERSION string ';' - { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("83"); +yang_version_stmt : K_YANG_VERSION string stmtend + { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("yang_version_stmt"); clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } ; /* import */ import_stmt : K_IMPORT identifier_str - { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("81"); } + { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("import_stmt"); } '{' import_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("82"); + { if (ystack_pop(_yy) < 0) _YYERROR("import_stmt"); clicon_debug(2,"import-stmt -> IMPORT id-arg-str { import-substmts }");} ; @@ -484,69 +482,79 @@ import_substmts : import_substmts import_substmt ; import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } - | revision_date_stmt { clicon_debug(2,"import-stmt -> revision-date-stmt"); } + | revision_date_stmt { clicon_debug(2,"import-stmt -> revision-date-stmt"); } + | description_stmt { clicon_debug(2,"import-stmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"import-stmt -> reference-stmt"); } ; include_stmt : K_INCLUDE identifier_str ';' - { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("97"); - clicon_debug(2,"include-stmt -> id-arg-str"); } - | K_INCLUDE identifier_str '{' revision_date_stmt '}' - { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("98"); - clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("include_stmt"); + clicon_debug(2,"include-stmt -> id-str"); } + | K_INCLUDE identifier_str '{' include_substmts '}' + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("include_stmt"); + clicon_debug(2,"include-stmt -> id-str { include-substmts }"); } ; +include_substmts : include_substmts include_substmt + { clicon_debug(2,"include-substmts -> include-substmts include-substm");} + | include_substmt + { clicon_debug(2,"include-substmts ->");} + ; + +include_substmt : revision_date_stmt { clicon_debug(2,"include-stmt -> revision-date-stmt"); } + | description_stmt { clicon_debug(2,"include-stmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"include-stmt -> reference-stmt"); } + ; + + /* namespace-stmt = namespace-keyword sep uri-str */ namespace_stmt : K_NAMESPACE string stmtend - { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("99"); + { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("namespace_stmt"); clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } ; -prefix_stmt : K_PREFIX identifier_str ';' /* XXX prefix-arg-str */ - { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("100"); +prefix_stmt : K_PREFIX identifier_str stmtend /* XXX prefix-arg-str */ + { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("prefix_stmt"); clicon_debug(2,"prefix-stmt -> PREFIX string ;");} ; -belongs_to_stmt : K_BELONGS_TO identifier_str ';' - - { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("100"); - clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str ;");} - | K_BELONGS_TO identifier_str '{' prefix_stmt '}' - { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("98"); +belongs_to_stmt : K_BELONGS_TO identifier_str '{' prefix_stmt '}' + { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("belongs_to_stmt"); clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");} ; -organization_stmt: K_ORGANIZATION string ';' - { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("102"); +organization_stmt: K_ORGANIZATION string stmtend + { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("belongs_to_stmt"); clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} ; -contact_stmt : K_CONTACT string ';' - { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("95"); +contact_stmt : K_CONTACT string stmtend + { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("contact_stmt"); clicon_debug(2,"contact-stmt -> CONTACT string"); } ; -description_stmt: K_DESCRIPTION string ';' - { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101"); +description_stmt : K_DESCRIPTION string stmtend + { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("description_stmt"); clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} ; -reference_stmt: K_REFERENCE string ';' - { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("105"); +reference_stmt : K_REFERENCE string stmtend + { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("reference_stmt"); clicon_debug(2,"reference-stmt -> REFERENCE string ;");} ; units_stmt : K_UNITS string ';' - { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("93"); + { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("units_stmt"); clicon_debug(2,"units-stmt -> UNITS string"); } ; revision_stmt : K_REVISION string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION, $2, NULL) == NULL) _YYERROR("4"); + { if (ysp_add(_yy, Y_REVISION, $2, NULL) == NULL) _YYERROR("revision_stmt"); clicon_debug(2,"revision-stmt -> date-arg-str ;"); } | K_REVISION string - { if (ysp_add_push(_yy, Y_REVISION, $2) == NULL) _YYERROR("5"); } + { if (ysp_add_push(_yy, Y_REVISION, $2) == NULL) _YYERROR("revision_stmt"); } '{' revision_substmts '}' /* XXX date-arg-str */ - { if (ystack_pop(_yy) < 0) _YYERROR("6"); + { if (ystack_pop(_yy) < 0) _YYERROR("revision_stmt"); clicon_debug(2,"revision-stmt -> date-arg-str { revision-substmts }"); } ; @@ -570,19 +578,18 @@ revision_stmts : revision_stmts revision_stmt { clicon_debug(2,"revision-stmts -> "); } ; -revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("96"); +revision_date_stmt : K_REVISION_DATE string stmtend /* XXX date-arg-str */ + { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("revision_date_stmt"); clicon_debug(2,"revision-date-stmt -> date;"); } ; -/* Extension */ -extension_stmt: K_EXTENSION identifier_str ';' - { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("59"); +extension_stmt : K_EXTENSION identifier_str ';' + { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("extension_stmt"); clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } | K_EXTENSION identifier_str - { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } + { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("extension_stmt"); } '{' extension_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("61"); + { if (ystack_pop(_yy) < 0) _YYERROR("extension_stmt"); clicon_debug(2,"extension-stmt -> FEATURE id-arg-str { extension-substmts }"); } ; @@ -612,13 +619,13 @@ yin_element_stmt1 : K_YIN_ELEMENT bool_str stmtend {free($2);} /* Identity */ identity_stmt : K_IDENTITY identifier_str ';' - { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("65"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("identity_stmt"); clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } | K_IDENTITY identifier_str - { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("66"); } + { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("identity_stmt"); } '{' identity_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("67"); + { if (ystack_pop(_yy) < 0) _YYERROR("identity_stmt"); clicon_debug(2,"identity-stmt -> IDENTITY string { identity-substmts }"); } ; @@ -636,19 +643,19 @@ identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base- | { clicon_debug(2,"identity-substmt -> "); } ; -base_stmt : K_BASE identifier_ref_str ';' - { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("90"); +base_stmt : K_BASE identifier_ref_str stmtend + { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("base_stmt"); clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } ; /* Feature */ feature_stmt : K_FEATURE identifier_str ';' - { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("62"); + { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("feature_stmt"); clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } | K_FEATURE identifier_str - { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } + { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("feature_stmt"); } '{' feature_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("64"); + { if (ystack_pop(_yy) < 0) _YYERROR("feature_stmt"); clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); } ; @@ -669,15 +676,15 @@ feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-fea /* if-feature-stmt = if-feature-keyword sep if-feature-expr-str */ if_feature_stmt : K_IF_FEATURE string stmtend - { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("85"); + { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("if_feature_stmt"); clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } ; /* Typedef */ typedef_stmt : K_TYPEDEF identifier_str - { if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); } + { if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("typedef_stmt"); } '{' typedef_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("47"); + { if (ystack_pop(_yy) < 0) _YYERROR("typedef_stmt"); clicon_debug(2,"typedef-stmt -> TYPEDEF id-arg-str { typedef-substmts }"); } ; @@ -699,13 +706,13 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s /* Type */ type_stmt : K_TYPE identifier_ref_str ';' - { if (ysp_add(_yy, Y_TYPE, $2, NULL) == NULL) _YYERROR("48"); + { if (ysp_add(_yy, Y_TYPE, $2, NULL) == NULL) _YYERROR("type_stmt"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");} | K_TYPE identifier_ref_str - { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49"); + { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("type_stmt"); } '{' type_body_stmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("50"); + { if (ystack_pop(_yy) < 0) _YYERROR("type_stmt"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str { type-body-stmts }");} ; @@ -742,13 +749,13 @@ type_body_stmt/* numerical-restrictions */ /* range-stmt */ range_stmt : K_RANGE string ';' /* XXX range-arg-str */ - { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("68"); + { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("range_stmt"); clicon_debug(2,"range-stmt -> RANGE string ;"); } | K_RANGE string - { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("69"); } + { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("range_stmt"); } '{' range_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("70"); + { if (ystack_pop(_yy) < 0) _YYERROR("range_stmt"); clicon_debug(2,"range-stmt -> RANGE string { range-substmts }"); } ; @@ -767,7 +774,7 @@ range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-me /* fraction-digits-stmt = fraction-digits-keyword fraction-digits-arg-str */ fraction_digits_stmt : K_FRACTION_DIGITS string stmtend - { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("84"); + { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("fraction_digits_stmt"); clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } ; @@ -776,15 +783,22 @@ meta_stmts : meta_stmts meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmts | meta_stmt { clicon_debug(2,"meta-stmts -> meta-stmt"); } ; +meta_stmt : organization_stmt { clicon_debug(2,"meta-stmt -> organization-stmt"); } + | contact_stmt { clicon_debug(2,"meta-stmt -> contact-stmt"); } + | description_stmt { clicon_debug(2,"meta-stmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"meta-stmt -> reference-stmt"); } + ; + + /* length-stmt */ length_stmt : K_LENGTH string ';' /* XXX length-arg-str */ - { if (ysp_add(_yy, Y_LENGTH, $2, NULL) == NULL) _YYERROR("53"); + { if (ysp_add(_yy, Y_LENGTH, $2, NULL) == NULL) _YYERROR("length_stmt"); clicon_debug(2,"length-stmt -> LENGTH string ;"); } | K_LENGTH string - { if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("54"); } + { if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("length_stmt"); } '{' length_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("55"); + { if (ystack_pop(_yy) < 0) _YYERROR("length_stmt"); clicon_debug(2,"length-stmt -> LENGTH string { length-substmts }"); } ; @@ -803,13 +817,13 @@ length_substmt : error_message_stmt { clicon_debug(2,"length-substmt -> error-m /* Pattern */ pattern_stmt : K_PATTERN string ';' - { if (ysp_add(_yy, Y_PATTERN, $2, NULL) == NULL) _YYERROR("56"); + { if (ysp_add(_yy, Y_PATTERN, $2, NULL) == NULL) _YYERROR("pattern_stmt"); clicon_debug(2,"pattern-stmt -> PATTERN string ;"); } | K_PATTERN string - { if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("57"); } + { if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("pattern_stmt"); } '{' pattern_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("58"); + { if (ystack_pop(_yy) < 0) _YYERROR("pattern_stmt"); clicon_debug(2,"pattern-stmt -> PATTERN string { pattern-substmts }"); } ; @@ -819,27 +833,34 @@ pattern_substmts : pattern_substmts pattern_substmt { clicon_debug(2,"pattern-substmts -> pattern-substmt"); } ; -pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> reference-stmt"); } - | error_message_stmt { clicon_debug(2,"pattern-substmt -> error-message-stmt");} - | unknown_stmt { clicon_debug(2,"pattern-substmt -> unknown-stmt");} +pattern_substmt : modifier_stmt { clicon_debug(2,"pattern-substmt -> modifier-stmt");} + | error_message_stmt { clicon_debug(2,"pattern-substmt -> error-message-stmt");} + | error_app_tag_stmt { clicon_debug(2,"pattern-substmt -> error-app-tag-stmt");} + | description_stmt { clicon_debug(2,"pattern-substmt -> description-stmt");} + | reference_stmt { clicon_debug(2,"pattern-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"pattern-substmt -> unknown-stmt");} + | { clicon_debug(2,"pattern-substmt -> "); } ; -default_stmt : K_DEFAULT string ';' - { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("94"); +modifier_stmt : K_MODIFIER string stmtend + { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("modifier_stmt"); + clicon_debug(2,"modifier-stmt -> MODIFIER string"); } + ; + +default_stmt : K_DEFAULT string stmtend + { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("default_stmt"); clicon_debug(2,"default-stmt -> DEFAULT string"); } ; - - /* enum-stmt */ enum_stmt : K_ENUM string ';' - { if (ysp_add(_yy, Y_ENUM, $2, NULL) == NULL) _YYERROR("71"); + { if (ysp_add(_yy, Y_ENUM, $2, NULL) == NULL) _YYERROR("enum_stmt"); clicon_debug(2,"enum-stmt -> ENUM string ;"); } | K_ENUM string - { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("72"); } + { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("enum_stmt"); } '{' enum_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("73"); + { if (ystack_pop(_yy) < 0) _YYERROR("enum_stmt"); clicon_debug(2,"enum-stmt -> ENUM string { enum-substmts }"); } ; @@ -857,24 +878,24 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm | { clicon_debug(2,"enum-substmt -> "); } ; -path_stmt : K_PATH string ';' /* XXX: path-arg-str */ - { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("91"); +path_stmt : K_PATH string stmtend /* XXX: path-arg-str */ + { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("path_stmt"); clicon_debug(2,"path-stmt -> PATH string"); } ; -require_instance_stmt : K_REQUIRE_INSTANCE bool_str ';' - { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("92"); +require_instance_stmt : K_REQUIRE_INSTANCE bool_str stmtend + { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("require_instance_stmt"); clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } ; /* bit-stmt */ bit_stmt : K_BIT identifier_str ';' - { if (ysp_add(_yy, Y_BIT, $2, NULL) == NULL) _YYERROR("74"); + { if (ysp_add(_yy, Y_BIT, $2, NULL) == NULL) _YYERROR("bit_stmt"); clicon_debug(2,"bit-stmt -> BIT string ;"); } | K_BIT identifier_str - { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); } + { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("bit_stmt"); } '{' bit_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("76"); + { if (ystack_pop(_yy) < 0) _YYERROR("bit_stmt"); clicon_debug(2,"bit-stmt -> BIT string { bit-substmts }"); } ; @@ -893,50 +914,50 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition /* position-stmt = position-keyword position-value-arg-str */ position_stmt : K_POSITION integer_value_str stmtend - { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("87"); + { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("position_stmt"); clicon_debug(2,"position-stmt -> POSITION integer-value"); } ; /* status-stmt = status-keyword sep status-arg-str XXX: current-keyword*/ status_stmt : K_STATUS string stmtend - { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("88"); + { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("status_stmt"); clicon_debug(2,"status-stmt -> STATUS string"); } ; -config_stmt : K_CONFIG bool_str ';' - { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("89"); +config_stmt : K_CONFIG bool_str stmtend + { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("config_stmt"); clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } ; /* mandatory-stmt = mandatory-keyword mandatory-arg-str */ -mandatory_stmt: K_MANDATORY bool_str ';' +mandatory_stmt : K_MANDATORY bool_str stmtend { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("106"); + if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("mandatory_stmt"); clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} ; -presence_stmt: K_PRESENCE string ';' +presence_stmt : K_PRESENCE string stmtend { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("107"); + if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("presence_stmt"); clicon_debug(2,"presence-stmt -> PRESENCE string ;");} ; /* ordered-by-stmt = ordered-by-keyword sep ordered-by-arg-str */ -ordered_by_stmt: K_ORDERED_BY string stmtend +ordered_by_stmt : K_ORDERED_BY string stmtend { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("108"); + if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("ordered_by_stmt"); clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} ; /* must-stmt */ must_stmt : K_MUST string ';' - { if (ysp_add(_yy, Y_MUST, $2, NULL) == NULL) _YYERROR("77"); + { if (ysp_add(_yy, Y_MUST, $2, NULL) == NULL) _YYERROR("must_stmt"); clicon_debug(2,"must-stmt -> MUST string ;"); } | K_MUST string - { if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("78"); } + { if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("must_stmt"); } '{' must_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("79"); + { if (ystack_pop(_yy) < 0) _YYERROR("must_stmt"); clicon_debug(2,"must-stmt -> MUST string { must-substmts }"); } ; @@ -947,42 +968,47 @@ must_substmts : must_substmts must_substmt ; must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-message-stmt"); } + | error_app_tag_stmt { clicon_debug(2,"must-substmt -> error-app-tag-stmt"); } | description_stmt { clicon_debug(2,"must-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"must-substmt -> reference-stmt"); } | { clicon_debug(2,"must-substmt -> "); } ; /* error-message-stmt */ -error_message_stmt : K_ERROR_MESSAGE string ';' - { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("80"); } +error_message_stmt : K_ERROR_MESSAGE string stmtend + { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("error_message_stmt"); + clicon_debug(2,"error-message-stmt -> ERROR-MESSAGE string"); } ; -/* XXX error-app-tag-stmt */ +error_app_tag_stmt : K_ERROR_APP_TAG string stmtend + { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("error_message_stmt"); + clicon_debug(2,"error-app-tag-stmt -> ERROR-APP-TAG string"); } + ; /* min-elements-stmt = min-elements-keyword min-value-arg-str */ -min_elements_stmt: K_MIN_ELEMENTS integer_value_str stmtend - { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2, NULL)== NULL) _YYERROR("103"); +min_elements_stmt : K_MIN_ELEMENTS integer_value_str stmtend + { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2, NULL)== NULL) _YYERROR("min_elements_stmt"); clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");} ; /* max-elements-stmt = max-elements-keyword ("unbounded"|integer-value) * XXX cannot use integer-value */ -max_elements_stmt: K_MAX_ELEMENTS string ';' - { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2, NULL)== NULL) _YYERROR("104"); +max_elements_stmt : K_MAX_ELEMENTS string stmtend + { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2, NULL)== NULL) _YYERROR("max_elements_stmt"); clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");} ; -value_stmt : K_VALUE integer_value_str ';' - { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("86"); +value_stmt : K_VALUE integer_value_str stmtend + { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("value_stmt"); clicon_debug(2,"value-stmt -> VALUE integer-value"); } ; /* Grouping */ grouping_stmt : K_GROUPING identifier_str - { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("51"); } + { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("grouping_stmt"); } '{' grouping_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("52"); + { if (ystack_pop(_yy) < 0) _YYERROR("grouping_stmt"); clicon_debug(2,"grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } ; @@ -1006,12 +1032,12 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st /* container */ container_stmt : K_CONTAINER identifier_str ';' - { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("7"); + { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("container_stmt"); clicon_debug(2,"container-stmt -> CONTAINER id-arg-str ;");} | K_CONTAINER identifier_str - { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("8"); } + { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("container_stmt"); } '{' container_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("9"); + { if (ystack_pop(_yy) < 0) _YYERROR("container_stmt"); clicon_debug(2,"container-stmt -> CONTAINER id-arg-str { container-substmts }");} ; @@ -1036,14 +1062,13 @@ container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when- | { clicon_debug(2,"container-substmt ->");} ; -/* leaf */ leaf_stmt : K_LEAF identifier_str ';' - { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("10"); + { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("leaf_stmt"); clicon_debug(2,"leaf-stmt -> LEAF id-arg-str ;");} | K_LEAF identifier_str - { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("11"); } + { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("leaf_stmt"); } '{' leaf_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("12"); + { if (ystack_pop(_yy) < 0) _YYERROR("leaf_stmt"); clicon_debug(2,"leaf-stmt -> LEAF id-arg-str { lead-substmts }");} ; @@ -1068,12 +1093,12 @@ leaf_substmt : when_stmt { clicon_debug(2,"leaf-substmt -> when-stmt /* leaf-list */ leaf_list_stmt : K_LEAF_LIST identifier_str ';' - { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("13"); + { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("leaf_list_stmt"); clicon_debug(2,"leaf-list-stmt -> LEAF id-arg-str ;");} | K_LEAF_LIST identifier_str - { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("14"); } + { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("leaf_list_stmt"); } '{' leaf_list_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("15"); + { if (ystack_pop(_yy) < 0) _YYERROR("leaf_list_stmt"); clicon_debug(2,"leaf-list-stmt -> LEAF-LIST id-arg-str { lead-substmts }");} ; @@ -1097,14 +1122,13 @@ leaf_list_substmt : when_stmt { clicon_debug(2,"leaf-list-substmt -> when | { clicon_debug(2,"leaf-list-stmt ->"); } ; -/* list */ list_stmt : K_LIST identifier_str ';' - { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("16"); + { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("list_stmt"); clicon_debug(2,"list-stmt -> LIST id-arg-str ;"); } | K_LIST identifier_str - { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("17"); } + { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("list_stmt"); } '{' list_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("18"); + { if (ystack_pop(_yy) < 0) _YYERROR("list_stmt"); clicon_debug(2,"list-stmt -> LIST id-arg-str { list-substmts }"); } ; @@ -1137,24 +1161,24 @@ list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt /* key-stmt = key-keyword sep key-arg-str */ key_stmt : K_KEY string stmtend - { if (ysp_add(_yy, Y_KEY, $2, NULL)== NULL) _YYERROR("109"); + { if (ysp_add(_yy, Y_KEY, $2, NULL)== NULL) _YYERROR("key_stmt"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; /* unique-stmt = unique-keyword unique-arg-str */ unique_stmt : K_UNIQUE string stmtend - { if (ysp_add(_yy, Y_UNIQUE, $2, NULL)== NULL) _YYERROR("110"); + { if (ysp_add(_yy, Y_UNIQUE, $2, NULL)== NULL) _YYERROR("unique_stmt"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; /* choice */ choice_stmt : K_CHOICE identifier_str ';' - { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("19"); + { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("choice_stmt"); clicon_debug(2,"choice-stmt -> CHOICE id-arg-str ;"); } | K_CHOICE identifier_str - { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("20"); } + { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("choice_stmt"); } '{' choice_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("21"); + { if (ystack_pop(_yy) < 0) _YYERROR("choice_stmt"); clicon_debug(2,"choice-stmt -> CHOICE id-arg-str { choice-substmts }"); } ; @@ -1180,12 +1204,12 @@ choice_substmt : when_stmt { clicon_debug(2,"choice-substmt -> when-st /* case */ case_stmt : K_CASE identifier_str ';' - { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("22"); + { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("case_stmt"); clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } | K_CASE identifier_str - { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } + { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("case_stmt"); } '{' case_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("24"); + { if (ystack_pop(_yy) < 0) _YYERROR("case_stmt"); clicon_debug(2,"case-stmt -> CASE id-arg-str { case-substmts }"); } ; @@ -1206,23 +1230,23 @@ case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt ; anydata_stmt : K_ANYDATA identifier_str ';' - { if (ysp_add(_yy, Y_ANYDATA, $2, NULL) == NULL) _YYERROR("25"); + { if (ysp_add(_yy, Y_ANYDATA, $2, NULL) == NULL) _YYERROR("anydata_stmt"); clicon_debug(2,"anydata-stmt -> ANYDATA id-arg-str ;"); } | K_ANYDATA identifier_str - { if (ysp_add_push(_yy, Y_ANYDATA, $2) == NULL) _YYERROR("26"); } + { if (ysp_add_push(_yy, Y_ANYDATA, $2) == NULL) _YYERROR("anydata_stmt"); } '{' anyxml_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("27"); + { if (ystack_pop(_yy) < 0) _YYERROR("anydata_stmt"); clicon_debug(2,"anydata-stmt -> ANYDATA id-arg-str { anyxml-substmts }"); } ; /* anyxml */ anyxml_stmt : K_ANYXML identifier_str ';' - { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("25"); + { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("anyxml_stmt"); clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } | K_ANYXML identifier_str - { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } + { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("anyxml_stmt"); } '{' anyxml_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("27"); + { if (ystack_pop(_yy) < 0) _YYERROR("anyxml_stmt"); clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str { anyxml-substmts }"); } ; @@ -1245,12 +1269,12 @@ anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-st /* uses-stmt = uses-keyword identifier-ref-arg-str */ uses_stmt : K_USES identifier_ref_str ';' - { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("28"); + { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("uses_stmt"); clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } | K_USES identifier_ref_str - { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } + { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("uses_stmt"); } '{' uses_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("30"); + { if (ystack_pop(_yy) < 0) _YYERROR("uses_stmt"); clicon_debug(2,"uses-stmt -> USES id-arg-str { uses-substmts }"); } ; @@ -1273,12 +1297,12 @@ uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt /* refine-stmt = refine-keyword sep refine-arg-str */ refine_stmt : K_REFINE desc_schema_node_str ';' - { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("31"); + { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("refine_stmt"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } | K_REFINE desc_schema_node_str - { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } + { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("refine_stmt"); } '{' refine_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("33"); + { if (ystack_pop(_yy) < 0) _YYERROR("refine_stmt"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str { refine-substmts }"); } ; @@ -1297,9 +1321,9 @@ refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); /* uses-augment-stmt = augment-keyword augment-arg-str */ uses_augment_stmt : K_AUGMENT desc_schema_node_str - { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } + { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("uses_augment_stmt"); } '{' augment_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("35"); + { if (ystack_pop(_yy) < 0) _YYERROR("uses_augment_stmt"); clicon_debug(2,"uses-augment-stmt -> AUGMENT desc-schema-node-str { augment-substmts }"); } @@ -1307,9 +1331,9 @@ uses_augment_stmt : K_AUGMENT desc_schema_node_str * XXX abs-schema-nodeid-str is too difficult, it needs the + semantics */ augment_stmt : K_AUGMENT string - { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } + { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("augment_stmt"); } '{' augment_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("35"); + { if (ystack_pop(_yy) < 0) _YYERROR("augment_stmt"); clicon_debug(2,"augment-stmt -> AUGMENT abs-schema-node-str { augment-substmts }"); } ; @@ -1333,12 +1357,12 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s /* when */ when_stmt : K_WHEN string ';' - { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("36"); + { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("when_stmt"); clicon_debug(2,"when-stmt -> WHEN string ;"); } | K_WHEN string - { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } + { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("when_stmt"); } '{' when_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("38"); + { if (ystack_pop(_yy) < 0) _YYERROR("when_stmt"); clicon_debug(2,"when-stmt -> WHEN string { when-substmts }"); } ; @@ -1355,12 +1379,12 @@ when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-s /* rpc */ rpc_stmt : K_RPC identifier_str ';' - { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("39"); + { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("rpc_stmt"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } | K_RPC identifier_str - { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } + { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("rpc_stmt"); } '{' rpc_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("41"); + { if (ystack_pop(_yy) < 0) _YYERROR("rpc_stmt"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str { rpc-substmts }"); } ; @@ -1383,23 +1407,23 @@ rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stm /* action */ action_stmt : K_ACTION identifier_str ';' - { if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("39"); + { if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("action_stmt"); clicon_debug(2,"action-stmt -> ACTION id-arg-str ;"); } | K_ACTION identifier_str - { if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("40"); } + { if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("action_stmt"); } '{' rpc_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("41"); + { if (ystack_pop(_yy) < 0) _YYERROR("action_stmt"); clicon_debug(2,"action-stmt -> ACTION id-arg-str { rpc-substmts }"); } ; /* notification */ notification_stmt : K_NOTIFICATION identifier_str ';' - { if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("46"); + { if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("notification_stmt"); clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str ;"); } | K_NOTIFICATION identifier_str - { if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("47"); } + { if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("notification_stmt"); } '{' notification_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("48"); + { if (ystack_pop(_yy) < 0) _YYERROR("notification_stmt"); clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str { notification-substmts }"); } ; @@ -1427,9 +1451,9 @@ notification_substmt : if_feature_stmt { clicon_debug(2,"notification-substmt - */ deviation_stmt : K_DEVIATION string - { if (ysp_add_push(_yy, Y_DEVIATION, $2) == NULL) _YYERROR("47"); } + { if (ysp_add_push(_yy, Y_DEVIATION, $2) == NULL) _YYERROR("deviation_stmt"); } '{' deviation_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("48"); + { if (ystack_pop(_yy) < 0) _YYERROR("deviation_stmt"); clicon_debug(2,"deviation-stmt -> DEVIATION id-arg-str { notification-substmts }"); } ; @@ -1444,14 +1468,10 @@ deviation_substmt : description_stmt { clicon_debug(2,"deviation-substmt -> des | deviate_not_supported_stmt { clicon_debug(2,"deviation-substmt -> deviate-not-supported-stmt"); } ; -deviate_not_supported_stmt : K_DEVIATE string { clicon_debug(2,"deviate-not-supported-stmt -> DEVIATE string"); } +deviate_not_supported_stmt : K_DEVIATE string stmtend + { clicon_debug(2,"deviate-not-supported-stmt -> DEVIATE string"); } ; -meta_stmt : organization_stmt { clicon_debug(2,"meta-stmt -> organization-stmt"); } - | contact_stmt { clicon_debug(2,"meta-stmt -> contact-stmt"); } - | description_stmt { clicon_debug(2,"meta-stmt -> description-stmt"); } - | reference_stmt { clicon_debug(2,"meta-stmt -> reference-stmt"); } - ; /* body */ body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts body-stmt"); } @@ -1492,9 +1512,9 @@ short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> conta /* input */ input_stmt : K_INPUT - { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); } + { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("input_stmt"); } '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("43"); + { if (ystack_pop(_yy) < 0) _YYERROR("input_stmt"); clicon_debug(2,"input-stmt -> INPUT { input-substmts }"); } ; @@ -1512,15 +1532,12 @@ input_substmt : typedef_stmt { clicon_debug(2,"input-substmt -> typedef- /* output */ output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */ - { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("44"); } + { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("output_stmt"); } '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("45"); + { if (ystack_pop(_yy) < 0) _YYERROR("output_stmt"); clicon_debug(2,"output-stmt -> OUTPUT { input-substmts }"); } ; - - - string : qstrings { $$=$1; clicon_debug(2,"string -> qstrings (%s)", $1); } | ustring { $$=$1; clicon_debug(2,"string -> ustring (%s)", $1); } ; @@ -1554,12 +1571,11 @@ ustring : ustring CHAR {$$=$1; } ; - abs_schema_nodeid : abs_schema_nodeid '/' node_identifier - { if (($$=string_del_join($1, "/", $3)) == NULL) _YYERROR("0"); + { if (($$=string_del_join($1, "/", $3)) == NULL) _YYERROR("abs_schema_nodeid"); clicon_debug(2,"absolute-schema-nodeid -> absolute-schema-nodeid / node-identifier"); } | '/' node_identifier - { if (($$=string_del_join(NULL, "/", $2)) == NULL) _YYERROR("0"); + { if (($$=string_del_join(NULL, "/", $2)) == NULL) _YYERROR("abs_schema_nodeid"); clicon_debug(2,"absolute-schema-nodeid -> / node-identifier"); } ; @@ -1572,7 +1588,7 @@ desc_schema_node_str : desc_schema_nodeid desc_schema_nodeid : node_identifier { $$= $1; clicon_debug(2,"descendant-schema-nodeid -> node_identifier"); } | node_identifier abs_schema_nodeid - { if (($$=string_del_join($1, " ", $2)) == NULL) _YYERROR("0");clicon_debug(2,"descendant-schema-nodeid -> node_identifier abs_schema_nodeid"); } + { if (($$=string_del_join($1, " ", $2)) == NULL) _YYERROR("desc_schema_nodeid");clicon_debug(2,"descendant-schema-nodeid -> node_identifier abs_schema_nodeid"); } ; identifier_str : '"' IDENTIFIER '"' { $$ = $2; @@ -1602,7 +1618,7 @@ bool_str : '"' BOOL '"' { $$ = $2; node_identifier : IDENTIFIER { $$=$1; clicon_debug(2,"identifier-ref-arg-str -> string"); } | IDENTIFIER ':' IDENTIFIER - { if (($$=string_del_join($1, ":", $3)) == NULL) _YYERROR("112"); + { if (($$=string_del_join($1, ":", $3)) == NULL) _YYERROR("node_identifier"); clicon_debug(2,"identifier-ref-arg-str -> prefix : string"); } ; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 68204c9b..788af4f7 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -150,7 +150,7 @@ yang_type_cache_set(yang_type_cache **ycache0, return retval; } -/*! Get individual fields (direct/destrucively) from yang type cache. */ +/*! Get individual fields (direct/destructively) from yang type cache. */ int yang_type_cache_get(yang_type_cache *ycache, yang_stmt **resolved, @@ -227,19 +227,17 @@ ys_resolve_type(yang_stmt *ys, yang_stmt *resolved = NULL; assert(ys->ys_keyword == Y_TYPE); + /* Recursively resolve ys -> resolve with restrictions(options, etc) + * Note that the resolved type could be ys itself. + */ if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, &options, &mincv, &maxcv, &pattern, &fraction) < 0) goto done; - if (resolved && strcmp(resolved->ys_argument, "union")==0) - ; - /* skip unions since they may have different sets of options, mincv, etc - * You would have to resolve all sub-types also recursively - */ - else - if (yang_type_cache_set(&ys->ys_typecache, - resolved, options, mincv, maxcv, pattern, fraction) < 0) - goto done; + /* Cache the resolve locally */ + if (yang_type_cache_set(&ys->ys_typecache, + resolved, options, mincv, maxcv, pattern, fraction) < 0) + goto done; retval = 0; done: return retval; @@ -660,10 +658,12 @@ ys_cv_validate_union_one(yang_stmt *ys, clicon_err(OE_UNIX, errno, "cv_new"); goto done; } - if (cv_parse(val, cvt) <0){ + if ((retval = cv_parse1(val, cvt, reason)) < 0){ clicon_err(OE_UNIX, errno, "cv_parse"); goto done; } + if (retval == 0) + goto done; if ((retval = cv_validate1(cvt, cvtype, options, range_min, range_max, pattern, yrt, restype, reason)) < 0) goto done; @@ -688,16 +688,32 @@ ys_cv_validate_union(yang_stmt *ys, { int retval = 1; /* valid */ yang_stmt *yt = NULL; + char *reason1 = NULL; /* saved reason */ while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){ if (yt->ys_keyword != Y_TYPE) continue; if ((retval = ys_cv_validate_union_one(ys, reason, yt, type, val)) < 0) goto done; + /* If validation failed, save reason, reset error and continue, + * save latest reason if noithing validates. + */ + if (retval == 0 && reason && *reason != NULL){ + if (reason1) + free(reason1); + reason1 = *reason; + *reason = NULL; + } if (retval == 1) /* Enough that one type validates value */ break; } done: + if (retval == 0 && reason1){ + *reason = reason1; + reason1 = NULL; + } + if (reason1) + free(reason1); return retval; } @@ -908,7 +924,7 @@ resolve_restrictions(yang_stmt *yrange, } /*! Recursively resolve a yang type to built-in type with optional restrictions - * @param[in] ys yang-stmt from where the current search is based + * @param[in] ys (original) type yang-stmt where the current search is based * @param[in] ytype yang-stmt object containing currently resolving type * @param[out] yrestype resolved type. return built-in type or NULL. mandatory * @param[out] options pointer to flags field of optional values. optional @@ -945,14 +961,14 @@ yang_type_resolve(yang_stmt *ys, char *prefix = NULL; int retval = -1; yang_node *yn; - yang_stmt *ymod; + yang_stmt *yrmod; /* module where resolved type is looked for */ if (options) *options = 0x0; *yrestype = NULL; /* Initialization of resolved type that may not be necessary */ type = yarg_id(ytype); /* This is the type to resolve */ prefix = yarg_prefix(ytype); /* And this its prefix */ - /* Cache does not work for eg string length 32 */ + /* Cache does not work for eg string length 32? */ if (!yang_builtin(type) && ytype->ys_typecache != NULL){ if (yang_type_cache_get(ytype->ys_typecache, yrestype, options, mincv, maxcv, pattern, fraction) < 0) @@ -974,11 +990,12 @@ yang_type_resolve(yang_stmt *ys, /* Not basic type. Now check if prefix which means we look in other module */ if (prefix){ /* Go to top and find import that matches */ - if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL){ - clicon_err(OE_DB, 0, "Type not resolved: %s:%s", prefix, type); + if ((yrmod = yang_find_module_by_prefix(ytype, prefix)) == NULL){ + clicon_err(OE_DB, 0, "Type not resolved: \"%s:%s\" in module %s", + prefix, type, ys_module(ys)->ys_argument); goto done; } - if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL) + if ((rytypedef = yang_find((yang_node*)yrmod, Y_TYPEDEF, type)) == NULL) goto ok; /* unresolved */ } else diff --git a/test/lib.sh b/test/lib.sh index ab0a5ed9..716b6c83 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -78,14 +78,24 @@ expectfn(){ expect2= fi ret=$($cmd) + r=$? # echo "cmd:\"$cmd\"" # echo "retval:\"$retval\"" # echo "ret:\"$ret\"" - if [ $? -ne $retval ]; then - echo -e "\e[31m\nError in Test$testnr [$testname]:" +# echo "r:\"$r\"" + if [ $r != $retval ]; then + echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" echo -e "\e[0m:" exit -1 - fi + fi + if [ $r != 0 ]; then + return + fi +# if [ $ret -ne $retval ]; then +# echo -e "\e[31m\nError in Test$testnr [$testname]:" +# echo -e "\e[0m:" +# exit -1 +# fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return diff --git a/test/test_cli.sh b/test/test_cli.sh index 1304bf81..a93cf0fb 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -17,6 +17,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon $APPNAME /usr/local/lib/$APPNAME/backend /usr/local/lib/$APPNAME/clispec diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 27f3b4bf..122b3add 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -57,7 +57,7 @@ run(){ fi rm -rf $mydir/* - conf="-d candidate -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip" + conf="-d candidate -b $mydir -p ../datastore/$name/$name.so -y $dir/ietf-ip.yang" new "datastore $name init" expectfn "$datastore $conf init" 0 "" @@ -144,7 +144,7 @@ run(){ expectfn "$datastore $conf put create 13newentry" 0 "" new "datastore other db init" - expectfn "$datastore -d kalle -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip init" 0 "" + expectfn "$datastore -d kalle -b $mydir -p ../datastore/$name/$name.so -y $dir/ietf-ip.yang init" 0 "" new "datastore other db copy" expectfn "$datastore $conf copy kalle" 0 "" diff --git a/test/test_feature.sh b/test/test_feature.sh index e5279f6c..86f3ffd2 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -13,6 +13,7 @@ cat < $cfg ietf-routing:router-id $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon $APPNAME /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli diff --git a/test/test_identity.sh b/test/test_identity.sh index e006ca34..f9498013 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -11,6 +11,7 @@ cat < $cfg $cfg $dir + /usr/local/share/clixon /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend example_backend.so$ diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 6a146916..719a77e6 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -10,6 +10,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon example /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli diff --git a/test/test_list.sh b/test/test_list.sh index 67c3f953..ff12015a 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -12,6 +12,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon $APPNAME /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 586e5538..afac1df7 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -15,6 +15,7 @@ cat < $cfg $cfg /usr/local/share/clixon + /usr/local/share/clixon /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index ee5ae60b..997965ff 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -18,6 +18,7 @@ cat < $cfg $cfg /usr/local/share/example/yang + /usr/local/share/clixon /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend example_backend.so$ diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 2d3d2b0c..e66aae53 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -13,6 +13,7 @@ cat < $cfg $cfg 42 /usr/local/share/$APPNAME/yang + /usr/local/share/clixon /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend example_backend.so$ @@ -200,7 +201,6 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "eth/0/0true]]>]]>" - new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index 6ed5423b..efb68c54 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -1,24 +1,104 @@ #!/bin/bash # Parse yang openconfig tests +# Note that the openconfig test suites are patched to counter CLixon issues as follows: +# - release/models/mpls/openconfig-mpls-te.yang +# issue: https://github.com/clicon/clixon/issues/60 +# - release/models/wifi/types/openconfig-wifi-types.yang +# issue: https://github.com/clicon/clixon/issues/59 +# #PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" PROG=../util/clixon_util_yang -OPENCONFIG=~/syssrc/openconfig +OPENCONFIG=public +OCDIR=$OPENCONFIG/release/models + +# Clone openconfig dir if not there +if [ ! -d public ]; then + git clone https://github.com/openconfig/public +else + (cd public; git pull) +fi # include err() and new() functions and creates $dir . ./lib.sh -# Openconfig -# Files not parseable: -# - openconfig-access-points.yang -# - openconfig-access-points.yang -new "Openconfig" +# Yang specifics: multi-keys and empty type +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang + +cat < $cfg + + $cfg + $OCDIR + $OCDIR/acl + $OCDIR/aft + $OCDIR/bfd + $OCDIR/bgp + $OCDIR/catalog + $OCDIR/interfaces + $OCDIR/isis + $OCDIR/lacp + $OCDIR/lldp + $OCDIR/local-routing + $OCDIR/mpls + $OCDIR/multicast + $OCDIR/network-instance + $OCDIR/openflow + $OCDIR/optical-transport + $OCDIR/ospf + $OCDIR/platform + $OCDIR/policy + $OCDIR/policy-forwarding + $OCDIR/probes + $OCDIR/qos + $OCDIR/relay-agent + $OCDIR/rib + $OCDIR/segment-routing + $OCDIR/stp + $OCDIR/system + $OCDIR/telemetry + $OCDIR/types + $OCDIR/vlan + $OCDIR/wifi + $OCDIR/wifi/access-points + $OCDIR/wifi/ap-manager + $OCDIR/wifi/mac + $OCDIR/wifi/phy + $OCDIR/wifi/types + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + $APPNAME + /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 + /usr/local/lib/xmldb/text.so + true + +EOF + files=$(find $OPENCONFIG -name "*.yang") +# Just cound nr of modules (exclude submodule) +let m=0; # Nr of modules for f in $files; do - new "$f" - YANG=$(cat $f) - # NYI - expecteof "$PROG" 0 "$YANG" "module" + if [ -n "$(head -1 $f|grep '^module')" ]; then + let m++; + fi done +echo "Number of modules:$m" +for f in $files; do + if [ -n "$(head -1 $f|grep '^module')" ]; then + new "cli $f" + expectfn "$clixon_cli -1f $cfg -y $f show version" 0 "3." + fi + +done + rm -rf $dir - diff --git a/test/test_order.sh b/test/test_order.sh index da1ecb87..a44586cd 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -26,6 +26,7 @@ cat < $cfg /tmp/conf_yang.xml /usr/local/share/$APPNAME/yang + /usr/local/share/clixon example /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli diff --git a/test/test_perf.sh b/test/test_perf.sh index e6dde054..a2c36cf8 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -23,7 +23,7 @@ fyang=$dir/scaling.yang fconfig=$dir/config cat < $fyang -module ietf-ip{ +module scaling{ yang-version 1.1; namespace "urn:example:clixon"; prefix ip; @@ -47,8 +47,9 @@ EOF cat < $cfg $cfg - $fyang - ietf-ip + $dir + /usr/local/share/clixon + scaling /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile false diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 260d7eb9..f3b8fa72 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -11,6 +11,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon example /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 52364f2f..938aa232 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -12,6 +12,7 @@ cat < $cfg $cfg /usr/local/var + /usr/local/share/clixon false /usr/local/var/$APPNAME/$APPNAME.sock $dir/restconf.pidfile diff --git a/test/test_startup.sh b/test/test_startup.sh index ba4960f7..d9ec5b07 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -14,6 +14,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon example $APPNAME /usr/local/lib/$APPNAME/backend diff --git a/test/test_stream.sh b/test/test_stream.sh index 7e5d0e48..ce0aefc5 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -25,8 +25,7 @@ UTIL=../util/clixon_util_stream NCWAIT=5 # Wait (netconf valgrind may need more time) if [ ! -x $UTIL ]; then - echo "$UTIL not found. To build: (cd ../util; make clixon_util_stream)" - exit 1 + (cd ../util; make clixon_util_stream) fi DATE=$(date +"%Y-%m-%d") # include err() and new() functions and creates $dir diff --git a/test/test_type.sh b/test/test_type.sh index a2126e1c..d23d13cb 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -7,11 +7,15 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/type.yang +fyang2=$dir/example2.yang +fyang3=$dir/example3.yang cat < $cfg $cfg + $dir /usr/local/share/$APPNAME/yang + /usr/local/share/clixon example /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli @@ -21,14 +25,59 @@ cat < $cfg 1 /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so + false EOF +# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang +cat < $fyang3 +module example3{ + prefix ex3; + namespace "urn:example:example3"; + typedef w{ + type union{ + type int32{ + range "4..44"; + } + } + } + typedef u{ + type union { + type w; + type enumeration { + enum "bounded"; + enum "unbounded"; + } + } + } + typedef t{ + type string{ + pattern '[a-z][0-9]*'; + } + } +} +EOF +cat < $fyang2 +module example2{ + import example3 { prefix ex3; } + namespace "urn:example:example2"; + prefix ex2; + grouping gr2 { + leaf talle{ + type ex3:t; + } + leaf ulle{ + type ex3:u; + } + } +} +EOF cat < $fyang module example{ yang-version 1.1; namespace "urn:example:clixon"; prefix ex; + import example2 { prefix ex2; } typedef ab { type string { pattern @@ -136,6 +185,10 @@ module example{ leaf mbits{ type mybits; } + container c{ + description "transitive type- exists in ex3"; + uses ex2:gr2; + } } EOF @@ -151,6 +204,50 @@ if [ $? -ne 0 ]; then err fi +new "cli set transitive string" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle x99" 0 "^$" + +new "cli set transitive string error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle 9xx" 255 "^$" + +new "netconf set transitive string error" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" + +new "netconf validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of talle failed regexp match fail" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "cli set transitive union int" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 33" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o validate" 0 "^$" + +new "cli set transitive union string" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle unbounded" 0 "^$" + +new "cli validate" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang -l o validate" 0 "^$" + +new "cli set transitive union error. should fail" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle kalle" 255 "" + +new "cli set transitive union error int" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 55" 255 "" + +new "netconf set transitive union error int" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" + +new "netconf validate should fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of ulle failed" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +#----------- + new "cli set ab" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set list a.b.a.b" 0 "^$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index da629784..f83edd78 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -13,6 +13,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang + /usr/local/share/clixon $APPNAME /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli diff --git a/test/test_yang.sh b/test/test_yang.sh index a15bf6cb..d81277a4 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -6,12 +6,16 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/test.yang +fsubmod=$dir/example-types.yang fyangerr=$dir/err.yang +# /usr/local/share/$APPNAME/yang cat < $cfg $cfg - /usr/local/share/$APPNAME/yang + + $dir + /usr/local/share/clixon $APPNAME /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli @@ -30,6 +34,7 @@ module $APPNAME{ yang-version 1.1; prefix ex; namespace "urn:example:clixon"; + include example-types; extension c-define { description "Example from RFC 6020"; argument "name"; @@ -85,6 +90,41 @@ module $APPNAME{ type string; } } + list mylist{ /* uses submodule */ + key x; + leaf x{ + type string; + } + uses ex:subm-group; + } +} +EOF + +# Submodule Example from rfc7950 sec 7.2.3 +cat < $fsubmod +submodule example-types { + yang-version 1.1; + belongs-to $APPNAME { + prefix "sys"; + } + import ietf-yang-types { + prefix "yang"; + } + organization "Example Inc."; + contact "Joe L. User"; + description + "This submodule defines common Example types."; + revision "2007-06-09" { + description "Initial revision."; + } + grouping subm-group { + description "Defined in submodule"; + container subm-container{ + leaf subm-leaf{ + type string; + } + } + } } EOF @@ -128,7 +168,8 @@ new "netconf get config" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^a]]>]]>$" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + #new "cli not defined extension" #new "netconf not defined extension" @@ -182,7 +223,7 @@ new "netconf get presence only" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -191,7 +232,7 @@ new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf delete candidate" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" # Check 3-keys new "netconf add one 3-key entry" @@ -218,6 +259,25 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' +# clear db for next test +new "netconf delete candidate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf commit empty candidate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconfig config submodule" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afoo]]>]]>" "^]]>]]>$" + +new "netconf submodule get config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^afoo]]>]]>$" + +new "netconf submodule validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf submodule discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c index 9232b535..13b78743 100644 --- a/util/clixon_util_yang.c +++ b/util/clixon_util_yang.c @@ -31,6 +31,8 @@ ***** END LICENSE BLOCK ***** + * Parse a SINGLE yang file - no dependencies - utility function only useful + * for basic syntactic checks. */ #ifdef HAVE_CONFIG_H diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 839e16b0..a0376c21 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -133,11 +133,14 @@ module clixon-config { description "Location of configuration-file for default values (this file)"; } - leaf CLICON_YANG_DIR { + leaf-list CLICON_YANG_DIR { type string; - mandatory true; description - "Location of YANG module and submodule files."; + "Yang directory path for finding module and submodule files. + A list of these options should be in the configuration. + When loading a Yang module, Clixon searches this list in the order + they appear. Ensure that CLIXON_DATADIR(default + /usr/local/share/clixon) is present in the path"; } leaf CLICON_YANG_MODULE_MAIN { type string; From d09a8c08aa6cd80c5418348b0ffc254ee569224e Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 1 Dec 2018 18:34:52 +0100 Subject: [PATCH 07/72] getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58) --- CHANGELOG.md | 5 ++++- apps/backend/backend_main.c | 2 +- apps/cli/cli_main.c | 2 +- apps/netconf/netconf_main.c | 2 +- apps/restconf/restconf_main.c | 2 +- datastore/datastore_client.c | 2 +- example/example_restconf.c | 2 +- lib/src/json_xpath.c | 4 ++-- util/clixon_util_stream.c | 2 +- util/clixon_util_xml.c | 2 +- util/clixon_util_xpath.c | 2 +- util/clixon_util_yang.c | 4 ++-- 12 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed77983..a70162a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved unknown handling - * `CLICON_YANG_DIR` is changed from a single directory to a path of directories + * Configure option `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list ### API changes on existing features (you may need to change your code) @@ -26,7 +26,10 @@ * Syntactically Correct handling of ' Date: Mon, 3 Dec 2018 21:16:35 +0100 Subject: [PATCH 08/72] * Yang Configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * Change all @datamodel:tree to @datamodel in all CLI specification files * If you generate CLI code from the model (CLIXON_CLI_GENMODEL). * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h * Removed return value ymodp from yang parse functions (eg yang_parse()). * New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. --- CHANGELOG.md | 12 ++- README.md | 3 +- apps/backend/backend_main.c | 35 ++++--- apps/cli/cli_main.c | 54 ++++++----- apps/cli/cli_plugin.c | 29 +++++- apps/netconf/netconf_main.c | 27 ++++-- apps/restconf/restconf_main.c | 32 +++--- datastore/datastore_client.c | 2 +- doc/FAQ.md | 15 ++- example/example_cli.cli | 18 ++-- include/clixon_custom.h | 12 +++ lib/clixon/clixon_options.h | 9 ++ lib/clixon/clixon_yang.h | 9 +- lib/src/clixon_netconf_lib.c | 4 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_plugin.c | 3 +- lib/src/clixon_xml_parse.y | 2 +- lib/src/clixon_yang.c | 150 ++++++++++++++++++++--------- lib/src/clixon_yang_module.c | 2 +- test/test_yang.sh | 2 +- yang/clixon-config@2018-10-21.yang | 27 +++++- 21 files changed, 309 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70162a9..d71d1ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,23 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved unknown handling - * Configure option `CLICON_YANG_DIR` is changed from a single directory to a path of directories - * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list + * Yang Configure options changed + * `CLICON_YANG_DIR` is changed from a single directory to a path of directories + * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list + * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. + * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) * Yang parser is stricter (see above) which may break parsing of existing yang specs. * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. +* Change all @datamodel:tree to @datamodel in all CLI specification files + * If you generate CLI code from the model (CLIXON_CLI_GENMODEL). + * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Removed return value ymodp from yang parse functions (eg yang_parse()). +* New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. * XML parser conformance to W3 spec * Names lexically correct (NCName) * Syntactically Correct handling of ' cd doc diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index db66647e..1359e984 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -264,7 +264,7 @@ nacm_load_external(clicon_handle h) } if ((yspec = yspec_new()) == NULL) goto done; - if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec, NULL) < 0) + if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec) < 0) goto done; fd = fileno(f); /* Read configfile */ @@ -527,7 +527,7 @@ main(int argc, int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; + char *str; /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -652,8 +652,8 @@ main(int argc, case 'g': /* config socket group */ clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg); break; - case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + case 'y' :{ /* Load yang absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'x' :{ /* xmldb plugin */ @@ -750,17 +750,20 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL) + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL) + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL) + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; - } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; - /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; @@ -769,11 +772,11 @@ main(int argc, goto done; /* Load yang Restconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0) goto done; /* Load yang Netconf stream discovery */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; /* Set options: database dir and yangspec (could be hidden in connect?)*/ if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index feb5f158..b22e1c9f 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -250,15 +250,14 @@ main(int argc, char **argv) int logclisyntax = 0; int help = 0; char *treename = NULL; - int len; + // int len; int logdst = CLICON_LOG_STDERR; char *restarg = NULL; /* what remains after options */ int dump_configfile_xml = 0; yang_spec *yspec; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ struct passwd *pw; - char *yang_filename = NULL; - yang_stmt *ymod = NULL; /* Main module */ + char *str; /* Defaults */ once = 0; @@ -390,8 +389,8 @@ main(int argc, char **argv) case 'L' : /* Debug print dynamic CLI syntax */ logclisyntax++; break; - case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + case 'y' :{ /* Load yang absolute filename */ + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'c' :{ /* Overwrite clispec with absolute filename */ @@ -430,16 +429,24 @@ main(int argc, char **argv) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, &ymod) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, &ymod) < 0) - goto done; + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) + goto done; + } + /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; @@ -448,25 +455,22 @@ main(int argc, char **argv) /* Create tree generated from dataspec. If no other trees exists, this is * the only one. + * The following code creates the tree @datamodel + * This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) + * using the "tree reference" + * syntax, ie @datamodel + * But note that yang2cli generates syntax for ALL modules, not just for + * . */ if (clicon_cli_genmodel(h)){ parse_tree pt = {0,}; /* cli parse tree */ - char *name; /* main module name */ + char *treeref; + treeref = clicon_cli_model_treename(h); /* Create cli command tree from dbspec */ if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0) goto done; - - /* name of main module */ - name = ymod->ys_argument; - - len = strlen("datamodel:") + strlen(name) + 1; - if ((treename = malloc(len)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - snprintf(treename, len, "datamodel:%s", name); - cligen_tree_add(cli_cligen(h), treename, pt); + cligen_tree_add(cli_cligen(h), treeref, pt); if (printgen) cligen_print(stdout, pt, 1); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 0fb58736..633c46ae 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -209,6 +209,30 @@ clixon_str2fn(char *name, return NULL; } +#ifdef CLICON_CLI_MODEL_TREENAME_PATCH +/*! Patch all CLI spec calls to @datamodel:tree to @datamodel. + * This is a backward compatible fix for 3.9 for CLIgen specification files + * using model generation (CLIXON_CLI_GENMODEL). + * All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME). + * whereas older code used @datamodel:tree. + */ +static int +mask_datamodel_fn(cg_obj *co, + void *arg) +{ + char *str = "datamodel:"; + int len = strlen(str); + if (co->co_type == CO_REFERENCE){ + + if (strlen(co->co_command) > len && + strncmp(co->co_command, "datamodel:", len)==0){ + co->co_command[len-1] = '\0'; + } + } + return 0; +} +#endif /* CLICON_CLI_MODEL_TREENAME_PATCH */ + /*! Append to syntax mode from file * @param[in] h Clixon handle * @param[in] filename Name of file where syntax is specified (in syntax-group dir) @@ -253,7 +277,10 @@ cli_load_syntax(clicon_handle h, goto done; } fclose(f); - +#ifdef CLICON_CLI_MODEL_TREENAME_PATCH + if (pt_apply(pt, mask_datamodel_fn, h) < 0) + goto done; +#endif /* Get CLICON specific global variables */ prompt = cvec_find_str(cvv, "CLICON_PROMPT"); plgnam = cvec_find_str(cvv, "CLICON_PLUGIN"); diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index aee1cf80..cc51d966 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -341,7 +341,7 @@ main(int argc, struct timeval tv = {0,}; /* timeout */ yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; + char *str; /* Create handle */ if ((h = clicon_handle_init()) == NULL) @@ -420,7 +420,7 @@ main(int argc, clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg); break; case 'y' :{ /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; } case 'U': /* Clixon 'pseudo' user */ @@ -444,16 +444,23 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + } + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 76fe7aab..d5067c4f 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -522,9 +522,9 @@ main(int argc, int logdst = CLICON_LOG_SYSLOG; yang_spec *yspec = NULL; yang_spec *yspecfg = NULL; /* For config XXX clixon bug */ - char *yang_filename = NULL; char *stream_path; int finish; + char *str; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -601,7 +601,7 @@ main(int argc, clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg); break; case 'y' : /* Load yang spec file (override yang main module) */ - yang_filename = optarg; + clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; case 'a': /* internal backend socket address family */ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg); @@ -627,26 +627,34 @@ main(int argc, if ((yspec = yspec_new()) == NULL) goto done; clicon_dbspec_yang_set(h, yspec); - /* Load main application yang specification either module or specific file - * If -y is given, it overrides main module */ - if (yang_filename){ - if (yang_spec_parse_file(h, yang_filename, yspec, NULL) < 0) + + /* Load Yang modules + * 1. Load a yang module as a specific absolute filename */ + if ((str = clicon_yang_main_file(h)) != NULL){ + if (yang_spec_parse_file(h, str, yspec) < 0) + goto done; + } + /* 2. Load a (single) main module */ + if ((str = clicon_yang_module_main(h)) != NULL){ + if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h), + yspec) < 0) + goto done; + } + /* 3. Load all modules in a directory */ + if ((str = clicon_yang_main_dir(h)) != NULL){ + if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - else if (yang_spec_parse_module(h, clicon_yang_module_main(h), - clicon_yang_module_revision(h), - yspec, NULL) < 0) - goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; /* Add system modules */ if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0) goto done; if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; /* Call start function in all plugins before we go interactive Pass all args after the standard options to plugin_start diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 165b4238..5722d497 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -195,7 +195,7 @@ main(int argc, char **argv) if ((yspec = yspec_new()) == NULL) goto done; /* Parse yang spec from given file */ - if (yang_parse(h, yangmodule, NULL, NULL, yspec, NULL) < 0) + if (yang_parse(h, yangmodule, NULL, NULL, yspec) < 0) goto done; /* Set database directory option */ if (xmldb_setopt(h, "dbdir", dbdir) < 0) diff --git a/doc/FAQ.md b/doc/FAQ.md index a5539c5a..9e54e304 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -73,8 +73,14 @@ clicon:x:1001:,www-data ``` ## What about reference documentation? -Clixon uses Doxygen for reference documentation. -Build using 'make doc' and aim your browser at doc/html/index.html. +Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. +You need to install doxygen and graphviz on your system. +Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows: +``` +> cd doc +> make doc +> make graphs # detailed callgraphs +``` ## How is configuration data stored? Configuration data is stored in an XML datastore. In the example the @@ -115,12 +121,15 @@ are included. The following configuration file options control the loading of Yang files: - `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules. +- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory. +- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file. - `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path. - `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module. Note that the special `CLIXON_DATADIR`, by default `/usr/local/share/clixon` should be included in the yang dir path for Clixon system files to be found. -Application also has a command-line option `-y` to include a single Yang using absolute file path. This is mainly for debugging. +You can combine the options, however, more specific options override +less specific. For example, `CLICON_YANG_MAIN_FILE` overrides `CLICON_YANG_MODULE_MAIN`. ## How do I enable Yang features? diff --git a/example/example_cli.cli b/example/example_cli.cli index 1856c8c7..302a6cf3 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -7,10 +7,10 @@ CLICON_PLUGIN="example_cli"; translate value (),cli_set("/translate/value"); # Note, when switching to PT, change datamodel to only @datamodel -set @datamodel:example, cli_set(); -merge @datamodel:example, cli_merge(); -create @datamodel:example, cli_create(); -delete("Delete a configuration item") @datamodel:example, cli_del(); +set @datamodel, cli_set(); +merge @datamodel, cli_merge(); +create @datamodel, cli_create(); +delete("Delete a configuration item") @datamodel, cli_del(); validate("Validate changes"), cli_validate(); commit("Commit the changes"), cli_commit(); @@ -40,19 +40,19 @@ show("Show a particular state of the system"){ } configuration("Show configuration"), cli_show_config("candidate", "text", "/");{ xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{ - @datamodel:example, cli_show_auto("candidate", "text"); + @datamodel, cli_show_auto("candidate", "text"); } cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{ - @datamodel:example, cli_show_auto("candidate", "cli"); + @datamodel, cli_show_auto("candidate", "cli"); } netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{ - @datamodel:example, cli_show_auto("candidate", "netconf"); + @datamodel, cli_show_auto("candidate", "netconf"); } text("Show configuration as text"), cli_show_config("candidate","text","/");{ - @datamodel:example, cli_show_auto("candidate", "text"); + @datamodel, cli_show_auto("candidate", "text"); } json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{ - @datamodel:example, cli_show_auto("candidate", "json"); + @datamodel, cli_show_auto("candidate", "json"); } } } diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 8bad0f92..cbb3f1df 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -51,3 +51,15 @@ int strverscmp (__const char *__s1, __const char *__s2); */ #define XMLNS_YANG_ONLY 1 +/* Set for full XML namespace code in XML, NETCONF and YANG + * Experimental + */ +#undef ENABLE_XMLNS + +/* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. + * This is a backward compatible fix for 3.9 for CLIgen specification files + * using model generation (CLIXON_CLI_GENMODEL). + * All new references should use @datamodel (or CLICON_CLI_MODEL_TREENAME). + * whereas older code used @datamodel:tree. + */ +#define CLICON_CLI_MODEL_TREENAME_PATCH diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index d774fe29..1f4069fc 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -105,6 +105,12 @@ int clicon_option_del(clicon_handle h, const char *name); static inline char *clicon_configfile(clicon_handle h){ return clicon_option_str(h, "CLICON_CONFIGFILE"); } +static inline char *clicon_yang_main_file(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MAIN_FILE"); +} +static inline char *clicon_yang_main_dir(clicon_handle h){ + return clicon_option_str(h, "CLICON_YANG_MAIN_DIR"); +} static inline char *clicon_yang_module_main(clicon_handle h){ return clicon_option_str(h, "CLICON_YANG_MODULE_MAIN"); } @@ -129,6 +135,9 @@ static inline char *clicon_clispec_dir(clicon_handle h){ static inline char *clicon_cli_mode(clicon_handle h){ return clicon_option_str(h, "CLICON_CLI_MODE"); } +static inline char *clicon_cli_model_treename(clicon_handle h){ + return clicon_option_str(h, "CLICON_CLI_MODEL_TREENAME"); +} static inline char *clicon_sock(clicon_handle h){ return clicon_option_str(h, "CLICON_SOCK"); } diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 222f3f91..a49d1e94 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -256,7 +256,7 @@ int yang_nodeid_split(char *nodeid, char **prefix, char **id); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); -yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); +yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); @@ -269,7 +269,7 @@ int ys_populate(yang_stmt *ys, void *arg); yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp); int yang_parse(clicon_handle h, const char *filename, const char *module, - const char *revision, yang_spec *ysp, yang_stmt **ymodp); + const char *revision, yang_spec *ysp); int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); int yang_abs_schema_nodeid(yang_spec *yspec, yang_stmt *ys, @@ -281,8 +281,9 @@ cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); -int yang_spec_parse_module(clicon_handle h, char *module, char *revision, yang_spec *yspec, yang_stmt **ymodp); -int yang_spec_parse_file(clicon_handle h, char *filename, yang_spec *yspec, yang_stmt **ymodp); +int yang_spec_parse_module(clicon_handle h, char *module, char *revision, yang_spec *yspec); +int yang_spec_parse_file(clicon_handle h, char *filename, yang_spec *yspec); +int yang_spec_load_dir(clicon_handle h, char *dir, yang_spec *yspec); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); int yang_key_match(yang_node *yn, char *name); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index c2a25f25..27c0acc0 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -988,9 +988,9 @@ netconf_module_load(clicon_handle h) yspec = clicon_dbspec_yang(h); /* Load yang spec */ - if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) goto done; - if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec, NULL)< 0) + if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) goto done; if ((xc = clicon_conf_xml(h)) == NULL){ clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded"); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 5804f182..9c737b18 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -252,7 +252,7 @@ clicon_options_main(clicon_handle h, /* Set clixon_conf pointer to handle */ clicon_conf_xml_set(h, xconfig); /* Parse clixon yang spec */ - if (yang_parse(h, NULL, "clixon-config", NULL, yspec, NULL) < 0) + if (yang_parse(h, NULL, "clixon-config", NULL, yspec) < 0) goto done; clicon_conf_xml_set(h, NULL); if (xconfig) diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 7710ae80..03ed2b29 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -269,8 +269,7 @@ clixon_plugins_load(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, - regexp?regexp:"(.so)$", S_IFREG))<0) + if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0) goto done; /* Load all plugins */ for (i = 0; i < ndp; i++) { diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index a86c7276..7072ed01 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -315,7 +315,7 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *xa; -#ifdef notyet +#ifdef ENABLE_XMLNS if (prefix && strcmp(prefix,"xmlns")==0) fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); if (prefix==NULL && strcmp(name,"xmlns")==0) diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index a7033ce2..2a6b1572 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -46,7 +46,7 @@ #include #include #include -#define __USE_GNU /* strverscmp */ +#define __USE_GNU /* strverscmp */ #include #include #include @@ -56,6 +56,7 @@ #include #include #include +#include #include /* cligen */ @@ -416,7 +417,7 @@ yn_each(yang_node *yn, yang_stmt * yang_find(yang_node *yn, int keyword, - char *argument) + const char *argument) { yang_stmt *ys = NULL; int i; @@ -1939,7 +1940,6 @@ yang_parse_find_match(clicon_handle h, S_IFREG)) < 0) goto done; /* Entries are sorted, last entry should be most recent date - * Found */ if (ndp != 0){ cprintf(fbuf, "%s/%s", dir, dp[ndp-1].d_name); @@ -2278,7 +2278,6 @@ yang_merge_submodules(clicon_handle h, * @param[in] module Name of main YANG module. Or absolute file name. * @param[in] revision Main module revision date string or NULL * @param[in,out] ysp Yang specification. Should have been created by caller using yspec_new - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 Everything OK * @retval -1 Error encountered * The database symbols are inserted in alphabetical order. @@ -2298,72 +2297,89 @@ yang_parse(clicon_handle h, const char *filename, const char *module, const char *revision, - yang_spec *ysp, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; yang_stmt *ymod = NULL; /* Top-level yang (sub)module */ int i; int modnr; /* Existing number of modules */ + char *base = NULL;; /* Apply steps 2.. on new modules, ie ones after modnr. */ - modnr = ysp->yp_len; + modnr = yspec->yp_len; if (filename){ - if ((ymod = yang_parse_filename(filename, ysp)) == NULL) + /* Find module, and do not load file if module already exists */ + if (basename(filename) == NULL){ + clicon_err(OE_YANG, errno, "No basename"); + goto done; + } + if ((base = strdup(basename(filename))) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + if (index(base, '@') != NULL) + *index(base, '@') = '\0'; + if (yang_find((yang_node*)yspec, Y_MODULE, base) != NULL) + goto ok; + if ((ymod = yang_parse_filename(filename, yspec)) == NULL) goto done; } - else - if ((ymod = yang_parse_module(h, module, revision, ysp)) == NULL) + else { + /* Do not load module if it already exists */ + if (yang_find((yang_node*)yspec, Y_MODULE, module) != NULL) + goto ok; + if ((ymod = yang_parse_module(h, module, revision, yspec)) == NULL) goto done; + } /* 1: Parse from text to yang parse-tree. */ /* Iterate through modules */ - if (yang_parse_recurse(h, ymod, ysp) < 0) + if (yang_parse_recurse(h, ymod, yspec) < 0) goto done; /* 2. Check cardinality maybe this should be done after grouping/augment */ - for (i=modnr; iyp_len; i++) /* XXX */ - if (yang_cardinality(h, ysp->yp_stmt[i], ysp->yp_stmt[i]->ys_argument) < 0) + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_cardinality(h, yspec->yp_stmt[i], yspec->yp_stmt[i]->ys_argument) < 0) goto done; /* 3: Merge sub-modules with modules - after this step, no submodules exist * In the merge, remove submodule headers */ - for (i=modnr; iyp_len; i++){ - if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE) + for (i=modnr; iyp_len; i++){ + if (yspec->yp_stmt[i]->ys_keyword != Y_SUBMODULE) continue; } i = 0; - while (iyp_len){ + while (iyp_len){ int j; - if (ysp->yp_stmt[i]->ys_keyword != Y_SUBMODULE){ + if (yspec->yp_stmt[i]->ys_keyword != Y_SUBMODULE){ i++; continue; } - if (yang_merge_submodules(h, ysp, ysp->yp_stmt[i]) < 0) + if (yang_merge_submodules(h, yspec, yspec->yp_stmt[i]) < 0) goto done; /* shift down one step */ - for (j=i; jyp_len-1; j++) - ysp->yp_stmt[j] = ysp->yp_stmt[j+1]; - ysp->yp_len--; + for (j=i; jyp_len-1; j++) + yspec->yp_stmt[j] = yspec->yp_stmt[j+1]; + yspec->yp_len--; } /* 4: Check features: check if enabled and remove disabled features */ - for (i=modnr; iyp_len; i++) /* XXX */ - if (yang_features(h, ysp->yp_stmt[i]) < 0) + for (i=modnr; iyp_len; i++) /* XXX */ + if (yang_features(h, yspec->yp_stmt[i]) < 0) goto done; /* 5: Go through parse tree and populate it with cv types */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_populate, (void*)h) < 0) goto done; /* 6: Resolve all types: populate type caches. Requires eg length/range cvecs * from ys_populate step. * Must be done using static binding. */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL) < 0) goto done; /* Up to here resolving is made in the context they are defined, rather @@ -2374,25 +2390,26 @@ yang_parse(clicon_handle h, */ /* 7: Macro expansion of all grouping/uses pairs. Expansion needs marking */ - for (i=modnr; iyp_len; i++){ - if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0) + for (i=modnr; iyp_len; i++){ + if (yang_expand((yang_node*)yspec->yp_stmt[i]) < 0) goto done; - yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); + yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK); } /* 8: Top-level augmentation of all modules XXX: only new modules? */ - if (yang_augment_spec(ysp) < 0) + if (yang_augment_spec(yspec) < 0) goto done; /* 9: sanity check of schemanode references, need more here */ - for (i=modnr; iyp_len; i++) - if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) + for (i=modnr; iyp_len; i++) + if (yang_apply((yang_node*)yspec->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0) goto done; /* Return main module parsed in step 1 */ - if (ymodp) - *ymodp = ymod; + ok: retval = 0; - done: + done: + if (base) + free(base); return retval; } @@ -2792,7 +2809,6 @@ yang_config(yang_stmt *ys) * @param[in] dir Directory where to look for modules and sub-modules * @param[in] revision Revision, or NULL * @param[in,out] yspec Modules parse are added to this yangspec - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 OK * @retval -1 Error * @see yang_spec_parse_file @@ -2801,8 +2817,7 @@ int yang_spec_parse_module(clicon_handle h, char *module, char *revision, - yang_spec *yspec, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; @@ -2819,7 +2834,7 @@ yang_spec_parse_module(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang module illegal format"); goto done; } - if (yang_parse(h, NULL, module, revision, yspec, ymodp) < 0) + if (yang_parse(h, NULL, module, revision, yspec) < 0) goto done; retval = 0; done: @@ -2831,7 +2846,6 @@ yang_spec_parse_module(clicon_handle h, * @param[in] filename Actual filename (including dir and revision) * @param[in] dir Directory for sub-modules * @param[in,out] yspec Modules parse are added to this yangspec - * @param[out] ymodp Yang module of first, topmost Yang module, if given. * @retval 0 OK * @retval -1 Error * @see yang_spec_parse_module for yang dir,module,revision instead of actual filename @@ -2839,8 +2853,7 @@ yang_spec_parse_module(clicon_handle h, int yang_spec_parse_file(clicon_handle h, char *filename, - yang_spec *yspec, - yang_stmt **ymodp) + yang_spec *yspec) { int retval = -1; @@ -2848,13 +2861,62 @@ yang_spec_parse_file(clicon_handle h, clicon_err(OE_YANG, EINVAL, "yang spec is NULL"); goto done; } - if (yang_parse(h, filename, NULL, NULL, yspec, ymodp) < 0) + if (yang_parse(h, filename, NULL, NULL, yspec) < 0) goto done; retval = 0; done: return retval; } +/*! Load all yang modules in directory + * @param[in] h Clicon handle + * @param[in] dir Load all yang modules in this directory + * @param[in,out] yspec Modules parse are added to this yangspec + * @retval 0 OK + * @retval -1 Error + */ +int +yang_spec_load_dir(clicon_handle h, + char *dir, + yang_spec *yspec) +{ + int retval = -1; + int ndp; + struct dirent *dp = NULL; + int i; + char filename[MAXPATHLEN]; + char *base; + char *b; + int j; + int len; + + /* Get plugin objects names from plugin directory */ + if((ndp = clicon_file_dirent(dir, &dp, "(.yang)$", S_IFREG)) < 0) + goto done; + /* Load all yang files */ + for (i = 0; i < ndp; i++) { + base = dp[i].d_name; + /* Entries are sorted, see if later entry exists (include @), if so skip + * this one and take last. + */ + if ((b = index(base, '@')) != NULL) + len = b-base; + else + len = strlen(base); + for (j = (i+1); j < ndp; j++) + if (strncmp(base, dp[j].d_name, len) == 0) + break; + if (j[@]"; @@ -224,9 +234,18 @@ module clixon-config { type int32; default 1; description - "Generate code for CLI completion of existing db symbols. - Example: Add name=\"myspec\" in datamodel spec and reference - as @myspec"; + "If set, generate CLI specification for CLI completion of + loaded Yang modules. This CLI tree can be accessed in CLI + spec files using the tree reference syntax (eg @datamodel). + See also CLICON_CLI_MODEL_TREENAME."; + } + leaf CLICON_CLI_MODEL_TREENAME { + type string; + default "datamodel"; + description + "If CLICON_CLI_GENMODEL is set, CLI specs can reference the + model syntax using this reference. + Example: set @datamodel, cli_set();"; } leaf CLICON_CLI_GENMODEL_COMPLETION { type int32; From 9377986c817a41557b323db68b7c7f1e1562d0b4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 4 Dec 2018 20:04:16 +0100 Subject: [PATCH 09/72] testing of yang load ocnfig options --- CHANGELOG.md | 2 +- doc/FAQ.md | 12 +- test/test_yang_load.sh | 427 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+), 5 deletions(-) create mode 100755 test/test_yang_load.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index d71d1ec7..3305eb87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved unknown handling - * Yang Configure options changed + * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. diff --git a/doc/FAQ.md b/doc/FAQ.md index 9e54e304..9ecf46ff 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -121,15 +121,19 @@ are included. The following configuration file options control the loading of Yang files: - `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules. -- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory. -- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file. +- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file. - `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path. - `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module. +- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory. Note that the special `CLIXON_DATADIR`, by default `/usr/local/share/clixon` should be included in the yang dir path for Clixon system files to be found. -You can combine the options, however, more specific options override -less specific. For example, `CLICON_YANG_MAIN_FILE` overrides `CLICON_YANG_MODULE_MAIN`. +You can combine the options, however, if there are different variants +of the same module, more specific options override less +specific. The precedence of the options are as follows: +- `CLICON_YANG_MAIN_FILE` +- `CLICON_YANG_MODULE_MAIN` +- `CLICON_YANG_MAIN_DIR` ## How do I enable Yang features? diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh new file mode 100755 index 00000000..ae4f56e3 --- /dev/null +++ b/test/test_yang_load.sh @@ -0,0 +1,427 @@ +#!/bin/bash +# Load yang files. Test the different options +# CLICON_YANG_MODULE_DIR vs CLICON_YANG_MAIN_FILE vs CLICON_YANG_MAIN_DIR +# as well as revisions +# Test is made by having different config files and then try to set configure +# options available in specific modules +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang1=$dir/$APPNAME@2018-12-02.yang +fyang2=$dir/$APPNAME@2018-01-01.yang +fyang3=$dir/other.yang + +# /usr/local/share/$APPNAME/yang + +# 1st variant of the example module +cat < $fyang1 +module $APPNAME{ + prefix ex; + revision 2018-12-02; + namespace "urn:example:example"; + leaf newex{ + type string; + } +} +EOF + +# 2nd variant of the same example module +cat < $fyang2 +module $APPNAME{ + prefix ex; + revision 2018-01-01; + namespace "urn:example:example"; + leaf oldex{ + type string; + } +} +EOF + +# Other module +cat < $fyang3 +module other{ + prefix oth; + revision 2018-01-01; + namespace "urn:example:example2"; + leaf other{ + type string; + } +} +EOF + +#--------------------------------- +new "1. Load module as file" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $fyang1 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + 1 + +EOF +# kill old backend (if any) +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 +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "1. Set newex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set oldex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "2. Load old module as file" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $fyang2 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + 1 + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set oldex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set newex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "3. Load module with no revision" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + example + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set newex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set oldex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "4. Load module with old revision" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + example + 2018-01-01 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set oldex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set newex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "5. Load dir" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set newex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set oldex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "6. Load dir override with file" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $dir + $fyang2 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set oldex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set newex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + + +#-------------------------------------- +new "7. Load dir override with module + revision" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $dir + example + 2018-01-01 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set oldex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set newex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +#-------------------------------------- +new "8. Load module w new revision overrided by old file" +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + $fyang2 + example + 2018-12-02 + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +new "start backend -s init -f $cfg" +# start new backend +sudo $clixon_backend -s init -f $cfg +if [ $? -ne 0 ]; then + err +fi + +new "Set oldex" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' + +new "Set newex should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +new "Set other should fail" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +rm -rf $dir From 7d5bfe5c81f78e52eb06caa267ab8160a5e872d1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 5 Dec 2018 22:18:28 +0100 Subject: [PATCH 10/72] YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) --- CHANGELOG.md | 2 +- example/README.md | 3 +- lib/src/clixon_yang_cardinality.c | 316 +++++++++++++++++++++++++++++- lib/src/clixon_yang_parse.y | 22 ++- test/test_openconfig.sh | 10 +- 5 files changed, 335 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3305eb87..e2ab6c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ### Major New features * More complete Yang parser - * YANG parser cardinality checked (only modules level yet) + * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public diff --git a/example/README.md b/example/README.md index 6fdc8641..48ed4595 100644 --- a/example/README.md +++ b/example/README.md @@ -1,7 +1,6 @@ # Clixon example -This directory contains a Clixon example which includes a simple -routing example. It contains the following files: +This directory contains a Clixon example which includes a simple example. It contains the following files: * example.xml The configuration file. See yang/clixon-config@.yang for all available fields. * example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. * example_cli.cli CLIgen specification. diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 60964c86..4a8b1ddf 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -108,6 +108,217 @@ struct ycard{ */ #define NMAX 1000000 /* Just a large number */ static const struct ycard yclist[] = { + {Y_ACTION, Y_DESCRIPTION, 0, 1}, + {Y_ACTION, Y_GROUPING, 0, NMAX}, + {Y_ACTION, Y_IF_FEATURE, 0, NMAX}, + {Y_ACTION, Y_INPUT, 0, 1}, + {Y_ACTION, Y_OUTPUT, 0, 1}, + {Y_ACTION, Y_REFERENCE, 0, 1}, + {Y_ACTION, Y_STATUS, 0, 1}, + {Y_ACTION, Y_TYPEDEF, 0, NMAX}, + {Y_ANYDATA, Y_CONFIG, 0, 1}, + {Y_ANYDATA, Y_DESCRIPTION, 0, 1}, + {Y_ANYDATA, Y_IF_FEATURE, 0, NMAX}, + {Y_ANYDATA, Y_MANDATORY, 0, 1}, + {Y_ANYDATA, Y_MUST, 0, NMAX}, + {Y_ANYDATA, Y_REFERENCE, 0, 1}, + {Y_ANYDATA, Y_STATUS, 0, 1}, + {Y_ANYDATA, Y_WHEN, 0, 1}, + {Y_ANYXML, Y_CONFIG, 0, 1}, + {Y_ANYXML, Y_DESCRIPTION, 0, 1}, + {Y_ANYXML, Y_IF_FEATURE, 0, NMAX}, + {Y_ANYXML, Y_MANDATORY, 0, 1}, + {Y_ANYXML, Y_MUST, 0, NMAX}, + {Y_ANYXML, Y_REFERENCE, 0, 1}, + {Y_ANYXML, Y_STATUS, 0, 1}, + {Y_ANYXML, Y_WHEN, 0, 1}, + {Y_ARGUMENT, Y_YIN_ELEMENT, 0, 1}, + {Y_AUGMENT, Y_ACTION, 0, NMAX}, + {Y_AUGMENT, Y_ANYDATA, 0, NMAX}, + {Y_AUGMENT, Y_ANYXML, 0, NMAX}, + {Y_AUGMENT, Y_CASE, 0, NMAX}, + {Y_AUGMENT, Y_CHOICE, 0, NMAX}, + {Y_AUGMENT, Y_CONTAINER, 0, NMAX}, + {Y_AUGMENT, Y_DESCRIPTION, 0, 1}, + {Y_AUGMENT, Y_IF_FEATURE, 0, NMAX}, + {Y_AUGMENT, Y_LEAF, 0, NMAX}, + {Y_AUGMENT, Y_LEAF_LIST, 0, NMAX}, + {Y_AUGMENT, Y_LIST, 0, NMAX}, + {Y_AUGMENT, Y_NOTIFICATION, 0, NMAX}, + {Y_AUGMENT, Y_REFERENCE, 0, 1}, + {Y_AUGMENT, Y_STATUS, 0, 1}, + {Y_AUGMENT, Y_USES, 0, NMAX}, + {Y_AUGMENT, Y_WHEN, 0, 1}, + {Y_BELONGS_TO, Y_PREFIX, 1, 1}, + {Y_BIT, Y_DESCRIPTION, 0, 1}, + {Y_BIT, Y_IF_FEATURE, 0, NMAX}, + {Y_BIT, Y_POSITION, 0, 1}, + {Y_BIT, Y_REFERENCE, 0, 1}, + {Y_BIT, Y_STATUS, 0, 1}, + {Y_CASE, Y_ANYDATA, 0, NMAX}, + {Y_CASE, Y_ANYXML, 0, NMAX}, + {Y_CASE, Y_CHOICE, 0, NMAX}, + {Y_CASE, Y_CONTAINER, 0, NMAX}, + {Y_CASE, Y_DESCRIPTION, 0, 1}, + {Y_CASE, Y_IF_FEATURE, 0, NMAX}, + {Y_CASE, Y_LEAF, 0, NMAX}, + {Y_CASE, Y_LEAF_LIST, 0, NMAX}, + {Y_CASE, Y_LIST, 0, NMAX}, + {Y_CASE, Y_REFERENCE, 0, 1}, + {Y_CASE, Y_STATUS, 0, 1}, + {Y_CASE, Y_USES, 0, NMAX}, + {Y_CASE, Y_WHEN, 0, 1}, + {Y_CHOICE, Y_ANYXML, 0, NMAX}, + {Y_CHOICE, Y_CASE, 0, NMAX}, + {Y_CHOICE, Y_CHOICE, 0, NMAX}, + {Y_CHOICE, Y_CONFIG, 0, 1}, + {Y_CHOICE, Y_CONTAINER, 0, NMAX}, + {Y_CHOICE, Y_DEFAULT, 0, 1}, + {Y_CHOICE, Y_DESCRIPTION, 0, 1}, + {Y_CHOICE, Y_IF_FEATURE, 0, NMAX}, + {Y_CHOICE, Y_LEAF, 0, NMAX}, + {Y_CHOICE, Y_LEAF_LIST, 0, NMAX}, + {Y_CHOICE, Y_LIST, 0, NMAX}, + {Y_CHOICE, Y_MANDATORY, 0, 1}, + {Y_CHOICE, Y_REFERENCE, 0, 1}, + {Y_CHOICE, Y_STATUS, 0, 1}, + {Y_CHOICE, Y_WHEN, 0, 1}, + {Y_CHOICE, Y_ANYDATA, 0, NMAX}, + {Y_CONTAINER, Y_ACTION, 0, NMAX}, + {Y_CONTAINER, Y_ANYDATA, 0, NMAX}, + {Y_CONTAINER, Y_ANYXML, 0, NMAX}, + {Y_CONTAINER, Y_CHOICE, 0, NMAX}, + {Y_CONTAINER, Y_CONFIG, 0, 1}, + {Y_CONTAINER, Y_CONTAINER, 0, NMAX}, + {Y_CONTAINER, Y_DESCRIPTION, 0, 1}, + {Y_CONTAINER, Y_GROUPING, 0, NMAX}, + {Y_CONTAINER, Y_IF_FEATURE, 0, NMAX}, + {Y_CONTAINER, Y_LEAF, 0, NMAX}, + {Y_CONTAINER, Y_LEAF_LIST, 0, NMAX}, + {Y_CONTAINER, Y_LIST, 0, NMAX}, + {Y_CONTAINER, Y_MUST, 0, NMAX}, + {Y_CONTAINER, Y_NOTIFICATION, 0, NMAX}, + {Y_CONTAINER, Y_PRESENCE, 0, 1}, + {Y_CONTAINER, Y_REFERENCE, 0, 1}, + {Y_CONTAINER, Y_STATUS, 0, 1}, + {Y_CONTAINER, Y_TYPEDEF, 0, NMAX}, + {Y_CONTAINER, Y_USES, 0, NMAX}, + {Y_CONTAINER, Y_WHEN, 0, 1}, + {Y_DEVIATE, Y_CONFIG, 0, 1}, + {Y_DEVIATE, Y_DEFAULT, 0, NMAX}, + {Y_DEVIATE, Y_MANDATORY, 0, 1}, + {Y_DEVIATE, Y_MAX_ELEMENTS, 0, 1}, + {Y_DEVIATE, Y_MIN_ELEMENTS, 0, 1}, + {Y_DEVIATE, Y_MUST, 0, NMAX}, + {Y_DEVIATE, Y_TYPE, 0, 1}, + {Y_DEVIATE, Y_UNIQUE, 0, NMAX}, + {Y_DEVIATE, Y_UNITS, 0, 1}, + {Y_DEVIATION, Y_DESCRIPTION, 0, 1}, + {Y_DEVIATION, Y_DEVIATE, 1, NMAX}, + {Y_DEVIATION, Y_REFERENCE, 0, 1}, + {Y_ENUM, Y_DESCRIPTION, 0, 1}, + {Y_ENUM, Y_IF_FEATURE, 0, NMAX}, + {Y_ENUM, Y_REFERENCE, 0, 1}, + {Y_ENUM, Y_STATUS, 0, 1}, + {Y_ENUM, Y_VALUE, 0, 1}, + {Y_EXTENSION, Y_ARGUMENT, 0, 1}, + {Y_EXTENSION, Y_DESCRIPTION, 0, 1}, + {Y_EXTENSION, Y_REFERENCE, 0, 1}, + {Y_EXTENSION, Y_STATUS, 0, 1}, + {Y_FEATURE, Y_DESCRIPTION, 0, 1}, + {Y_FEATURE, Y_IF_FEATURE, 0, NMAX}, + {Y_FEATURE, Y_REFERENCE, 0, 1}, + {Y_FEATURE, Y_STATUS, 0, 1}, + {Y_GROUPING, Y_ACTION, 0, NMAX}, + {Y_GROUPING, Y_ANYDATA, 0, NMAX}, + {Y_GROUPING, Y_ANYXML, 0, NMAX}, + {Y_GROUPING, Y_CHOICE, 0, NMAX}, + {Y_GROUPING, Y_CONTAINER, 0, NMAX}, + {Y_GROUPING, Y_DESCRIPTION, 0, 1}, + {Y_GROUPING, Y_GROUPING, 0, NMAX}, + {Y_GROUPING, Y_LEAF, 0, NMAX}, + {Y_GROUPING, Y_LEAF_LIST, 0, NMAX}, + {Y_GROUPING, Y_LIST, 0, NMAX}, + {Y_GROUPING, Y_NOTIFICATION, 0, NMAX}, + {Y_GROUPING, Y_REFERENCE, 0, 1}, + {Y_GROUPING, Y_STATUS, 0, 1}, + {Y_GROUPING, Y_TYPEDEF, 0, NMAX}, + {Y_GROUPING, Y_USES, 0, NMAX}, + {Y_IDENTITY, Y_BASE, 0, NMAX}, + {Y_IDENTITY, Y_DESCRIPTION, 0, 1}, + {Y_IDENTITY, Y_IF_FEATURE, 0, NMAX}, + {Y_IDENTITY, Y_REFERENCE, 0, 1}, + {Y_IDENTITY, Y_STATUS, 0, 1}, + {Y_IMPORT, Y_DESCRIPTION, 0, 1}, + {Y_IMPORT, Y_PREFIX, 1, 1}, + {Y_IMPORT, Y_REFERENCE, 0, 1}, + {Y_IMPORT, Y_REVISION_DATE,0, 1}, + {Y_INCLUDE, Y_DESCRIPTION, 0, 1}, + {Y_INCLUDE, Y_REFERENCE, 0, 1}, + {Y_INCLUDE, Y_REVISION_DATE,0, 1}, + {Y_INPUT, Y_ANYDATA, 0, NMAX}, + {Y_INPUT, Y_ANYXML, 0, NMAX}, + {Y_INPUT, Y_CHOICE, 0, NMAX}, + {Y_INPUT, Y_CONTAINER, 0, NMAX}, + {Y_INPUT, Y_GROUPING, 0, NMAX}, + {Y_INPUT, Y_LEAF, 0, NMAX}, + {Y_INPUT, Y_LEAF_LIST, 0, NMAX}, + {Y_INPUT, Y_LIST, 0, NMAX}, + {Y_INPUT, Y_MUST, 0, NMAX}, + {Y_INPUT, Y_TYPEDEF, 0, NMAX}, + {Y_INPUT, Y_USES, 0, NMAX}, + {Y_LEAF, Y_CONFIG, 0, 1}, + {Y_LEAF, Y_DEFAULT, 0, 1}, + {Y_LEAF, Y_DESCRIPTION, 0, 1}, + {Y_LEAF, Y_IF_FEATURE, 0, NMAX}, + {Y_LEAF, Y_MANDATORY, 0, 1}, + {Y_LEAF, Y_MUST, 0, NMAX}, + {Y_LEAF, Y_REFERENCE, 0, 1}, + {Y_LEAF, Y_STATUS, 0, 1}, + {Y_LEAF, Y_TYPE, 1, 1}, + {Y_LEAF, Y_UNITS, 0, 1}, + {Y_LEAF, Y_WHEN, 0, 1}, + {Y_LEAF_LIST, Y_CONFIG, 0, 1}, + {Y_LEAF_LIST, Y_DEFAULT, 0, NMAX}, + {Y_LEAF_LIST, Y_DESCRIPTION, 0, 1}, + {Y_LEAF_LIST, Y_IF_FEATURE, 0, NMAX}, + {Y_LEAF_LIST, Y_MAX_ELEMENTS, 0, 1}, + {Y_LEAF_LIST, Y_MIN_ELEMENTS, 0, 1}, + {Y_LEAF_LIST, Y_MUST, 0, NMAX}, + {Y_LEAF_LIST, Y_ORDERED_BY, 0, 1}, + {Y_LEAF_LIST, Y_REFERENCE, 0, 1}, + {Y_LEAF_LIST, Y_STATUS, 0, 1}, + {Y_LEAF_LIST, Y_TYPE, 1, 1}, + {Y_LEAF_LIST, Y_UNITS, 0, 1}, + {Y_LEAF_LIST, Y_WHEN, 0, 1}, + {Y_LENGTH, Y_DESCRIPTION, 0, 1}, + {Y_LENGTH, Y_ERROR_APP_TAG, 0, 1}, + {Y_LENGTH, Y_ERROR_MESSAGE, 0, 1}, + {Y_LENGTH, Y_REFERENCE, 0, 1}, + {Y_LIST, Y_ACTION, 0, NMAX}, + {Y_LIST, Y_ANYDATA, 0, NMAX}, + {Y_LIST, Y_ANYXML, 0, NMAX}, + {Y_LIST, Y_CHOICE, 0, NMAX}, + {Y_LIST, Y_CONFIG, 0, 1}, + {Y_LIST, Y_CONTAINER, 0, NMAX}, + {Y_LIST, Y_DESCRIPTION, 0, 1}, + {Y_LIST, Y_GROUPING, 0, NMAX}, + {Y_LIST, Y_IF_FEATURE, 0, NMAX}, + {Y_LIST, Y_KEY, 0, 1}, + {Y_LIST, Y_LEAF, 0, NMAX}, + {Y_LIST, Y_LEAF_LIST, 0, NMAX}, + {Y_LIST, Y_LIST, 0, NMAX}, + {Y_LIST, Y_MAX_ELEMENTS, 0, 1}, + {Y_LIST, Y_MIN_ELEMENTS, 0, 1}, + {Y_LIST, Y_MUST, 0, NMAX}, + {Y_LIST, Y_NOTIFICATION, 0, NMAX}, + {Y_LIST, Y_ORDERED_BY, 0, 1}, + {Y_LIST, Y_REFERENCE, 0, 1}, + {Y_LIST, Y_STATUS, 0, 1}, + {Y_LIST, Y_TYPEDEF, 0, NMAX}, + {Y_LIST, Y_UNIQUE, 0, NMAX}, + {Y_LIST, Y_USES, 0, NMAX}, + {Y_LIST, Y_WHEN, 0,1}, {Y_MODULE, Y_ANYDATA, 0, NMAX}, {Y_MODULE, Y_ANYXML, 0, NMAX}, {Y_MODULE, Y_AUGMENT, 0, NMAX}, @@ -134,17 +345,115 @@ static const struct ycard yclist[] = { {Y_MODULE, Y_RPC, 0, NMAX}, {Y_MODULE, Y_TYPEDEF, 0, NMAX}, {Y_MODULE, Y_USES, 0, NMAX}, - {Y_MODULE, Y_YANG_VERSION, 0, 1}, + {Y_MODULE, Y_YANG_VERSION, 0, 1}, + {Y_MUST, Y_DESCRIPTION, 0, 1}, + {Y_MUST, Y_ERROR_APP_TAG, 0, 1}, + {Y_MUST, Y_ERROR_MESSAGE, 0, 1}, + {Y_MUST, Y_REFERENCE, 0, 1}, + {Y_NOTIFICATION, Y_ANYDATA, 0, NMAX}, + {Y_NOTIFICATION, Y_ANYXML, 0, NMAX}, + {Y_NOTIFICATION, Y_CHOICE, 0, NMAX}, + {Y_NOTIFICATION, Y_CONTAINER, 0, NMAX}, + {Y_NOTIFICATION, Y_DESCRIPTION, 0, 1}, + {Y_NOTIFICATION, Y_GROUPING, 0, NMAX}, + {Y_NOTIFICATION, Y_IF_FEATURE, 0, NMAX}, + {Y_NOTIFICATION, Y_LEAF, 0, NMAX}, + {Y_NOTIFICATION, Y_LEAF_LIST, 0, NMAX}, + {Y_NOTIFICATION, Y_LIST, 0, NMAX}, + {Y_NOTIFICATION, Y_MUST, 0, NMAX}, + {Y_NOTIFICATION, Y_REFERENCE, 0, 1}, + {Y_NOTIFICATION, Y_STATUS, 0, 1}, + {Y_NOTIFICATION, Y_TYPEDEF, 0, NMAX}, + {Y_NOTIFICATION, Y_USES, 0, NMAX}, + {Y_OUTPUT, Y_ANYDATA, 0, NMAX}, + {Y_OUTPUT, Y_ANYXML, 0, NMAX}, + {Y_OUTPUT, Y_CHOICE, 0, NMAX}, + {Y_OUTPUT, Y_CONTAINER, 0, NMAX}, + {Y_OUTPUT, Y_GROUPING, 0, NMAX}, + {Y_OUTPUT, Y_LEAF, 0, NMAX}, + {Y_OUTPUT, Y_LEAF_LIST, 0, NMAX}, + {Y_OUTPUT, Y_LIST, 0, NMAX}, + {Y_OUTPUT, Y_MUST, 0, NMAX}, + {Y_OUTPUT, Y_TYPEDEF, 0, NMAX}, + {Y_OUTPUT, Y_USES, 0, NMAX}, + {Y_PATTERN, Y_DESCRIPTION, 0, 1}, + {Y_PATTERN, Y_ERROR_APP_TAG, 0, 1}, + {Y_PATTERN, Y_ERROR_MESSAGE, 0, 1}, + {Y_PATTERN, Y_MODIFIER, 0, 1}, + {Y_PATTERN, Y_REFERENCE, 0, 1}, + {Y_RANGE, Y_DESCRIPTION, 0, 1}, + {Y_RANGE, Y_ERROR_APP_TAG, 0, 1}, + {Y_RANGE, Y_ERROR_MESSAGE, 0, 1}, + {Y_RANGE, Y_REFERENCE, 0, 1}, + {Y_REVISION, Y_DESCRIPTION, 0, 1}, + {Y_REVISION, Y_REFERENCE, 0, 1}, + {Y_RPC, Y_DESCRIPTION, 0, 1}, + {Y_RPC, Y_GROUPING, 0, NMAX}, + {Y_RPC, Y_IF_FEATURE, 0, NMAX}, + {Y_RPC, Y_INPUT, 0, 1}, + {Y_RPC, Y_OUTPUT, 0, 1}, + {Y_RPC, Y_REFERENCE, 0, 1}, + {Y_RPC, Y_STATUS, 0, 1}, + {Y_RPC, Y_TYPEDEF, 0, NMAX}, + {Y_SUBMODULE, Y_ANYDATA, 0, NMAX}, + {Y_SUBMODULE, Y_AUGMENT, 0, NMAX}, + {Y_SUBMODULE, Y_BELONGS_TO, 1, 1}, + {Y_SUBMODULE, Y_CHOICE, 0, NMAX}, + {Y_SUBMODULE, Y_CONTACT, 0, 1}, + {Y_SUBMODULE, Y_CONTAINER, 0, NMAX}, + {Y_SUBMODULE, Y_DESCRIPTION,0, 1}, + {Y_SUBMODULE, Y_DEVIATION, 0, NMAX}, + {Y_SUBMODULE, Y_EXTENSION, 0, NMAX}, + {Y_SUBMODULE, Y_FEATURE, 0, NMAX}, + {Y_SUBMODULE, Y_GROUPING, 0, NMAX}, + {Y_SUBMODULE, Y_IDENTITY, 0, NMAX}, + {Y_SUBMODULE, Y_IMPORT, 0, NMAX}, + {Y_SUBMODULE, Y_INCLUDE, 0, NMAX}, + {Y_SUBMODULE, Y_LEAF, 0, NMAX}, + {Y_SUBMODULE, Y_LEAF_LIST, 0, NMAX}, + {Y_SUBMODULE, Y_LIST, 0, NMAX}, + {Y_SUBMODULE, Y_NOTIFICATION,0, NMAX}, + {Y_SUBMODULE, Y_ORGANIZATION,0, 1}, + {Y_SUBMODULE, Y_REFERENCE, 0, 1}, + {Y_SUBMODULE, Y_REVISION, 0, NMAX}, + {Y_SUBMODULE, Y_RPC, 0, NMAX}, + {Y_SUBMODULE, Y_TYPEDEF, 0, NMAX}, + {Y_SUBMODULE, Y_USES, 0, NMAX}, + {Y_SUBMODULE, Y_YANG_VERSION,0, 1}, /* "yang-version" statement is mandatory in YANG version "1.1". */ + {Y_TYPE, Y_BASE, 0, NMAX}, + {Y_TYPE, Y_BIT, 0, NMAX}, + {Y_TYPE, Y_ENUM, 0, NMAX}, + {Y_TYPE, Y_FRACTION_DIGITS, 0, 1}, + {Y_TYPE, Y_LENGTH, 0, 1}, + {Y_TYPE, Y_PATH, 0, 1}, + {Y_TYPE, Y_PATTERN, 0, NMAX}, + {Y_TYPE, Y_RANGE, 0, 1}, + {Y_TYPE, Y_REQUIRE_INSTANCE, 0, 1}, + {Y_TYPE, Y_TYPE, 0, NMAX}, + {Y_TYPEDEF, Y_DEFAULT, 0, 1}, + {Y_TYPEDEF, Y_DESCRIPTION,0, 1}, + {Y_TYPEDEF, Y_REFERENCE, 0, 1}, + {Y_TYPEDEF, Y_STATUS, 0, 1}, + {Y_TYPEDEF, Y_TYPE, 1, 1}, + {Y_TYPEDEF, Y_UNITS, 0, 1}, + {Y_USES, Y_AUGMENT, 0, NMAX}, + {Y_USES, Y_DESCRIPTION, 0, 1}, + {Y_USES, Y_IF_FEATURE, 0, NMAX}, + {Y_USES, Y_REFERENCE, 0, 1}, + {Y_USES, Y_REFINE, 0, NMAX}, + {Y_USES, Y_STATUS, 0, 1}, + {Y_USES, Y_WHEN, 0, 1}, {0,} }; /*! Find yang parent and child combination in yang cardinality table * @param[in] parent Parent Yang spec - * @param[in] child Child yang spec if -1 first + * @param[in] child Child yang spec if 0 first * @param[in] yc Yang cardinality map * @param[in] p If set, quit as soon as parents dont match * @retval NULL Not found * @retval yp Found + * @note if cardinal list above is sorted, this search could do binary */ static const struct ycard * ycard_find(enum rfc_6020 parent, @@ -202,6 +511,7 @@ yang_cardinality(clicon_handle h, ck = ys->ys_keyword; if (ck == Y_UNKNOWN) /* special case */ continue; + /* Find entry in yang cardinality table from parent/child keyword pair */ if ((yc = ycard_find(pk, ck, ycplist, 1)) == NULL){ clicon_err(OE_YANG, 0, "%s: \"%s\" is child of \"%s\", but should not be", modname, yang_key2str(ck), yang_key2str(pk)); @@ -231,7 +541,7 @@ yang_cardinality(clicon_handle h, } } - if (0) { /* Notyet */ + if (1) { /* Notyet */ /* 4) Recurse */ i = 0; while (iys_len){ /* Note, children may be removed */ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 0af79687..7bd42cc1 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -490,9 +490,11 @@ import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } include_stmt : K_INCLUDE identifier_str ';' { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("include_stmt"); clicon_debug(2,"include-stmt -> id-str"); } - | K_INCLUDE identifier_str '{' include_substmts '}' - { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("include_stmt"); - clicon_debug(2,"include-stmt -> id-str { include-substmts }"); } + | K_INCLUDE identifier_str + { if (ysp_add_push(_yy, Y_INCLUDE, $2) == NULL) _YYERROR("include_stmt"); } + '{' include_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("include_stmt"); + clicon_debug(2,"include-stmt -> id-str { include-substmts }"); } ; include_substmts : include_substmts include_substmt @@ -518,9 +520,13 @@ prefix_stmt : K_PREFIX identifier_str stmtend /* XXX prefix-arg-str */ clicon_debug(2,"prefix-stmt -> PREFIX string ;");} ; -belongs_to_stmt : K_BELONGS_TO identifier_str '{' prefix_stmt '}' - { if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("belongs_to_stmt"); - clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");} +belongs_to_stmt : K_BELONGS_TO identifier_str + { if (ysp_add_push(_yy, Y_BELONGS_TO, $2) == NULL) _YYERROR("belongs_to_stmt"); } + + '{' prefix_stmt '}' + { if (ystack_pop(_yy) < 0) _YYERROR("belongs_to_stmt"); + clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } "); + } ; organization_stmt: K_ORGANIZATION string stmtend @@ -1469,10 +1475,10 @@ deviation_substmt : description_stmt { clicon_debug(2,"deviation-substmt -> des ; deviate_not_supported_stmt : K_DEVIATE string stmtend - { clicon_debug(2,"deviate-not-supported-stmt -> DEVIATE string"); } + { if (ysp_add(_yy, Y_DEVIATE, $2, NULL) == NULL) _YYERROR("notification_stmt"); + clicon_debug(2,"deviate-not-supported-stmt -> DEVIATE string ;"); } ; - /* body */ body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts body-stmt"); } | body_stmt { clicon_debug(2,"body-stmts -> body-stmt");} diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index efb68c54..26d81976 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -6,16 +6,18 @@ # - release/models/wifi/types/openconfig-wifi-types.yang # issue: https://github.com/clicon/clixon/issues/59 # -#PROG="valgrind --leak-check=full --show-leak-kinds=all ../util/clixon_util_yang" -PROG=../util/clixon_util_yang OPENCONFIG=public OCDIR=$OPENCONFIG/release/models # Clone openconfig dir if not there +if false; then if [ ! -d public ]; then git clone https://github.com/openconfig/public else - (cd public; git pull) + (cd public; + #git pull + ) +fi fi # include err() and new() functions and creates $dir @@ -91,7 +93,7 @@ for f in $files; do let m++; fi done -echo "Number of modules:$m" +new "Openconfig test: $clixon_cli -1f $cfg -y $f show version ($m modules)" for f in $files; do if [ -n "$(head -1 $f|grep '^module')" ]; then new "cli $f" From 66ce941ac4d1bf2d3d28ac8aa2439a6e444b551a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 6 Dec 2018 10:06:13 +0100 Subject: [PATCH 11/72] minor cardinality patch --- lib/src/clixon_yang_cardinality.c | 14 ++++++-------- test/lib.sh | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 4a8b1ddf..9c26fe92 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -541,14 +541,12 @@ yang_cardinality(clicon_handle h, } } - if (1) { /* Notyet */ - /* 4) Recurse */ - i = 0; - while (iys_len){ /* Note, children may be removed */ - ys = yt->ys_stmt[i++]; - if (yang_cardinality(h, ys, modname) < 0) - goto done; - } + /* 4) Recurse */ + i = 0; + while (iys_len){ /* Note, children may be removed */ + ys = yt->ys_stmt[i++]; + if (yang_cardinality(h, ys, modname) < 0) + goto done; } ok: retval = 0; diff --git a/test/lib.sh b/test/lib.sh index 716b6c83..eb6bdbbd 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -13,6 +13,7 @@ clixon_cli=clixon_cli # For memcheck / performance #clixon_netconf="valgrind --tool=callgrind clixon_netconf" +# use kcachegrind to view #clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" clixon_netconf=clixon_netconf From e5c0b06cf92729630960d10b2f78bb952f67ac5f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Dec 2018 11:22:26 +0100 Subject: [PATCH 12/72] Move NACM files from backend to lib src dir --- CHANGELOG.md | 5 +- apps/backend/backend_client.c | 266 --------------------- apps/backend/backend_handle.h | 4 - apps/backend/backend_main.c | 4 +- apps/backend/clixon_backend_handle.c | 23 -- lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_nacm.h | 44 ++++ lib/clixon/clixon_options.h | 3 + lib/src/Makefile.in | 2 +- lib/src/clixon_nacm.c | 334 +++++++++++++++++++++++++++ lib/src/clixon_options.c | 44 +++- 11 files changed, 432 insertions(+), 298 deletions(-) create mode 100644 lib/clixon/clixon_nacm.h create mode 100644 lib/src/clixon_nacm.c diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ab6c86..7e3837ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features -* More complete Yang parser + +* NACM extension (RFC8341) + * Move NACM files from backend to lib src dir +* Yang code upgrade (RFC7950) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 * Support of submodule, include and belongs-to. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index fedad77a..7ff71e72 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -935,272 +935,6 @@ from_client_debug(clicon_handle h, return retval; } -/*! Match nacm access operations according to RFC8341 3.4.4. - * Incoming RPC Message Validation Step 7 (c) - * The rule's "access-operations" leaf has the "exec" bit set or - * has the special value "*". - * @retval 0 No match - * @retval 1 Match - * XXX access_operations is bit-fields - */ -static int -nacm_match_access(char *access_operations, - char *mode) -{ - if (access_operations==NULL) - return 0; - if (strcmp(access_operations,"*")==0) - return 1; - if (strstr(access_operations, mode)!=NULL) - return 1; - return 0; -} - -/*! Match nacm single rule. Either match with access or deny. Or not match. - * @param[in] h Clicon handle - * @param[in] name rpc name - * @param[in] xrule NACM rule XML tree - * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. - * @retval -1 Error - * @retval 0 Matching rule AND Not access and cbret set - * @retval 1 Matchung rule AND Access - * @retval 2 No matching rule Goto step 10 - * From RFC8341 3.4.4. Incoming RPC Message Validation - +---------+-----------------+---------------------+-----------------+ - | Method | Resource class | NETCONF operation | Access | - | | | | operation | - +---------+-----------------+---------------------+-----------------+ - | OPTIONS | all | none | none | - | HEAD | all | , | read | - | GET | all | , | read | - | POST | datastore, data | | create | - | POST | operation | specified operation | execute | - | PUT | data | | create, update | - | PUT | datastore | | update | - | PATCH | data, datastore | | update | - | DELETE | data | | delete | - - 7.(cont) A rule matches if all of the following criteria are met: - * The rule's "module-name" leaf is "*" or equals the name of - the YANG module where the protocol operation is defined. - - * Either (1) the rule does not have a "rule-type" defined or - (2) the "rule-type" is "protocol-operation" and the - "rpc-name" is "*" or equals the name of the requested - protocol operation. - - * The rule's "access-operations" leaf has the "exec" bit set or - has the special value "*". - */ -static int -nacm_match_rule(clicon_handle h, - char *name, - cxobj *xrule, - cbuf *cbret) -{ - int retval = -1; - // cxobj *x; - char *module_name; - char *rpc_name; - char *access_operations; - char *action; - - module_name = xml_find_body(xrule, "module-name"); - rpc_name = xml_find_body(xrule, "rpc-name"); - /* XXX access_operations can be a set of bits */ - access_operations = xml_find_body(xrule, "access-operations"); - action = xml_find_body(xrule, "action"); - clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__, - module_name, rpc_name, access_operations, action); - if (module_name && strcmp(module_name,"*")==0){ - if (nacm_match_access(access_operations, "exec")){ - if (rpc_name==NULL || - strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){ - /* Here is a matching rule */ - if (action && strcmp(action, "permit")==0){ - retval = 1; - goto done; - } - else{ - if (netconf_access_denied(cbret, "protocol", "access denied") < 0) - goto done; - retval = 0; - goto done; - } - } - } - } - retval = 2; /* no matching rule */ - done: - return retval; - -} - -/*! Make nacm access control - * @param[in] h Clicon handle - * @param[in] mode NACMmode, internal or external - * @param[in] name rpc name - * @param[in] username - * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. - * @retval -1 Error - * @retval 0 Not access and cbret set - * @retval 1 Access - * From RFC8341 3.4.4. Incoming RPC Message Validation - */ -static int -nacm_access(clicon_handle h, - char *mode, - char *name, - char *username, - cbuf *cbret) -{ - int retval = -1; - cxobj *xtop = NULL; - cxobj *xacm; - cxobj *x; - cxobj *xrlist; - cxobj *xrule; - char *enabled = NULL; - cxobj **gvec = NULL; /* groups */ - size_t glen; - cxobj **rlistvec = NULL; /* rule-list */ - size_t rlistlen; - cxobj **rvec = NULL; /* rules */ - size_t rlen; - int i, j; - char *exec_default = NULL; - int ret; - - clicon_debug(1, "%s", __FUNCTION__); - /* 0. If nacm-mode is external, get NACM defintion from separet tree, - otherwise get it from internal configuration */ - if (strcmp(mode, "external")==0){ - if ((xtop = backend_nacm_list_get(h)) == NULL){ - clicon_err(OE_XML, 0, "No nacm external tree"); - goto done; - } - } - else if (strcmp(mode, "internal")==0){ - if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) - goto done; - } - else{ - clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode); - goto done; - } - - /* 1. If the "enable-nacm" leaf is set to "false", then the protocol - operation is permitted. (or config does not exist) */ - - if ((xacm = xpath_first(xtop, "nacm")) == NULL) - goto permit; - exec_default = xml_find_body(xacm, "exec-default"); - if ((x = xpath_first(xacm, "enable-nacm")) == NULL) - goto permit; - enabled = xml_body(x); - if (strcmp(enabled, "true") != 0) - goto permit; - - /* 2. If the requesting session is identified as a recovery session, - then the protocol operation is permitted. NYI */ - - /* 3. If the requested operation is the NETCONF - protocol operation, then the protocol operation is permitted. - */ - if (strcmp(name, "close-session") == 0) - goto permit; - /* 4. Check all the "group" entries to see if any of them contain a - "user-name" entry that equals the username for the session - making the request. (If the "enable-external-groups" leaf is - "true", add to these groups the set of groups provided by the - transport layer.) */ - if (username == NULL) - goto step10; - /* User's group */ - if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0) - goto done; - /* 5. If no groups are found, continue with step 10. */ - if (glen == 0) - goto step10; - /* 6. Process all rule-list entries, in the order they appear in the - configuration. If a rule-list's "group" leaf-list does not - match any of the user's groups, proceed to the next rule-list - entry. */ - if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0) - goto done; - for (i=0; i or , then the protocol operation - is denied. */ - if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){ - if (netconf_access_denied(cbret, "protocol", "default deny") < 0) - goto done; - goto deny; - } - /* 12. If the "exec-default" leaf is set to "permit", then permit the - protocol operation; otherwise, deny the request. */ - if (exec_default ==NULL || strcmp(exec_default, "permit")==0) - goto permit; - if (netconf_access_denied(cbret, "protocol", "default deny") < 0) - goto done; - goto deny; - permit: - retval = 1; - done: - clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); - if (strcmp(mode, "internal")==0 && xtop) - xml_free(xtop); - if (gvec) - free(gvec); - if (rlistvec) - free(rlistvec); - if (rvec) - free(rvec); - return retval; - deny: /* Here, cbret must contain a netconf error msg */ - assert(cbuf_len(cbret)); - retval = 0; - goto done; -} - /*! 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. diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 676cc50c..8eb3e073 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -52,8 +52,4 @@ struct client_entry *backend_client_list(clicon_handle h); int backend_client_delete(clicon_handle h, struct client_entry *ce); -int backend_nacm_list_set(clicon_handle h, cxobj *xnacm); - -cxobj * backend_nacm_list_get(clicon_handle h); - #endif /* _BACKEND_HANDLE_H_ */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 1359e984..ec4a99d7 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -91,6 +91,8 @@ backend_terminate(clicon_handle h) yspec_free(yspec); if ((yspec = clicon_config_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_nacm_ext(h)) != NULL) + xml_free(x); if ((x = clicon_conf_xml(h)) != NULL) xml_free(x); stream_publish_exit(); @@ -274,7 +276,7 @@ nacm_load_external(clicon_handle h) clicon_err(OE_XML, 0, "No xml tree in %s", filename); goto done; } - if (backend_nacm_list_set(h, xt) < 0) + if (clicon_nacm_ext_set(h, xt) < 0) goto done; retval = 0; done: diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index 8ed13d97..f4707de4 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -91,7 +91,6 @@ struct backend_handle { /* ------ end of common handle ------ */ struct client_entry *bh_ce_list; /* The client list */ int bh_ce_nr; /* Number of clients, just increment */ - cxobj *bh_nacm; /* NACM external struct */ }; /*! Creates and returns a clicon config handle for other CLICON API calls @@ -109,7 +108,6 @@ backend_handle_init(void) int backend_handle_exit(clicon_handle h) { - struct backend_handle *bh = handle(h); struct client_entry *ce; /* only delete client structs, not close sockets, etc, see backend_client_rm WHY NOT? */ @@ -120,8 +118,6 @@ backend_handle_exit(clicon_handle h) } backend_client_delete(h, ce); } - if (bh->bh_nacm) - xml_free(bh->bh_nacm); clicon_handle_exit(h); /* frees h and options (and streams) */ return 0; } @@ -188,22 +184,3 @@ backend_client_delete(clicon_handle h, return 0; } -int -backend_nacm_list_set(clicon_handle h, - cxobj *xnacm) -{ - struct backend_handle *bh = handle(h); - - if (bh->bh_nacm) - xml_free(bh->bh_nacm); - bh->bh_nacm = xnacm; - return 0; -} - -cxobj * -backend_nacm_list_get(clicon_handle h) -{ - struct backend_handle *bh = handle(h); - - return bh->bh_nacm; -} diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index c454835f..2fc6c50d 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -87,6 +87,7 @@ #include #include #include +#include /* * Global variables generated by Makefile diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h new file mode 100644 index 00000000..34e9a939 --- /dev/null +++ b/lib/clixon/clixon_nacm.h @@ -0,0 +1,44 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * XML sort and earch functions when used with YANG + */ +#ifndef _CLIXON_NACM_H +#define _CLIXON_NACM_H + +/* + * Prototypes + */ +int nacm_access(clicon_handle h, char *mode, char *name, char *username, cbuf *cbret); + +#endif /* _CLIXON_NACM_H */ diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 1f4069fc..b5cb55c3 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -171,6 +171,9 @@ int clicon_quiet_mode_set(clicon_handle h, int val); yang_spec * clicon_dbspec_yang(clicon_handle h); int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys); +cxobj * clicon_nacm_ext(clicon_handle h); +int clicon_nacm_ext_set(clicon_handle h, cxobj *xn); + #if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */ yang_spec * clicon_config_yang(clicon_handle h); int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index eefa969f..ce0c0587 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -74,7 +74,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \ - clixon_xml_db.c clixon_netconf_lib.c clixon_stream.c + clixon_xml_db.c clixon_netconf_lib.c clixon_stream.c clixon_nacm.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c new file mode 100644 index 00000000..1ab18411 --- /dev/null +++ b/lib/src/clixon_nacm.c @@ -0,0 +1,334 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * NACM code according to RFC8341 Network Configuration Access Control Model + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_string.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_netconf_lib.h" +#include "clixon_xpath_ctx.h" +#include "clixon_xpath.h" +#include "clixon_xml_db.h" +#include "clixon_nacm.h" + +/*! Match nacm access operations according to RFC8341 3.4.4. + * Incoming RPC Message Validation Step 7 (c) + * The rule's "access-operations" leaf has the "exec" bit set or + * has the special value "*". + * @retval 0 No match + * @retval 1 Match + * @note access_operations is bit-fields + */ +static int +nacm_match_access(char *access_operations, + char *mode) +{ + if (access_operations==NULL) + return 0; + if (strcmp(access_operations,"*")==0) + return 1; + if (strstr(access_operations, mode)!=NULL) + return 1; + return 0; +} + +/*! Match nacm single rule. Either match with access or deny. Or not match. + * @param[in] h Clicon handle + * @param[in] name rpc name + * @param[in] xrule NACM rule XML tree + * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. + * @retval -1 Error + * @retval 0 Matching rule AND Not access and cbret set + * @retval 1 Matchung rule AND Access + * @retval 2 No matching rule Goto step 10 + * From RFC8341 3.4.4. Incoming RPC Message Validation + +---------+-----------------+---------------------+-----------------+ + | Method | Resource class | NETCONF operation | Access | + | | | | operation | + +---------+-----------------+---------------------+-----------------+ + | OPTIONS | all | none | none | + | HEAD | all | , | read | + | GET | all | , | read | + | POST | datastore, data | | create | + | POST | operation | specified operation | execute | + | PUT | data | | create, update | + | PUT | datastore | | update | + | PATCH | data, datastore | | update | + | DELETE | data | | delete | + + 7.(cont) A rule matches if all of the following criteria are met: + * The rule's "module-name" leaf is "*" or equals the name of + the YANG module where the protocol operation is defined. + + * Either (1) the rule does not have a "rule-type" defined or + (2) the "rule-type" is "protocol-operation" and the + "rpc-name" is "*" or equals the name of the requested + protocol operation. + + * The rule's "access-operations" leaf has the "exec" bit set or + has the special value "*". + */ +static int +nacm_match_rule(clicon_handle h, + char *name, + cxobj *xrule, + cbuf *cbret) +{ + int retval = -1; + // cxobj *x; + char *module_name; + char *rpc_name; + char *access_operations; + char *action; + + module_name = xml_find_body(xrule, "module-name"); + rpc_name = xml_find_body(xrule, "rpc-name"); + /* XXX access_operations can be a set of bits */ + access_operations = xml_find_body(xrule, "access-operations"); + action = xml_find_body(xrule, "action"); + clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__, + module_name, rpc_name, access_operations, action); + if (module_name && strcmp(module_name,"*")==0){ + if (nacm_match_access(access_operations, "exec")){ + if (rpc_name==NULL || + strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){ + /* Here is a matching rule */ + if (action && strcmp(action, "permit")==0){ + retval = 1; + goto done; + } + else{ + if (netconf_access_denied(cbret, "protocol", "access denied") < 0) + goto done; + retval = 0; + goto done; + } + } + } + } + retval = 2; /* no matching rule */ + done: + return retval; + +} + +/*! Make nacm access control + * @param[in] h Clicon handle + * @param[in] mode NACMmode, internal or external + * @param[in] name rpc name + * @param[in] username + * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. + * @retval -1 Error + * @retval 0 Not access and cbret set + * @retval 1 Access + * @see RFC8341 3.4.4. Incoming RPC Message Validation + */ +int +nacm_access(clicon_handle h, + char *mode, + char *name, + char *username, + cbuf *cbret) +{ + int retval = -1; + cxobj *xtop = NULL; + cxobj *xacm; + cxobj *x; + cxobj *xrlist; + cxobj *xrule; + char *enabled = NULL; + cxobj **gvec = NULL; /* groups */ + size_t glen; + cxobj **rlistvec = NULL; /* rule-list */ + size_t rlistlen; + cxobj **rvec = NULL; /* rules */ + size_t rlen; + int i, j; + char *exec_default = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + /* 0. If nacm-mode is external, get NACM defintion from separet tree, + otherwise get it from internal configuration */ + if (strcmp(mode, "external")==0){ + if ((xtop = clicon_nacm_ext(h)) == NULL){ + clicon_err(OE_XML, 0, "No nacm external tree"); + goto done; + } + } + else if (strcmp(mode, "internal")==0){ + if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) + goto done; + } + else{ + clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode); + goto done; + } + + /* 1. If the "enable-nacm" leaf is set to "false", then the protocol + operation is permitted. (or config does not exist) */ + if ((xacm = xpath_first(xtop, "nacm")) == NULL) + goto permit; + exec_default = xml_find_body(xacm, "exec-default"); + if ((x = xpath_first(xacm, "enable-nacm")) == NULL) + goto permit; + enabled = xml_body(x); + if (strcmp(enabled, "true") != 0) + goto permit; + + /* 2. If the requesting session is identified as a recovery session, + then the protocol operation is permitted. NYI */ + + /* 3. If the requested operation is the NETCONF + protocol operation, then the protocol operation is permitted. + */ + if (strcmp(name, "close-session") == 0) + goto permit; + /* 4. Check all the "group" entries to see if any of them contain a + "user-name" entry that equals the username for the session + making the request. (If the "enable-external-groups" leaf is + "true", add to these groups the set of groups provided by the + transport layer.) */ + if (username == NULL) + goto step10; + /* User's group */ + if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0) + goto done; + /* 5. If no groups are found, continue with step 10. */ + if (glen == 0) + goto step10; + /* 6. Process all rule-list entries, in the order they appear in the + configuration. If a rule-list's "group" leaf-list does not + match any of the user's groups, proceed to the next rule-list + entry. */ + if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0) + goto done; + for (i=0; i or , then the protocol operation + is denied. */ + if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){ + if (netconf_access_denied(cbret, "protocol", "default deny") < 0) + goto done; + goto deny; + } + /* 12. If the "exec-default" leaf is set to "permit", then permit the + protocol operation; otherwise, deny the request. */ + if (exec_default ==NULL || strcmp(exec_default, "permit")==0) + goto permit; + if (netconf_access_denied(cbret, "protocol", "default deny") < 0) + goto done; + goto deny; + permit: + retval = 1; + done: + clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); + if (strcmp(mode, "internal")==0 && xtop) + xml_free(xtop); + if (gvec) + free(gvec); + if (rlistvec) + free(rlistvec); + if (rvec) + free(rvec); + return retval; + deny: /* Here, cbret must contain a netconf error msg */ + assert(cbuf_len(cbret)); + retval = 0; + goto done; +} diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 9c737b18..74e5a645 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -33,8 +33,6 @@ * * CLICON options - * See clicon_tutorial appendix and clicon.conf.cpp.cpp on documentation of - * options */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -607,6 +605,48 @@ clicon_dbspec_yang_set(clicon_handle h, return 0; } +/*! Get NACM (rfc 8341) XML parse tree if external not in std xml config + * @param[in] h Clicon handle + * @retval xn XML NACM tree, or NULL + * @note only used if config option CLICON_NACM_MODE is external + * @see clicon_nacm_ext_set + */ +cxobj * +clicon_nacm_ext(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len; + void *p; + + if ((p = hash_value(cdat, "nacm_xml", &len)) != NULL) + return *(cxobj **)p; + return NULL; +} + +/*! Set NACM (rfc 8341) external XML parse tree, free old if any + * @param[in] h Clicon handle + * @param[in] xn XML Nacm tree + * @note only used if config option CLICON_NACM_MODE is external + * @see clicon_nacm_ext + */ +int +clicon_nacm_ext_set(clicon_handle h, + cxobj *xn) +{ + clicon_hash_t *cdat = clicon_data(h); + cxobj *xo; + + if ((xo = clicon_nacm_ext(h)) != NULL) + xml_free(xo); + /* It is the pointer to xn that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if (hash_add(cdat, "nacm_xml", &xn, sizeof(xn)) == NULL) + return -1; + return 0; +} + + #if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */ /*! Get YANG specification for clixon config * Must use hash functions directly since they are not strings. From f071e0b332b67c956b1276071502604668bcc305 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 8 Dec 2018 18:13:27 +0100 Subject: [PATCH 13/72] prioritized roadmap --- ROADMAP.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index e0dd98f3..12ae406f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,26 +1,43 @@ # Clixon roadmap -Not in prio order (yet) +In prio order -- XML - - [Namespace handling](https://github.com/clicon/clixon/issues/49) +High prio +- NACM (RFC 8341) + - Module rules + - Data node rules (read/create/delete/update/execute) +- Special handling of the initial startup transaction to avoid exit at startup + - Possibly - draft-wu-netconf-restconf-factory-restore-03 +- Handle revisions to data model. + - Possibly draft-wang-netmod-module-revision-management-01 + +Medium prio: +- Input validation on custom RPCs/ + - [Sanity checks](https://github.com/clicon/clixon/issues/47) +- Support for XML regex's. + - Currently Posix extended regular expressions +- Support a plugin callback that is invoked when copy-config is called. +- Preserve CLI command history across sessions. The up/down arrows + +Low prio: +- Provide a client library to access netconf APIs provided by system services. +- Support for restconf call-home (RFC 8071) +- Support for restconf PATCH method + +Not prioritized: +- XML [Namespace handling](https://github.com/clicon/clixon/issues/49) - NETCONF - Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53) - Netconf [framing](https://github.com/clicon/clixon/issues/50) - - [Sanity checks](https://github.com/clicon/clixon/issues/47) - [Child ordering](https://github.com/clicon/clixon/issues/22) - Netconf backend (Clixon acts as netconf controller) - Restconf - Query parameters -- NACM (RFC 8341) is somewhat limited - - Extend with data node access (read/create/delete/update/execute) - Streams (netconf and restconf) - Extend native stream mode with external persistent timeseries database, eg influxdb. - Jenkins CI/CD and webhooks - YANG - - [Cardinality](https://github.com/clicon/clixon/issues/48) - RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39) - - Factory default Setting - draft-wu-netconf-restconf-factory-restore-03 - Deviation, belongs-to, min/max-elements, action, unique - Containers - [Docker improvements](https://github.com/clicon/clixon/issues/44) From ae1af8da9ef6bc948aea4c7fb313d70365c785c6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 16 Dec 2018 19:46:26 +0100 Subject: [PATCH 14/72] * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. * Example user changed adm1 to andy to comply with RFC8341 example * Yang code upgrade (RFC7950) * RPC method input parameters validated * see https://github.com/clicon/clixon/issues/4 * Correct XML namespace handling * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: ``` # Wrong but accepted # Correct ``` * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) * XML to JSON translator support for mapping xmlns attribute to module name prefix. * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" * See https://github.com/clicon/clixon/issues/49 * Changed all make tags --> make TAGS * Keyvalue datastore removed (it has been disabled since 3.3.3) * debug rpc added in example application (should be in clixon-config). --- .gitignore | 3 + CHANGELOG.md | 26 +- README.md | 31 +- apps/Makefile.in | 2 +- apps/backend/backend_client.c | 80 +- apps/netconf/netconf_main.c | 69 +- apps/netconf/netconf_rpc.c | 26 +- apps/restconf/restconf_methods.c | 101 ++- datastore/Makefile.in | 3 +- datastore/README.md | 5 +- datastore/datastore_client.c | 9 +- datastore/keyvalue/Makefile.in | 98 --- datastore/keyvalue/clixon_chunk.c | 794 ------------------ datastore/keyvalue/clixon_chunk.h | 177 ---- datastore/keyvalue/clixon_keyvalue.c | 1130 -------------------------- datastore/keyvalue/clixon_keyvalue.h | 54 -- datastore/keyvalue/clixon_qdb.c | 588 -------------- datastore/keyvalue/clixon_qdb.h | 73 -- datastore/text/clixon_xmldb_text.c | 32 +- example/README.md | 3 +- example/example.yang | 10 + example/example_cli.c | 2 +- example/example_restconf.c | 10 +- extras/rpm/clixon.spec | 2 +- lib/Makefile.in | 2 +- lib/clixon/clixon_nacm.h | 12 +- lib/clixon/clixon_xml.h | 16 +- lib/clixon/clixon_xml_map.h | 2 + lib/clixon/clixon_yang.h | 5 +- lib/src/clixon_json.c | 77 +- lib/src/clixon_nacm.c | 119 ++- lib/src/clixon_netconf_lib.c | 13 +- lib/src/clixon_options.c | 7 + lib/src/clixon_proto_client.c | 3 +- lib/src/clixon_xml.c | 149 +++- lib/src/clixon_xml_db.c | 2 +- lib/src/clixon_xml_map.c | 198 ++++- lib/src/clixon_xml_sort.c | 41 +- lib/src/clixon_yang.c | 114 ++- test/lib.sh | 35 +- test/nacm.sh | 47 ++ test/test_cli.sh | 29 +- test/test_datastore.sh | 26 +- test/test_feature.sh | 35 +- test/test_identity.sh | 30 +- test/test_leafref.sh | 27 +- test/test_list.sh | 28 +- test/test_nacm.sh | 88 +- test/test_nacm_ext.sh | 95 +-- test/test_nacm_protocol.sh | 231 ++++++ test/test_netconf.sh | 37 +- test/test_order.sh | 27 +- test/test_perf.sh | 38 +- test/test_restconf.sh | 32 +- test/test_restconf2.sh | 27 +- test/test_stream.sh | 27 +- test/test_type.sh | 27 +- test/test_union.sh | 119 +++ test/test_when_must.sh | 27 +- test/test_yang.sh | 29 +- test/test_yang_load.sh | 33 +- test/test_yang_namespace.sh | 141 ++++ yang/clixon-config@2018-10-21.yang | 21 +- 63 files changed, 1852 insertions(+), 3492 deletions(-) delete mode 100644 datastore/keyvalue/Makefile.in delete mode 100644 datastore/keyvalue/clixon_chunk.c delete mode 100644 datastore/keyvalue/clixon_chunk.h delete mode 100644 datastore/keyvalue/clixon_keyvalue.c delete mode 100644 datastore/keyvalue/clixon_keyvalue.h delete mode 100644 datastore/keyvalue/clixon_qdb.c delete mode 100644 datastore/keyvalue/clixon_qdb.h create mode 100755 test/nacm.sh create mode 100755 test/test_nacm_protocol.sh create mode 100755 test/test_union.sh create mode 100755 test/test_yang_namespace.sh diff --git a/.gitignore b/.gitignore index e9ed0e3e..cd4446bc 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ lib/clixon/clixon.h build-root/*.tar.xz build-root/*.rpm build-root/rpmbuild + +test/public +doc/html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3837ec..160f6254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,21 +8,40 @@ ### Major New features * NACM extension (RFC8341) - * Move NACM files from backend to lib src dir + * NACM module support (RFC8341 A1+A2) + * Recovery user "_nacm_recovery" added. + * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. + * Example user changed adm1 to andy to comply with RFC8341 example * Yang code upgrade (RFC7950) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 + * RPC method input parameters validated + * see https://github.com/clicon/clixon/issues/47 * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public - * Improved unknown handling + * Improved "unknown" handling * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. +* Correct XML namespace handling + * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: +``` + # Wrong but accepted + # Correct + + +``` + * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) + * XML to JSON translator support for mapping xmlns attribute to module name prefix. + * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" + * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) * Yang parser is stricter (see above) which may break parsing of existing yang specs. +* XML namespace handling is corrected (see above) + * For backward compatibility set config option CLICON_XML_NS_ITERATE * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. * Change all @datamodel:tree to @datamodel in all CLI specification files @@ -30,6 +49,8 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Changed all make tags --> make TAGS +* Keyvalue datastore removed (it has been disabled since 3.3.3) * Removed return value ymodp from yang parse functions (eg yang_parse()). * New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. * XML parser conformance to W3 spec @@ -42,6 +63,7 @@ * getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58) ### Known issues +* debug rpc added in example application (should be in clixon-config). ## 3.8.0 (6 Nov 2018) diff --git a/README.md b/README.md index 0389fa17..bc0e7f50 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ Background ========== Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture. -Users of clixon currently include: +Users of Clixon currently include: * [Netgate](https://www.netgate.com) * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) @@ -98,16 +98,16 @@ XML Clixon has its own implementation of XML and XPATH implementation. The standards covered include: -- [XML](https://www.w3.org/TR/2008/REC-xml-20081126) -- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208) -- [XPATH](https://www.w3.org/TR/xpath-10) +- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126) +- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208) +- [XPATH 1.0](https://www.w3.org/TR/xpath-10) Not supported: -- config", NULL) < 0) goto done; goto ok; } else{ + /* yang spec may be set to anyxml by ingress yang check,...*/ + if (xml_spec(xc) != NULL) + xml_spec_set(xc, NULL); + /* Populate XML with Yang spec (why not do this in parser?) + * Maybe validate xml here as in text_modify_top? + */ if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0) @@ -952,13 +958,16 @@ from_client_msg(clicon_handle h, cxobj *xt = NULL; cxobj *x; cxobj *xe; - char *name = NULL; + char *rpc = NULL; + char *module = NULL; char *db; cbuf *cbret = NULL; /* return message */ int pid; int ret; char *username; - char *nacm_mode; + yang_spec *yspec; + yang_stmt *ye; + yang_stmt *ymod; clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; @@ -974,62 +983,81 @@ from_client_msg(clicon_handle h, goto done; goto reply; } + /* Get yang spec */ + yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */ if ((x = xpath_first(xt, "/rpc")) == NULL){ if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } + /* Populate incoming XML tree with yang */ + if (xml_spec_populate_rpc(h, x, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_rpc(x)) < 0) + goto done; + if (ret == 0){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + goto reply; + } xe = NULL; username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { - name = xml_name(xe); - clicon_debug(1, "%s name:%s", __FUNCTION__, name); - /* Make NACM access control if enabled as "internal"*/ - nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); - if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){ - if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0) + rpc = xml_name(xe); + if ((ye = xml_spec(xe)) == NULL){ + if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0) goto done; - if (!ret) - goto reply; + goto reply; } - if (strcmp(name, "get-config") == 0){ + if ((ymod = ys_module(ye)) == NULL){ + clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); + goto done; + } + module = ymod->ys_argument; + clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); + /* Make NACM access control if enabled as "internal"*/ + if ((ret = nacm_access(h, rpc, module, username, cbret)) < 0) + goto done; + if (ret == 0) + goto reply; + if (strcmp(rpc, "get-config") == 0){ if (from_client_get_config(h, xe, cbret) <0) goto done; } - else if (strcmp(name, "edit-config") == 0){ + else if (strcmp(rpc, "edit-config") == 0){ if (from_client_edit_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "copy-config") == 0){ + else if (strcmp(rpc, "copy-config") == 0){ if (from_client_copy_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "delete-config") == 0){ + else if (strcmp(rpc, "delete-config") == 0){ if (from_client_delete_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "lock") == 0){ + else if (strcmp(rpc, "lock") == 0){ if (from_client_lock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "unlock") == 0){ + else if (strcmp(rpc, "unlock") == 0){ if (from_client_unlock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "get") == 0){ + else if (strcmp(rpc, "get") == 0){ if (from_client_get(h, xe, cbret) < 0) goto done; } - else if (strcmp(name, "close-session") == 0){ + else if (strcmp(rpc, "close-session") == 0){ xmldb_unlock_all(h, pid); stream_ss_delete_all(h, ce_event_cb, (void*)ce); cprintf(cbret, ""); } - else if (strcmp(name, "kill-session") == 0){ + else if (strcmp(rpc, "kill-session") == 0){ if (from_client_kill_session(h, xe, cbret) < 0) goto done; } - else if (strcmp(name, "validate") == 0){ + else if (strcmp(rpc, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; @@ -1038,19 +1066,19 @@ from_client_msg(clicon_handle h, if (from_client_validate(h, db, cbret) < 0) goto done; } - else if (strcmp(name, "commit") == 0){ + else if (strcmp(rpc, "commit") == 0){ if (from_client_commit(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "discard-changes") == 0){ + else if (strcmp(rpc, "discard-changes") == 0){ if (from_client_discard_changes(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "create-subscription") == 0){ + else if (strcmp(rpc, "create-subscription") == 0){ if (from_client_create_subscription(h, xe, ce, cbret) < 0) goto done; } - else if (strcmp(name, "debug") == 0){ + else if (strcmp(rpc, "debug") == 0){ if (from_client_debug(h, xe, cbret) < 0) goto done; } @@ -1104,7 +1132,7 @@ from_client_msg(clicon_handle h, /* Sanity: log if clicon_err() is not called ! */ if (retval < 0 && clicon_errno < 0) clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)", - __FUNCTION__, name?name:""); + __FUNCTION__, rpc?rpc:""); // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index cc51d966..55ee628b 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -95,6 +95,10 @@ process_incoming_packet(clicon_handle h, clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); + if ((cbret = cbuf_new()) == NULL){ + clicon_err(LOG_ERR, errno, "cbuf_new"); + goto done; + } yspec = clicon_dbspec_yang(h); if ((str0 = strdup(cbuf_get(cb))) == NULL){ clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno)); @@ -103,19 +107,25 @@ process_incoming_packet(clicon_handle h, str = str0; /* Parse incoming XML message */ if (xml_parse_string(str, yspec, &xreq) < 0){ - if ((cbret = cbuf_new()) == NULL){ - if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) - goto done; - netconf_output(1, cbret, "rpc-error"); - } - else - clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__); free(str0); + if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); goto done; } free(str0); - if ((xrpc=xpath_first(xreq, "//rpc")) != NULL) + if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ + int ret; isrpc++; + if ((ret = xml_yang_validate_rpc(xrpc)) < 0) + goto done; + if (ret == 0){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); + goto done; + } + } else if (xpath_first(xreq, "//hello") != NULL) ; @@ -134,30 +144,27 @@ process_incoming_packet(clicon_handle h, else{ /* there is a return message in xret */ cxobj *xa, *xa2; assert(xret); - - if ((cbret = cbuf_new()) != NULL){ - if ((xc = xml_child_i(xret,0))!=NULL){ - xa=NULL; - /* Copy message-id attribute from incoming to reply. - * RFC 6241: - * If additional attributes are present in an element, a NETCONF - * peer MUST return them unmodified in the element. This - * includes any "xmlns" attributes. - */ - while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){ - if ((xa2 = xml_dup(xa)) ==NULL) - goto done; - if (xml_addsub(xc, xa2) < 0) - goto done; - } - add_preamble(cbret); - - clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); - add_postamble(cbret); - if (netconf_output(1, cbret, "rpc-reply") < 0){ - cbuf_free(cbret); + if ((xc = xml_child_i(xret,0))!=NULL){ + xa=NULL; + /* Copy message-id attribute from incoming to reply. + * RFC 6241: + * If additional attributes are present in an element, a NETCONF + * peer MUST return them unmodified in the element. This + * includes any "xmlns" attributes. + */ + while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){ + if ((xa2 = xml_dup(xa)) ==NULL) goto done; - } + if (xml_addsub(xc, xa2) < 0) + goto done; + } + add_preamble(cbret); + + clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); + add_postamble(cbret); + if (netconf_output(1, cbret, "rpc-reply") < 0){ + cbuf_free(cbret); + goto done; } } } diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index a8627b66..f53b8675 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -869,6 +869,7 @@ netconf_application_rpc(clicon_handle h, int retval = -1; yang_spec *yspec = NULL; /* application yspec */ yang_stmt *yrpc = NULL; + yang_stmt *ymod = NULL; yang_stmt *yinput; yang_stmt *youtput; cxobj *xoutput; @@ -888,26 +889,33 @@ netconf_application_rpc(clicon_handle h, goto done; } cbuf_reset(cb); - if (xml_namespace(xn) == NULL){ + if (ys_module_by_xml(yspec, xn, &ymod) < 0) + goto done; + if (ymod == NULL){ xml_parse_va(xret, NULL, "" "operation-failed" "rpc" "error" "%s" - "Not recognized" + "Not recognized module" "", xml_name(xn)); goto ok; } - cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); - /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, xml_spec(xn), cbuf_get(cb), Y_RPC, &yrpc) < 0) - goto done; + yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); + if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ + int i; + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL) + break; + } + } /* Check if found */ if (yrpc != NULL){ /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) + if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -933,7 +941,7 @@ netconf_application_rpc(clicon_handle h, if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xoutput, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -954,7 +962,6 @@ netconf_application_rpc(clicon_handle h, return retval; } - /*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions. * Call plugin handler if tag not found. If not handled by any handler, return * error. @@ -985,7 +992,6 @@ netconf_rpc_dispatch(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } - xe = NULL; while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { if (strcmp(xml_name(xe), "get-config") == 0){ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 133f0ed9..42d4d331 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -257,7 +257,6 @@ api_data_get2(clicon_handle h, else{ if (xpath_vec(xret, "%s", &xvec, &xlen, path) < 0) goto done; - clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, (int)xlen); if (use_xml){ for (i=0; iout, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); ok: @@ -408,9 +407,10 @@ api_data_post(clicon_handle h, yang_spec *yspec; cxobj *xa; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ cxobj *xerr = NULL; /* malloced must be freed */ - cxobj *xe; + cxobj *xe; /* dont free */ char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -488,13 +488,22 @@ api_data_post(clicon_handle h, } /* Assume this is validation failed since commit includes validate */ cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -512,6 +521,8 @@ api_data_post(clicon_handle h, xml_free(xerr); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); if (xdata) @@ -623,7 +634,8 @@ api_data_put(clicon_handle h, cxobj *xa; char *api_path; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; char *username; @@ -734,13 +746,23 @@ api_data_put(clicon_handle h, goto ok; } cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -758,6 +780,8 @@ api_data_put(clicon_handle h, xml_free(xerr); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); if (xdata) @@ -791,7 +815,7 @@ api_data_patch(clicon_handle h, return 0; } -/*! Generic REST DELETE method +/*! Generic REST DELETE method translated to edit-config * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) @@ -821,7 +845,8 @@ api_data_delete(clicon_handle h, yang_spec *yspec; enum operation_type op = OP_DELETE; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commmit */ + cxobj *xretdis = NULL; /* return from discard */ cxobj *xerr = NULL; char *username; @@ -836,7 +861,6 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) @@ -864,13 +888,22 @@ api_data_delete(clicon_handle h, } /* Assume this is validation failed since commit includes validate */ cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", NACM_RECOVERY_USER); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; @@ -887,6 +920,8 @@ api_data_delete(clicon_handle h, xml_free(xret); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -912,6 +947,11 @@ api_data_delete(clicon_handle h, * data-model-specific RPC operations supported by the server. The * server MAY omit this resource if no data-model-specific RPC * operations are advertised. + * From ietf-restconf.yang: + * In XML, the YANG module namespace identifies the module: + * + * In JSON, the YANG module name identifies the module: + * { 'ietf-system:system-restart' : [null] } */ int api_operations_get(clicon_handle h, @@ -926,9 +966,9 @@ api_operations_get(clicon_handle h, { int retval = -1; yang_spec *yspec; - yang_stmt *ym; + yang_stmt *ymod; /* yang module */ yang_stmt *yc; - char *modname; + char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; @@ -937,18 +977,17 @@ api_operations_get(clicon_handle h, if ((cbx = cbuf_new()) == NULL) goto done; cprintf(cbx, ""); - ym = NULL; - while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { - modname = ym->ys_argument; + ymod = NULL; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + namespace = yang_find_mynamespace(ymod); yc = NULL; - while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { + while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s:%s />", modname, yc->ys_argument); + cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); } } cprintf(cbx, ""); - clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx)); if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) goto done; if (xml_rootchild(xt, 0, &xt) < 0) @@ -962,7 +1001,6 @@ api_operations_get(clicon_handle h, if (xml2json_cbuf(cbx, xt, pretty) < 0) goto done; } - clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); @@ -1095,18 +1133,9 @@ api_operations_post(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } - /* XXX: something strange for rpc user */ if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; -#if 0 - { - cbuf *c = cbuf_new(); - clicon_xml2cbuf(c, xtop, 0, 0); - clicon_debug(1, "%s xtop:%s", __FUNCTION__, cbuf_get(c)); - cbuf_free(c); - } -#endif if (data && strlen(data)){ /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -1150,7 +1179,8 @@ api_operations_post(clicon_handle h, } if (yinput){ xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yinput) < 0) + /* XXX yinput <-> h ?*/ + if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xbot, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -1215,7 +1245,7 @@ api_operations_post(clicon_handle h, #endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xoutput, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -1235,9 +1265,6 @@ api_operations_post(clicon_handle h, else if (xml2json_cbuf(cbx, xoutput, pretty) < 0) goto done; -#if 1 - clicon_debug(1, "%s cbx:%s", __FUNCTION__, cbuf_get(cbx)); -#endif FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); } diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 98bc8a0e..6943dec5 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -49,7 +49,6 @@ INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ with_restconf = @with_restconf@ -with_keyvalue = @with_keyvalue@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -117,5 +116,5 @@ distclean: clean for i in $(SUBDIRS); \ do (cd $$i; $(MAKE) $(MFLAGS) $@); done -tags: +TAGS: find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/datastore/README.md b/datastore/README.md index a3283c6e..8f000d88 100644 --- a/datastore/README.md +++ b/datastore/README.md @@ -2,8 +2,7 @@ The Clixon datastore is a stand-alone XML based datastore. The idea is to be able to use different datastores backends with the same -API. There is currently a key-value plugin based on qdbm and a plain -text-file datastore. +API. There is currently only a plain text-file datastore. The datastore is primarily designed to be used by Clixon but can be used separately. @@ -38,7 +37,7 @@ int xmldb_create(clicon_handle h, char *db); To use the API, a client needs the following: - A clicon handle. -- A datastore plugin, such as a text.so or keyvalue.so. These are normally built and installed at Clixon make. +- A datastore plugin, such as a text.so. These are normally built and installed at Clixon make. - A directory where to store databases - A yang specification. This needs to be parsed using the Clixon yang_parse() method. diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 5722d497..63f1e7a0 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -31,14 +31,6 @@ ***** END LICENSE BLOCK ***** - * Examples: - -./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get / - -sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' - -sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / 'eth0true' - */ #ifdef HAVE_CONFIG_H @@ -247,6 +239,7 @@ main(int argc, char **argv) clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]); usage(argv0); } + _CLICON_XML_NS_ITERATE = 1; if (xml_parse_string(argv[2], NULL, &xt) < 0) goto done; if (xml_rootchild(xt, 0, &xt) < 0) diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in deleted file mode 100644 index 08fc997f..00000000 --- a/datastore/keyvalue/Makefile.in +++ /dev/null @@ -1,98 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# -VPATH = @srcdir@ -prefix = @prefix@ -datarootdir = @datarootdir@ -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -exec_prefix = @exec_prefix@ -bindir = @bindir@ -libdir = @libdir@ -dbdir = @prefix@/db -mandir = @mandir@ -libexecdir = @libexecdir@ -localstatedir = @localstatedir@ -sysconfdir = @sysconfdir@ - -VPATH = @srcdir@ -CC = @CC@ -CFLAGS = @CFLAGS@ -rdynamic -fPIC -INSTALLFLAGS = @INSTALLFLAGS@ -LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ -DATASTORE = keyvalue -CPPFLAGS = @CPPFLAGS@ - -INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ - -PLUGIN = $(DATASTORE).so - -SRC = clixon_keyvalue.c clixon_qdb.c clixon_chunk.c - -OBJS = $(SRC:.c=.o) - -all: $(PLUGIN) - - -$(PLUGIN): $(SRC) - $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS) - -clean: - rm -f $(PLUGIN) $(OBJS) *.core - -distclean: clean - rm -f Makefile *~ .depend - -.SUFFIXES: -.SUFFIXES: .c .o - -.c.o: $(SRC) - $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< - -install: $(PLUGIN) - install -d -m 0755 $(DESTDIR)$(libdir)/xmldb - install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb - -install-include: - -uninstall: - rm -rf $(DESTDIR)$(libdir)/xmldb/$(PLUGIN) - -TAGS: - find . -name '*.[chyl]' -print | etags - - -depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - -#include .depend - diff --git a/datastore/keyvalue/clixon_chunk.c b/datastore/keyvalue/clixon_chunk.c deleted file mode 100644 index 80db76da..00000000 --- a/datastore/keyvalue/clixon_chunk.c +++ /dev/null @@ -1,794 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, indicate - your decision by deleting the provisions above and replace them with the - notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - */ -/* Error handling: dont use clicon_err, treat as unix system calls. That is, - ensure errno is set and return -1/NULL */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* clicon */ -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" - -/* - * The chunk head array for the predefined chunk sizes. - */ -static chunk_head_t chunk_heads[CHUNK_HEADS]; - -/* - * Did we initialize the chunk heads yet? - */ -static int chunk_initialized = 0; - - -/* - * The pagesize of this system - */ -static int chunk_pagesz; - - -/* - * List of chunk groups - */ -static chunk_group_t *chunk_grp; - -/* - * Hack to tell unchunk() not to remove chunk_group if empty - */ -static int dont_unchunk_group; - - -/* - * Initialize chunk library - */ -static void -chunk_initialize () -{ - int pgs; - register int idx; - - chunk_pagesz = getpagesize(); - - bzero (&chunk_heads, sizeof(chunk_heads)); - - for (idx = 0; idx < CHUNK_HEADS; idx++) { - chunk_head_t *chead = &chunk_heads[idx]; - - - /* - * Calculate the size of a block - */ - pgs = (0x01lu << (CHUNK_BASE + idx)) / chunk_pagesz; - if (pgs == 0) - pgs = 1; - chead->ch_blksz = pgs * chunk_pagesz; - - - /* - * Chunks per block is 1 for all size above a page. For sizes - * (including the chunk header) less than a page it's as many - * as fits - */ - chead->ch_nchkperblk = chead->ch_blksz / (0x01lu << (CHUNK_BASE + idx)); - - - /* - * Size of each chunk is: - * (size + chnkhdr) * ncnkperblk = blksiz - blkhdr - */ - chead->ch_size = - (chead->ch_blksz / chead->ch_nchkperblk) - - sizeof(chunk_t); - - } - - /* Zero misc variables */ - chunk_grp = NULL; - dont_unchunk_group = 0; - - chunk_initialized = 1; -} - - -/* - * chunk_new_block() - Allocate new block, initialize it and it's chunks. - */ -static int -chunk_new_block (chunk_head_t *chead) -{ - register int idx; - register char *c; - chunk_block_t *blk; - chunk_t *cnk; - - /* Map block header mem */ - blk = (chunk_block_t *) - mmap(NULL, sizeof(chunk_block_t), - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (blk == MAP_FAILED) - return -1; - memset((void *)blk, 0, sizeof(*blk)); - - /* Allocate chunk block */ - blk->cb_blk = (void *) - mmap(NULL, chead->ch_blksz, - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (blk->cb_blk == MAP_FAILED) { - munmap(blk, chead->ch_blksz); - return -1; - } - memset(blk->cb_blk, 0, chead->ch_blksz); - - - /* Initialize chunk header */ - blk->cb_head = chead; - INSQ(blk, chead->ch_blks); - chead->ch_nblks++; - - /* Initialize chunks */ - c = ((char *)blk->cb_blk); - for (idx = 0; idx < chead->ch_nchkperblk; idx++) { - - cnk = (chunk_t *)c; - cnk->c_blk = blk; - INSQ(cnk, chead->ch_free); - chead->ch_nfree++; - - c += (chead->ch_size + sizeof(chunk_t)); - } - - - return 0; -} - -/* - * chunk_release_block() - Unqueue a block, it's chunks and free mem - */ -static void -chunk_release_block(chunk_block_t *cblk) -{ - int idx; - char *c; - chunk_t *cnk; - chunk_head_t *chead; - - - chead = cblk->cb_head; - - /* - * Dequeue block - */ - DELQ(cblk, chead->ch_blks, chunk_block_t *); - chead->ch_nblks--; - - /* - * Dequeue all chunks in the block - */ - c = (char *)cblk->cb_blk; - for (idx = 0; idx < chead->ch_nchkperblk; idx++) { - - cnk = (chunk_t *)c; - DELQ(cnk, chead->ch_free, chunk_t *); - chead->ch_nfree--; - - c += (chead->ch_size + sizeof(chunk_t)); - } - - /* - * Free block - */ - munmap((void *)cblk->cb_blk, chead->ch_blksz); - munmap((void *)cblk, sizeof(*cblk)); -} - - - -/* - * chunk_alloc() - Map new chunk of memory - */ -static void * -chunk_alloc(size_t len) -{ - register int idx; - chunk_head_t *chead; - chunk_t *cnk; - - - if (!len) - return (void *)NULL; - - - - /* Find sufficient sized block head */ - for (idx = 0; idx < CHUNK_HEADS; idx++) - if (chunk_heads[idx].ch_size >= len) - break; - - /* Too large chunk? */ - if (idx >= CHUNK_HEADS) { - errno = ENOMEM; - return (void *)NULL; - } - - chead = &chunk_heads[idx]; - - - /* Get new block if necessary */ - if (!chead->ch_nfree) - if (chunk_new_block(chead)) - return (void *)NULL; - - - /* Move a free chunk to the in-use list */ - cnk = chead->ch_free; - DELQ(cnk, chead->ch_free, chunk_t *); - chead->ch_nfree--; - INSQ(cnk, chead->ch_cnks); - /* Add reference to the corresponding block */ - cnk->c_blk->cb_ref++; - -#ifdef CHUNK_DIAG - /* Clear diag info */ - bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag)); -#endif /* CHUNK_DIAG */ - - /* Return pointer to first byte after the chunk header */ - return (void *) (cnk + 1); -} - - -/* - * chunk() - Map new chunk of memory in group - */ -void * -#ifdef CHUNK_DIAG -_chunk(size_t len, const char *name, const char *file, int line) -#else -chunk(size_t len, const char *name) -#endif -{ - int newgrp = 0; - void *ptr = NULL; - chunk_t *cnk; - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_grpent_t *ent = NULL; - - /* Make sure chunk_heads are initialized */ - if (!chunk_initialized) - chunk_initialize(); - - if (!len) - return (void *)NULL; - - /* Get actual chunk - */ - ptr = chunk_alloc(len); - if (!ptr) - goto error; - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - -#ifdef CHUNK_DIAG - /* Store who reuested us - */ - cnk->c_diag.cd_file = file; - cnk->c_diag.cd_line = line; -#endif /* CHUNK_DIAG */ - - /* No name given. Get an ungrouped chunk - */ - if (!name) - return ptr; - - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - - /* New group. - */ - if ( !grp ) { - - grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t)); - if (!grp) - goto error; - - bzero(grp, sizeof(chunk_group_t)); - - grp->cg_name = (char *) chunk_alloc(strlen(name) + 1); - if (!grp->cg_name) - goto error; - bcopy(name, grp->cg_name, strlen(name)+1); - newgrp = 1; - } - - - /* Get new entry. - */ - ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t)); - if (!ent) - goto error; - bzero(ent, sizeof(chunk_grpent_t)); - - /* Now put everything together - */ - cnk->c_grpent = ent; - - ent->ce_cnk = cnk; - ent->ce_grp = grp; - - INSQ(ent, grp->cg_ent); - if (newgrp) - INSQ(grp, chunk_grp); - - return (ptr); - - error: - if (grp && newgrp) { - if (grp->cg_name) - unchunk(grp->cg_name); - unchunk(grp); - } - if (ent) - unchunk(ent); - if (ptr) - unchunk(ptr); - - return (void *) NULL; -} - - -/* - * rechunk() - Resize previously allocated chunk. - */ -void * -#ifdef CHUNK_DIAG -_rechunk(void *ptr, size_t len, const char *name, const char *file, int line) -#else -rechunk(void *ptr, size_t len, const char *name) -#endif -{ - int idx; - void *new; - chunk_t *cnk; - chunk_t *newcnk; - chunk_head_t *chead; - chunk_head_t *newchead; - - - /* No previoud chunk, get new - */ - if (!ptr) { -#ifdef CHUNK_DIAG - return _chunk(len, name, file, line); -#else - return chunk(len, name); -#endif - } - - /* Zero length, free chunk - */ - if (len == 0) { - unchunk(ptr); - return (void *) NULL; - } - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - chead = cnk->c_blk->cb_head; - - /* Find sufficient sized block head - */ - for (idx = 0; idx < CHUNK_HEADS; idx++) - if (chunk_heads[idx].ch_size >= len) - break; - /* Too large chunk? */ - if (idx >= CHUNK_HEADS) { - errno = ENOMEM; - return (void *)NULL; - } - - /* Check if chunk size remains unchanged - */ - if (chunk_heads[idx].ch_size == chead->ch_size) - return (ptr); - - /* Get new chunk - */ -#ifdef CHUNK_DIAG - new = _chunk(len, name, file, line); -#else - new = chunk(len, name); -#endif - if (!new) - return (void *) NULL; - newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t)); - newchead = newcnk->c_blk->cb_head; - - /* Copy contents to new chunk - */ - bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size)); - - /* Free old chunk - */ - unchunk(ptr); - - - return (new); -} - -/* - * unchunk() - Release chunk - */ -void -unchunk(void *ptr) -{ - chunk_t *cnk; - chunk_head_t *chead; - chunk_block_t *cblk; - chunk_grpent_t *ent; - chunk_group_t *grp; - - if (!chunk_initialized) - return; - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - cblk = cnk->c_blk; - chead = cblk->cb_head; - - /* Move chunk back to free list - */ - DELQ(cnk, chead->ch_cnks, chunk_t *); - INSQ(cnk, chead->ch_free); - chead->ch_nfree++; - - /* If chunk is grouped, remove from group. - */ - ent = cnk->c_grpent; - if (ent) { - grp = ent->ce_grp; - DELQ(ent, grp->cg_ent, chunk_grpent_t *); - unchunk(ent); - cnk->c_grpent = NULL; - - /* Group empty? */ - if (!dont_unchunk_group && !grp->cg_ent) { - DELQ(grp, chunk_grp, chunk_group_t *); - unchunk(grp->cg_name); - unchunk(grp); - } - } - - /* Check block refs is nil, if so free it - */ - cblk->cb_ref--; - if (cblk->cb_ref == 0) - chunk_release_block (cblk); -} - - -/* - * unchunk_group() - Release all group chunks. - */ -void -unchunk_group(const char *name) -{ - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_t *cnk; - - if (!chunk_initialized) - return; - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - if (!grp) - return; - - - /* Walk through all chunks in group an free them - */ - dont_unchunk_group = 1; - while (grp->cg_ent) { - cnk = grp->cg_ent->ce_cnk; - unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t))); - } - dont_unchunk_group = 0; - - - /* Remove group from list and free it - */ - DELQ(grp, chunk_grp, chunk_group_t *); - unchunk(grp->cg_name); - unchunk(grp); -} - -/* - * chunkdup() - Copy block of data to a new chunk of memory in group - */ -void * -#ifdef CHUNK_DIAG -_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line) -#else -chunkdup(const void *ptr, size_t len, const char *name) -#endif -{ - void *new; - - /* No input data or no length - */ - if (!ptr || len <= 0) - return (void *)NULL; - - /* Get new chunk - */ -#ifdef CHUNK_DIAG - new = _chunk(len, name, file, line); -#else - new = chunk(len, name); -#endif - if (!new) - return (void *)NULL; - - /* Copy data to new chunk - */ - memcpy(new, ptr, len); - - return (new); -} - -/* - * chunksize() - Return size of memory chunk. - */ -size_t -chunksize(void *ptr) -{ - chunk_t *cnk; - chunk_head_t *chead; - chunk_block_t *cblk; - - if (!chunk_initialized) - return -1; - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - cblk = cnk->c_blk; - chead = cblk->cb_head; - - return chead->ch_size; -} - -/* - * chunk_strncat() - Concatenate 'n' characters to a chunk allocated string. If - * 'n' is zero, do the whole src string. - * - */ -char * -#ifdef CHUNK_DIAG -_chunk_strncat(const char *dst, const char *src, size_t n, const char *name, - const char *file, int line) -#else -chunk_strncat(const char *dst, const char *src, size_t n, const char *name) -#endif -{ - size_t len; - char *new; - void *ptr = (void *)dst; - - if (n == 0) /* zero length means cat whole string */ - n = strlen(src); - len = (dst ? strlen(dst) : 0) + n + 1; -#ifdef CHUNK_DIAG - ptr = _rechunk(ptr, len, name, file, line); -#else - ptr = rechunk(ptr, len, name); -#endif - if (ptr == NULL) - return NULL; - - new = (char *)ptr; - new += strlen(new); - while (n-- > 0 && *src) - *new++ = *src++; - *new = '\0'; - - return (char *)ptr; -} - -/* - * chunk_sprintf() - Format string into new chunk. - */ -char * -#ifdef CHUNK_DIAG -_chunk_sprintf(const char *name, const char *file, - int line, const char *fmt, ...) -#else -chunk_sprintf(const char *name, char *fmt, ...) -#endif -{ - size_t len; - char *str; - va_list args; - - /* Calculate formatted string length */ - va_start(args, fmt); - len = vsnprintf(NULL, 0, fmt, args) + 1; - va_end(args); - - /* get chunk */ -#ifdef CHUNK_DIAG - str = _chunk(len, name, file, line); -#else - str = chunk(len, name); -#endif - if (str == NULL) - return NULL; - - /* Format string */ - va_start(args, fmt); - len = vsnprintf(str, len, fmt, args); - va_end(args); - - return str; -} - -#ifdef CHUNK_DIAG -/* - * chunk_check() - Report all non-freed chunk for given group (if any) - */ -void -chunk_check(FILE *fout, const char *name) -{ - int idx; - chunk_t *cnk; - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_grpent_t *ent; - - if (!chunk_initialized) - return; - /* No name given, walk through everything - */ - if (name == (const char *)NULL) { - for (idx = 0; idx < CHUNK_HEADS; idx++) { - chunk_head_t *chead = &chunk_heads[idx]; - cnk = chead->ch_cnks; - if (cnk == (chunk_t *)NULL) - continue; - - do { - - /* If no file name it's an internal chunk */ - if (cnk->c_diag.cd_file) - clicon_debug(0, - "%s:%d,\t%zu bytes (%p), group \"%s\"\n", - cnk->c_diag.cd_file, - cnk->c_diag.cd_line, - cnk->c_blk->cb_head->ch_size, - (cnk +1), - cnk->c_grpent ? - cnk->c_grpent->ce_grp->cg_name : - "NULL"); - - cnk = NEXTQ(chunk_t *, cnk); - - } while (cnk != chead->ch_cnks); - } - } - - /* Walk through group - */ - else { - - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - if (!grp) - return; - - ent = grp->cg_ent; - do { - cnk = ent->ce_cnk; - - fprintf(fout ? fout : stdout, - "%s:%d,\t%zu bytes (%p), group \"%s\"\n", - cnk->c_diag.cd_file, - cnk->c_diag.cd_line, - cnk->c_blk->cb_head->ch_size, - (cnk +1), - cnk->c_grpent ? - cnk->c_grpent->ce_grp->cg_name : - "NULL"); - - ent = NEXTQ(chunk_grpent_t *, ent); - } while (ent != grp->cg_ent); - } -} -#else /* CHUNK_DIAG */ -void -chunk_check(FILE *fout, const char *name) -{ -} -#endif /* CHUNK_DIAG */ diff --git a/datastore/keyvalue/clixon_chunk.h b/datastore/keyvalue/clixon_chunk.h deleted file mode 100644 index 81a8f18b..00000000 --- a/datastore/keyvalue/clixon_chunk.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * - */ - -#ifndef _CLIXON_CHUNK_H_ -#define _CLIXON_CHUNK_H_ - - -/* - * Compile with chunk diagnostics. XXX Should be in Makefile.in ?? - */ -#undef CHUNK_DIAG - -/* - * Base number of bits to shift getting the size of a chunk_head. - */ -#define CHUNK_BASE 6 - -/* - * Number of predefined chunk sizes. I.e. the number of chunk heads in the - * chunk_heads vector. - */ -#define CHUNK_HEADS (32 - CHUNK_BASE) - - -#ifdef CHUNK_DIAG -/* - * Chunk diagnostics - */ -typedef struct _chunk_diag_t { - const char *cd_file; /* File which requested chunk */ - - int cd_line; /* Line in requesting file */ - -} chunk_diag_t; -#endif /* CHUNK_DIAG */ - -/* - * The block header. - */ -struct _chunk_head_t; -typedef struct _chunk_head_t chunk_head_t; - -typedef struct _chunk_block_t { - qelem_t cb_qelem; /* Circular queue of blocks */ - - chunk_head_t *cb_head; /* The chunk head I belong to */ - - void *cb_blk; /* Allocated memory block */ - - uint16_t cb_ref; /* Number of used chunks of block */ - -} chunk_block_t; - - -/* - * The chunk header. - */ -struct _chunk_grpent_t; -typedef struct _chunk_grpent_t chunk_grpent_t; -typedef struct _chunk_t { - qelem_t c_qelem; /* Circular queue of chunks */ - - chunk_block_t *c_blk; /* The block I belong to */ - -#ifdef CHUNK_DIAG - chunk_diag_t c_diag; /* The diagnostics structure */ -#endif /* CHUNK_DIAG */ - - chunk_grpent_t *c_grpent; -} chunk_t; - -/* - * The head of a chunk size. Each predefined size has it's own head keeping - * track of all blocks and chunks for the size. - */ -struct _chunk_head_t { - size_t ch_size; /* Chunk size */ - int ch_nchkperblk; /* Number pf chunks per block */ - - size_t ch_blksz; /* Size of a block */ - int ch_nblks; /* Number of allocated blocks */ - - chunk_block_t *ch_blks; /* Circular list of blocks */ - - chunk_t *ch_cnks; /* Circular list of chunks in use */ - - size_t ch_nfree; /* Number of free chunks */ - chunk_t *ch_free; /* Circular list of free chunks */ - -}; - - -/* - * The chunk group structure. - */ -typedef struct _chunk_group_t { - qelem_t cg_qelem; /* List of chunk groups */ - - char *cg_name; /* Name of group */ - - chunk_grpent_t *cg_ent; /* List of chunks in the group */ - -} chunk_group_t; - - -/* - * The chunk group entry structure. - */ -struct _chunk_grpent_t { - qelem_t ce_qelem; /* Circular list of entries */ - - chunk_group_t *ce_grp; /* The group I belong to */ - - chunk_t *ce_cnk; /* Pointer to the chunk */ -}; - -/* - * Public function declarations - */ -#ifdef CHUNK_DIAG -void *_chunk (size_t, const char *, const char *, int); -#define chunk(siz,label) _chunk((siz),(label),__FILE__,__LINE__) -void *_rechunk (void *, size_t, const char *, const char *, int); -#define rechunk(ptr,siz,label) _rechunk((ptr),(siz),(label),__FILE__,__LINE__) -void *_chunkdup (const void *, size_t, const char *, const char *, int); -#define chunkdup(ptr,siz,label) _chunkdup((ptr),(siz),(label),__FILE__,__LINE__) -char *_chunk_strncat (const char *, const char *, size_t, const char *, const char *, int); -#define chunk_strncat(str,new,n,label) _chunk_strncat((str),(new),(n),(label),__FILE__,__LINE__) -char *_chunk_sprintf (const char *, const char *, int, const char *, ...); -#define chunk_sprintf(label,fmt,...) _chunk_sprintf((label),__FILE__,__LINE__,(fmt),__VA_ARGS__) -#else /* CHUNK_DIAG */ -void *chunk (size_t, const char *); -void *rechunk (void *, size_t, const char *); -void *chunkdup (const void *, size_t, const char *); -char *chunk_strncat (const char *, const char *, size_t, const char *); -char *chunk_sprintf (const char *, char *, ...); -#endif /* CHUNK_DIAG */ -void unchunk (void *); -void unchunk_group (const char *); -void chunk_check (FILE *, const char *); -size_t chunksize (void *); - - -#endif /* _CLIXON_CHUNK_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c deleted file mode 100644 index 39a11134..00000000 --- a/datastore/keyvalue/clixon_keyvalue.c +++ /dev/null @@ -1,1130 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - */ -/* - * An xml database consists of key-value pairs for xml-trees. - * Each node in an xml-tree has a key and an optional value. - * The key (xmlkey) is constructed from the xml node name concatenated - * with its ancestors and any eventual list keys. - * A xmlkeyfmt is a help-structure used when accessing the XML database. - * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) - * Example: /aaa/bbb/%s/%s/ccc - * Such an xmlkeyfmt can be obtained from a yang-statement by following - * its ancestors to the root module. If one of the ancestors is a list, - * a wildchar (%s) is inserted for each key. - * These xmlkeyfmt keys are saved and used in cli callbacks such as when - * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols - * In this case, the variables are set and the wildcards can be instantiated. - * An xml tree can then be formed that can be used to the xmldb_get() or - * xmldb_put() functions. - * The relations between the functions and formats are as follows: - * - * +-----------------+ +-----------------+ - * | yang-stmt | yang2api_path_fmt | api_path_fmt | api_path_fmt2xpath - * | list aa,leaf k | ----------------->| /aa=%s |----------------> - * +-----------------+ +-----------------+ - * | - * | api_path_fmt2api_path - * | k=17 - * v - * +-------------------+ +-----------------+ - * | xml-tree/cxobj | xmlkey2xml |api_path RFC3986| - * | 17| <------------- | /aa=17 | - * +-------------------+ +-----------------+ - * - * Alternative for xmlkeyfmt would be eg: - * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) - * XPATH: /interfaces/interface[name='%s']/ipv4/address/[ip'=%s'] - * - * Paths through the code (for coverage) - * cli_callback_generate +----------------+ - * cli_expand_var_generate | yang2api_path_fmt | - * yang -------------> | | - * +----------------+ - * dependency on clixon handle: - * clixon_xmldb_dir() - * clicon_dbspec_yang(h) - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" -#include "clixon_qdb.h" -#include "clixon_keyvalue.h" - -#define handle(xh) (assert(kv_handle_check(xh)==0),(struct kv_handle *)(xh)) - -/* Magic to ensure plugin sanity. */ -#define KV_HANDLE_MAGIC 0xfa61a402 - -/*! Internal structure of keyvalue datastore handle. - */ -struct kv_handle { - int kh_magic; /* magic */ - char *kh_dbdir; /* Directory of database files */ - yang_spec *kh_yangspec; /* Yang spec if this datastore */ -}; - -/*! Check struct magic number for sanity checks - * return 0 if OK, -1 if fail. - */ -static int -kv_handle_check(xmldb_handle xh) -{ - /* Dont use handle macro to avoid recursion */ - struct kv_handle *kh = (struct kv_handle *)(xh); - - return kh->kh_magic == KV_HANDLE_MAGIC ? 0 : -1; -} - -/*! Database locking for candidate and running non-persistent - * Store an integer for running and candidate containing - * the session-id of the client holding the lock. - */ -static int _running_locked = 0; -static int _candidate_locked = 0; -static int _startup_locked = 0; - -/*! Translate from symbolic database name to actual filename in file-system - * @param[in] xh XMLDB handle - * @param[in] db Symbolic database name, eg "candidate", "running" - * @param[out] filename Filename. Unallocate after use with free() - * @retval 0 OK - * @retval -1 Error - * @note Could need a way to extend which databases exists, eg to register new. - * The currently allowed databases are: - * candidate, tmp, running, result - * The filename reside in CLICON_XMLDB_DIR option - */ -static int -kv_db2file(struct kv_handle *kh, - const char *db, - char **filename) -{ - int retval = -1; - cbuf *cb; - char *dir; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - if ((dir = kh->kh_dbdir) == NULL){ - clicon_err(OE_XML, errno, "dbdir not set"); - goto done; - } - if (strcmp(db, "running") != 0 && - strcmp(db, "candidate") != 0 && - strcmp(db, "startup") != 0 && - strcmp(db, "tmp") != 0){ - clicon_err(OE_XML, 0, "No such database: %s", db); - goto done; - } - cprintf(cb, "%s/%s_db", dir, db); - if ((*filename = strdup4(cbuf_get(cb))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - -/*! Help function to append key values from an xml list to a cbuf - * Example, a yang node x with keys a and b results in "x/a/b" - */ -static int -append_listkeys(cbuf *ckey, - cxobj *xt, - yang_stmt *ys) -{ - int retval = -1; - yang_stmt *ykey; - cxobj *xkey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *bodyenc; - int i=0; - - cvk = ys->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - /* Iterate over individual keys */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((xkey = xml_find(xt, keyname)) == NULL){ - clicon_err(OE_XML, errno, "XML list node \"%s\" does not have key \"%s\" child", - xml_name(xt), keyname); - goto done; - } - if (uri_percent_encode(&bodyenc, "%s", xml_body(xkey)) < 0) - goto done; - if (i++) - cprintf(ckey, ","); - else - cprintf(ckey, "="); - cprintf(ckey, "%s", bodyenc); - free(bodyenc); - bodyenc = NULL; - } - retval = 0; - done: - return retval; -} - -/*! Help function to create xml key values - * @param[in,out] x Parent - * @param[in] ykey - * @param[in] arg - * @param[in] keyname yang key name - */ -static int -create_keyvalues(cxobj *x, - yang_stmt *ykey, - char *arg, - char *keyname) -{ - int retval = -1; - cxobj *xn; - cxobj *xb; - - /* Check if key node exists */ - if ((xn = xml_new_spec(keyname, x, ykey)) == NULL) - goto done; - if ((xb = xml_new("body", xn)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - xml_value_set(xb, arg); - retval = 0; - done: - return retval; -} - - -/*! - * @param[in] xk xmlkey - * @param[out] xt XML tree as result - * XXX cannot handle top-level list - */ -static int -get(char *dbname, - yang_spec *ys, - char *xk, - char *val, - cxobj *xt) -{ - int retval = -1; - char **vec = NULL; - int nvec; - char **valvec = NULL; - int nvalvec; - int i; - int j; - char *name; - char *restval; - yang_stmt *y; - cxobj *x; - cxobj *xc; - cxobj *xb; - yang_stmt *ykey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *arg; - char *argdec; - cbuf *cb; - - // clicon_debug(1, "%s xkey:%s val:%s", __FUNCTION__, xk, val); - x = xt; - if (xk == NULL || *xk!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", xk); - goto done; - } - if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) - goto done; - /* Element 0 is NULL '/', - Element 1 is top symbol and needs to find subs in all modules: - spec->module->syntaxnode - */ - if (nvec < 2){ - clicon_err(OE_XML, 0, "Malformed key: %s", xk); - goto done; - } - i = 1; - while (i name:x restval=1,2 */ - if ((restval = index(name, '=')) != NULL){ - *restval = '\0'; - restval++; - } - if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - } - else - if ((y = yang_find_datanode((yang_node*)y, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - switch (y->ys_keyword){ - case Y_LEAF_LIST: - /* - * If xml element is a leaf-list, then the next element is expected to - * be a value - */ - if (uri_percent_decode(&argdec, restval) < 0) - goto done; - if ((xc = xml_find(x, name))==NULL || - (xb = xml_find(xc, argdec))==NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - /* Assume body is created at end of function */ - } - free(argdec); - argdec = NULL; - break; - case Y_LIST: - /* - * If xml element is a list, then the next element(s) is expected to be - * a key value. Check if this key value is already in the xml tree, - * otherwise create it. - */ - if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, y->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cvi = NULL; - /* Iterate over individual yang keys */ - cprintf(cb, "%s", name); - if (valvec) - free(valvec); - if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) - goto done; - if (cvec_len(cvk)!=nvalvec){ - retval = 0; - goto done; - } - j = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - if (j>=nvalvec) - break; - arg = valvec[j++]; - if (uri_percent_decode(arg, &argdec) < 0) - goto done; - cprintf(cb, "[%s='%s']", cv_string_get(cvi), argdec); - free(argdec); - argdec=NULL; - } - if ((xc = xpath_first(x, cbuf_get(cb))) == NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - cvi = NULL; - // i -= cvec_len(cvk); - /* Iterate over individual yang keys */ - j=0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - if (j>=nvalvec) - break; - arg = valvec[j++]; - keyname = cv_string_get(cvi); - if (uri_percent_decode(arg, &argdec) < 0) - goto done; - if (create_keyvalues(xc, - ykey, - argdec, - keyname) < 0) - goto done; - free(argdec); - argdec = NULL; - } /* while */ - } - if (cb){ - cbuf_free(cb); - cb = NULL; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - break; - case Y_LEAF: - case Y_CONTAINER: - default: - if ((xc = xml_find(x, name))==NULL) - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - break; - } /* switch */ - x = xc; - i++; - } - if (val && xml_body(x)==NULL){ - if ((x = xml_new("body", x)) == NULL) - goto done; - xml_type_set(x, CX_BODY); - xml_value_set(x, val); - } - if(debug>1){ - fprintf(stderr, "%s %s\n", __FUNCTION__, xk); - clicon_xml2file(stderr, xt, 0, 1); - } - retval = 0; - done: - if (vec) - free(vec); - if (valvec) - free(valvec); - if (cvk) - cvec_free(cvk); - return retval; -} - -/*! Connect to a datastore plugin - * @retval handle Use this handle for other API calls - * @retval NULL Error - * @note You can do several connects, and have multiple connections to the same - * datastore - */ -xmldb_handle -kv_connect(void) -{ - struct kv_handle *kh; - xmldb_handle xh = NULL; - int size; - - size = sizeof(struct kv_handle); - if ((kh = malloc(size)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(kh, 0, size); - kh->kh_magic = KV_HANDLE_MAGIC; - xh = (xmldb_handle)kh; - done: - return xh; -} - -/*! Disconnect from a datastore plugin and deallocate handle - * @param[in] handle Disconect and deallocate from this handle - * @retval 0 OK - */ -int -kv_disconnect(xmldb_handle xh) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (kh){ - if (kh->kh_dbdir) - free(kh->kh_dbdir); - free(kh); - } - retval = 0; - // done: - return retval; -} - -/*! Get value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle - * @param[in] optname Option name - * @param[out] value Pointer to Value of option - * @retval 0 OK - * @retval -1 Error - */ -int -kv_getopt(xmldb_handle xh, - char *optname, - void **value) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (strcmp(optname, "yangspec") == 0) - *value = kh->kh_yangspec; - else if (strcmp(optname, "dbdir") == 0) - *value = kh->kh_dbdir; - else{ - clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Set value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle - * @param[in] optname Option name - * @param[in] value Value of option - * @retval 0 OK - * @retval -1 Error - */ -int -kv_setopt(xmldb_handle xh, - char *optname, - void *value) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (strcmp(optname, "yangspec") == 0) - kh->kh_yangspec = (yang_spec*)value; - else if (strcmp(optname, "dbdir") == 0){ - if (value && (kh->kh_dbdir = strdup((char*)value)) == NULL){ - clicon_err(OE_UNIX, 0, "strdup"); - goto done; - } - } - else{ - clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Get content of database using xpath. return a set of matching sub-trees - * The function returns a minimal tree that includes all sub-trees that match - * xpath. - * This is a clixon datastore plugin of the the xmldb api - * @see xmldb_get - */ -int -kv_get(xmldb_handle xh, - const char *db, - char *xpath, - int config, - cxobj **xtop) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - yang_spec *yspec; - char *dbfile = NULL; - cxobj **xvec = NULL; - size_t xlen; - int i; - int npairs; - struct db_pair *pairs; - cxobj *xt = NULL; - - clicon_debug(2, "%s", __FUNCTION__); - if (kv_db2file(kh, db, &dbfile) < 0) - goto done; - if (dbfile==NULL){ - clicon_err(OE_XML, 0, "dbfile NULL"); - goto done; - } - if ((yspec = kh->kh_yangspec) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - /* Read in complete database (this can be optimized) */ - if ((npairs = db_regexp(dbfile, "", __FUNCTION__, &pairs, 0)) < 0) - goto done; - if ((xt = xml_new_spec("config", NULL, yspec)) == NULL) - goto done; - /* Translate to complete xml tree */ - for (i = 0; i < npairs; i++) { - if (get(dbfile, - yspec, - pairs[i].dp_key, /* xml key */ - pairs[i].dp_val, /* may be NULL */ - xt) < 0) - goto done; - } - if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0) - goto done; - /* If vectors are specified then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i1) - clicon_xml2file(stderr, xt, 0, 1); - *xtop = xt; - retval = 0; - done: - if (dbfile) - free(dbfile); - if (xvec) - free(xvec); - unchunk_group(__FUNCTION__); - return retval; - -} - -/*! Add data to database internal recursive function - * @param[in] dbfile Name of database to search in (filename incl dir path) - * @param[in] xt xml-node. - * @param[in] ys Yang statement corresponding to xml-node - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[in] xkey0 aggregated xmlkey - * @retval 0 OK - * @retval -1 Error - * @note XXX op only supports merge - */ -static int -put(char *dbfile, - cxobj *xt, - yang_stmt *ys, - enum operation_type op, - const char *xk0) -{ - int retval = -1; - cxobj *x = NULL; - char *xk; - cbuf *cbxk = NULL; - char *body; - yang_stmt *y; - int exists; - char *bodyenc=NULL; - char *opstr; - - clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument); - if (debug){ - xml_print(stderr, xt); - // yang_print(stderr, (yang_node*)ys); - } - if ((opstr = xml_find_value(xt, "operation")) != NULL) - if (xml_operation(opstr, &op) < 0) - goto done; - body = xml_body(xt); - if ((cbxk = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbxk, "%s/%s", xk0, xml_name(xt)); - switch (ys->ys_keyword){ - case Y_LIST: /* Note: can have many keys */ - if (append_listkeys(cbxk, xt, ys) < 0) - goto done; - break; - case Y_LEAF_LIST: - if (uri_percent_encode(&bodyenc, "%s", body) < 0) - goto done; - cprintf(cbxk, "=%s", bodyenc); - break; - default: - break; - } - xk = cbuf_get(cbxk); - // fprintf(stderr, "%s %s\n", key, body?body:""); - /* Write to database, key and a vector of variables */ - switch (op){ - case OP_CREATE: - if ((exists = db_exists(dbfile, xk)) < 0) - goto done; - if (exists == 1){ - clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); - goto done; - } - case OP_MERGE: - case OP_REPLACE: - if (db_set(dbfile, xk, body?body:NULL, body?strlen(body)+1:0) < 0) - goto done; - break; - case OP_DELETE: - if ((exists = db_exists(dbfile, xk)) < 0) - goto done; - if (exists == 0){ - clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); - goto done; - } - case OP_REMOVE: - switch (ys->ys_keyword){ - case Y_LIST: - case Y_CONTAINER:{ - struct db_pair *pairs; - int npairs; - cbuf *cbrx; - int i; - - if ((cbrx = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbrx, "^%s.*$", xk); - if ((npairs = db_regexp(dbfile, cbuf_get(cbrx), __FUNCTION__, - &pairs, 0)) < 0) - goto done; - /* Translate to complete xml tree */ - for (i = 0; i < npairs; i++) - if (db_del(dbfile, pairs[i].dp_key) < 0) - goto done; - if (cbrx) - cbuf_free(cbrx); - /* Skip recursion, we have deleted whole subtree */ - retval = 0; - goto done; - break; - } - default: - if (db_del(dbfile, xk) < 0) - goto done; - break; - } - - break; - case OP_NONE: - break; - } - /* For every node, create a key with values */ - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((y = yang_find_datanode((yang_node*)ys, xml_name(x))) == NULL){ - clicon_err(OE_UNIX, 0, "No yang node found: %s", xml_name(x)); - goto done; - } - if (put(dbfile, x, y, op, xk) < 0) - goto done; - } - retval = 0; - done: - if (cbxk) - cbuf_free(cbxk); - if (bodyenc) - free(bodyenc); - unchunk_group(__FUNCTION__); - return retval; -} - -/*! Modify database provided an xml tree and an operation - * This is a clixon datastore plugin of the the xmldb api - * @see xmldb_put - */ -int -kv_put(xmldb_handle xh, - const char *db, - enum operation_type op, - cxobj *xt) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - cxobj *x = NULL; - yang_stmt *ys; - yang_spec *yspec; - char *dbfilename = NULL; - - if ((yspec = kh->kh_yangspec) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if (kv_db2file(kh, db, &dbfilename) < 0) - goto done; - if (op == OP_REPLACE){ - if (db_delete(dbfilename) < 0) - goto done; - if (db_init(dbfilename) < 0) - goto done; - } - // clicon_log(LOG_WARNING, "%s", __FUNCTION__); - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((ys = yang_find_topnode(yspec, xml_name(x), YC_DATANODE)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); - goto done; - } - if (put(dbfilename, /* database name */ - x, /* xml root node */ - ys, /* yang statement of xml node */ - op, /* operation, eg merge/delete */ - "" /* aggregate xml key */ - ) < 0) - goto done; - } - retval = 0; - done: - if (dbfilename) - free(dbfilename); - return retval; -} - -/*! Copy database from db1 to db2 - * @param[in] xh XMLDB handle - * @param[in] from Source database copy - * @param[in] to Destination database - * @retval -1 Error - * @retval 0 OK - */ -int -kv_copy(xmldb_handle xh, - const char *from, - const char *to) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *fromfile = NULL; - char *tofile = NULL; - - /* XXX lock */ - if (kv_db2file(kh, from, &fromfile) < 0) - goto done; - if (kv_db2file(kh, to, &tofile) < 0) - goto done; - if (clicon_file_copy(fromfile, tofile) < 0) - goto done; - retval = 0; - done: - if (fromfile) - free(fromfile); - if (tofile) - free(tofile); - return retval; -} - -/*! Lock database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK - */ -int -kv_lock(xmldb_handle xh, - const char *db, - int pid) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - if (strcmp("running", db) == 0) - _running_locked = pid; - else if (strcmp("candidate", db) == 0) - _candidate_locked = pid; - else if (strcmp("startup", db) == 0) - _startup_locked = pid; - else{ - clicon_err(OE_DB, 0, "No such database: %s", db); - goto done; - } - clicon_debug(1, "%s: locked by %u", db, pid); - retval = 0; - done: - return retval; -} - -/*! Unlock database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK - * Assume all sanity checks have been made - */ -int -kv_unlock(xmldb_handle xh, - const char *db) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - if (strcmp("running", db) == 0) - _running_locked = 0; - else if (strcmp("candidate", db) == 0) - _candidate_locked = 0; - else if (strcmp("startup", db) == 0) - _startup_locked = 0; - else{ - clicon_err(OE_DB, 0, "No such database: %s", db); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Unlock all databases locked by pid (eg process dies) - * @param[in] xh XMLDB handle - * @param[in] pid Process / Session id - * @retval -1 Error - * @retval 0 Ok - */ -int -kv_unlock_all(xmldb_handle xh, - int pid) -{ - // struct kv_handle *kh = handle(xh); - - if (_running_locked == pid) - _running_locked = 0; - if (_candidate_locked == pid) - _candidate_locked = 0; - if (_startup_locked == pid) - _startup_locked = 0; - return 0; -} - -/*! Check if database is locked - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 Not locked - * @retval >0 Id of locker - */ -int -kv_islocked(xmldb_handle xh, - const char *db) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - - if (strcmp("running", db) == 0) - retval = _running_locked; - else if (strcmp("candidate", db) == 0) - retval = _candidate_locked; - else if (strcmp("startup", db) == 0) - retval = _startup_locked; - else - clicon_err(OE_DB, 0, "No such database: %s", db); - return retval; -} - -/*! Check if db exists - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 No it does not exist - * @retval 1 Yes it exists - */ -int -kv_exists(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - struct stat sb; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (lstat(filename, &sb) < 0) - retval = 0; - else - retval = 1; - done: - if (filename) - free(filename); - return retval; -} - -/*! Delete database. Remove file - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 OK - */ -int -kv_delete(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (db_delete(filename) < 0) - goto done; - retval = 0; - done: - if (filename) - free(filename); - return retval; -} - -/*! Create / Initialize database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval 0 OK - * @retval -1 Error - */ -int -kv_create(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (db_init(filename) < 0) - goto done; - retval = 0; - done: - if (filename) - free(filename); - return retval; -} - -/*! plugin init function */ -int -kv_plugin_exit(void) -{ - return 0; -} - -static const struct xmldb_api api; - -/*! plugin init function */ -void * -clixon_xmldb_plugin_init(int version) -{ - if (version != XMLDB_API_VERSION){ - clicon_err(OE_DB, 0, "Invalid version %d expected %d", - version, XMLDB_API_VERSION); - goto done; - } - return (void*)&api; - done: - return NULL; -} - -static const struct xmldb_api api = { - 1, - XMLDB_API_MAGIC, - clixon_xmldb_plugin_init, - kv_plugin_exit, - kv_connect, - kv_disconnect, - kv_getopt, - kv_setopt, - kv_get, - kv_put, - kv_copy, - kv_lock, - kv_unlock, - kv_unlock_all, - kv_islocked, - kv_exists, - kv_delete, - kv_create, -}; - - -#if 0 /* Test program */ -/* - * Turn this on to get an xpath test program - * Usage: clicon_xpath [] - * read xml from input - * Example compile: - gcc -g -o keyvalue -I. -I../../lib ./clixon_keyvalue.c clixon_chunk.c clixon_qdb.c -lclixon -lcligen -lqdbm -*/ - -/*! Raw dump of database, just keys and values, no xml interpretation - * @param[in] f File - * @param[in] dbfile File-name of database. This is a local file - * @param[in] rxkey Key regexp, eg "^.*$" - * @note This function can only be called locally. - */ -int -main(int argc, - char **argv) -{ - int retval = -1; - int npairs; - struct db_pair *pairs; - char *rxkey = NULL; - char *dbfilename; - - if (argc != 2 && argc != 3){ - fprintf(stderr, "usage: %s [rxkey]\n", argv[0]); - goto done; - } - dbfilename = argv[1]; - if (argc == 3) - rxkey = argv[2]; - else - rxkey = "^.*$"; /* Default is match all */ - - /* Get all keys/values for vector */ - if ((npairs = db_regexp(dbfilename, rxkey, __FUNCTION__, &pairs, 0)) < 0) - goto done; - - for (npairs--; npairs >= 0; npairs--) - fprintf(stdout, "%s %s\n", pairs[npairs].dp_key, - pairs[npairs].dp_val?pairs[npairs].dp_val:""); - retval = 0; - done: - unchunk_group(__FUNCTION__); - return retval; -} - -#endif /* Test program */ diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h deleted file mode 100644 index b11e15d5..00000000 --- a/datastore/keyvalue/clixon_keyvalue.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - Key-value store - */ -#ifndef _CLIXON_KEYVALUE_H -#define _CLIXON_KEYVALUE_H - -/* - * Prototypes - */ -int kv_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int kv_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); -int kv_dump(FILE *f, char *dbfilename, char *rxkey); -int kv_copy(xmldb_handle h, const char *from, const char *to); -int kv_lock(xmldb_handle h, const char *db, int pid); -int kv_unlock(xmldb_handle h, const char *db); -int kv_unlock_all(xmldb_handle h, int pid); -int kv_islocked(xmldb_handle h, const char *db); -int kv_exists(xmldb_handle h, const char *db); -int kv_delete(xmldb_handle h, const char *db); -int kv_init(xmldb_handle h, const char *db); - -#endif /* _CLIXON_KEYVALUE_H */ diff --git a/datastore/keyvalue/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c deleted file mode 100644 index b80daf71..00000000 --- a/datastore/keyvalue/clixon_qdb.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * @note Some unclarities with locking. man dpopen defines the following flags - * with dpopen: - * `DP_ONOLCK', which means it opens a database file without - * file locking, - * `DP_OLCKNB', which means locking is performed without blocking. - * - * While connecting as a writer, an exclusive lock is invoked to - * the database file. While connecting as a reader, a shared lock is - * invoked to the database file. The thread blocks until the lock is - * achieved. If `DP_ONOLCK' is used, the application is responsible - * for exclusion control. - * The code below uses for - * write, delete: DP_OLCKNB - * read: DP_OLCKNB - * This means that a write fails if one or many reads are occurring, and - * a read or write fails if a write is occurring, and - * QDBM allows a single write _or_ multiple readers, but - * not both. This is obviously extremely limiting. - * NOTE, the locking in netconf and xmldb is a write lock. - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_DEPOT_H -#include /* qdb api */ -#else /* HAVE_QDBM_DEPOT_H */ -#include /* qdb api */ -#endif - -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" -#include "clixon_qdb.h" - -/*! Initialize database - * @param[in] file database file - * @param[in] omode see man dpopen - */ -static int -db_init_mode(char *file, - int omode) -{ - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, omode | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - clicon_debug(1, "db_init(%s)", file); - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_set: dpclose: %s", - dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Open database for reading and writing - * @param[in] file database file - */ -int -db_init(char *file) -{ - return db_init_mode(file, DP_OWRITER | DP_OCREAT ); /* DP_OTRUNC? */ -} - -/*! Remove database by removing file, if it exists * - * @param[in] file database file - */ -int -db_delete(char *file) -{ - struct stat sb; - - if (stat(file, &sb) < 0){ - return 0; - } - if (unlink(file) < 0){ - clicon_err(OE_DB, errno, "unlink %s", file); - return -1; - } - return 0; -} - -/*! Write data to database - * @param[in] file database file - * @param[in] key database key - * @param[out] data Buffer containing content - * @param[out] datalen Length of buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - */ -int -db_set(char *file, - char *key, - void *data, - size_t datalen) -{ - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OWRITER|DP_OLCKNB , 0)) == NULL){ - clicon_err(OE_DB, errno, "db_set: dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - clicon_debug(2, "%s: db_put(%s, len:%d)", - file, key, (int)datalen); - if (dpput(dp, key, -1, data, datalen, DP_DOVER) == 0){ - clicon_err(OE_DB, errno, "%s: db_set: dpput(%s, %d): %s", - file, - key, - datalen, - dperrmsg(dpecode)); - dpclose(dp); - return -1; - } - if (dpclose(dp) == 0){ - clicon_err(OE_DB, 0, "db_set: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Get data from database - * @param[in] file database file - * @param[in] key database key - * @param[out] data Pre-allocated buffer where data corresponding key is placed - * @param[out] datalen Length of pre-allocated buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - * @see db_get_alloc Allocates memory - */ -int -db_get(char *file, - char *key, - void *data, - size_t *datalen) -{ - DEPOT *dp; - int len; - - /* Open database for readinf */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: db_get(%s, %d): dpopen: %s", - file, - key, - datalen, - dperrmsg(dpecode)); - return -1; - } - len = dpgetwb(dp, key, -1, 0, *datalen, data); - if (len < 0){ - if (dpecode == DP_ENOITEM){ - data = NULL; - *datalen = 0; - } - else{ - clicon_err(OE_DB, errno, "db_get: dpgetwb: %s (%d)", - dperrmsg(dpecode), dpecode); - dpclose(dp); - return -1; - } - } - else - *datalen = len; - clicon_debug(2, "db_get(%s, %s)=%s", file, key, (char*)data); - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_get: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Get data from database and allocates memory - * Similar to db_get but returns a malloced pointer to the data instead - * of copying data to pre-allocated buffer. This is necessary if the - * length of the data is not known when calling the function. - * @param[in] file database file - * @param[in] key database key - * @param[out] data Allocated buffer where data corresponding key is placed - * @param[out] datalen Length of pre-allocated buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - * @note: *data needs to be freed after use. - * @code - * char *lvec = NULL; - * size_t len = 0; - * if (db_get-alloc(dbname, "a.0", &val, &vlen) == NULL) - * return -1; - * ..do stuff.. - * if (val) free(val); - * @endcode - * @see db_get Pre-allocates memory - */ -int -db_get_alloc(char *file, - char *key, - void **data, - size_t *datalen) -{ - DEPOT *dp; - int len; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: dpopen(%s): %s", - __FUNCTION__, - file, - dperrmsg(dpecode)); - return -1; - } - if ((*data = dpget(dp, key, -1, 0, -1, &len)) == NULL){ - if (dpecode == DP_ENOITEM){ - *datalen = 0; - *data = NULL; - len = 0; - } - else{ - /* No entry vs error? */ - clicon_err(OE_DB, errno, "db_get_alloc: dpgetwb: %s (%d)", - dperrmsg(dpecode), dpecode); - dpclose(dp); - return -1; - } - } - *datalen = len; - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_get_alloc: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Delete database entry - * @param[in] file database file - * @param[in] key database key - * @retval -1 on failure, - * @retval 0 if key did not exist - * @retval 1 if successful. - */ -int -db_del(char *file, char *key) -{ - int retval = 0; - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OWRITER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "db_del: dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - if (dpout(dp, key, -1)) { - retval = 1; - } - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_del: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return retval; -} - -/*! Check if entry in database exists - * @param[in] file database file - * @param[in] key database key - * @retval 1 if key exists in database - * @retval 0 key does not exist in database - * @retval -1 error - */ -int -db_exists(char *file, - char *key) -{ - DEPOT *dp; - int len; - - /* Open database for reading */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: dpopen: %s", - __FUNCTION__, dperrmsg(dpecode)); - return -1; - } - - len = dpvsiz(dp, key, -1); - if (len < 0 && dpecode != DP_ENOITEM) - clicon_err(OE_DB, errno, "^s: dpvsiz: %s (%d)", - __FUNCTION__, dperrmsg(dpecode), dpecode); - - if (dpclose(dp) == 0) { - clicon_err(OE_DB, errno, "%s: dpclose: %s", dperrmsg(dpecode),__FUNCTION__); - return -1; - } - - return (len < 0) ? 0 : 1; -} - -/*! Return all entries in database that match a regular expression. - * @param[in] file database file - * @param[in] regexp regular expression for database keys - * @param[in] label for memory/chunk allocation - * @param[out] pairs Vector of database keys and values - * @param[in] noval If set don't retreive values, just keys - * @retval -1 on error - * @retval n Number of pairs - * @code - * struct db_pair *pairs; - * int npairs; - * if ((npairs = db_regexp(dbname, "^/test/kalle$", __FUNCTION__, - * &pairs, 0)) < 0) - * err; - * - * @endcode - */ -int -db_regexp(char *file, - char *regexp, - const char *label, - struct db_pair **pairs, - int noval) -{ - int npairs; - int status; - int retval = -1; - int vlen = 0; - char *key = NULL; - void *val = NULL; - char errbuf[512]; - struct db_pair *pair; - struct db_pair *newpairs; - regex_t iterre; - DEPOT *iterdp = NULL; - regmatch_t pmatch[1]; - size_t nmatch = 1; - - npairs = 0; - *pairs = NULL; - - if (regexp) { - if ((status = regcomp(&iterre, regexp, REG_EXTENDED)) != 0) { - regerror(status, &iterre, errbuf, sizeof(errbuf)); - clicon_err(OE_DB, errno, "%s: regcomp: %s", __FUNCTION__, errbuf); - return -1; - } - } - - /* Open database for reading */ - if ((iterdp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, 0, "%s: dpopen(%s): %s", - __FUNCTION__, file, dperrmsg(dpecode)); - goto quit; - } - - /* Initiate iterator */ - if(dpiterinit(iterdp) == 0) { - clicon_err(OE_DB, errno, "%s: dpiterinit: %s", __FUNCTION__, dperrmsg(dpecode)); - goto quit; - } - - /* Iterate through DB */ - while((key = dpiternext(iterdp, NULL)) != NULL) { - - if (regexp && regexec(&iterre, key, nmatch, pmatch, 0) != 0) { - free(key); - continue; - } - - /* Retrieve value if required */ - if ( ! noval) { - if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) { - clicon_log(LOG_WARNING, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode)); - goto quit; - } - } - - /* Resize and populate resulting array */ - newpairs = rechunk(*pairs, (npairs+1) * sizeof(struct db_pair), label); - if (newpairs == NULL) { - clicon_err(OE_DB, errno, "%s: rechunk", __FUNCTION__); - goto quit; - } - pair = &newpairs[npairs]; - memset(pair, 0, sizeof(*pair)); - - pair->dp_key = chunk_sprintf(label, "%s", key); - if (regexp) - pair->dp_matched = chunk_sprintf(label, "%.*s", - pmatch[0].rm_eo - pmatch[0].rm_so, - key + pmatch[0].rm_so); - else - pair->dp_matched = chunk_sprintf(label, "%s", key); - if (pair->dp_key == NULL || pair->dp_matched == NULL) { - clicon_err(OE_DB, errno, "%s: chunk_sprintf"); - goto quit; - } - if ( ! noval) { - if (vlen){ - pair->dp_val = chunkdup(val, vlen, label); - if (pair->dp_val == NULL) { - clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__); - goto quit; - } - } - pair->dp_vlen = vlen; - free(val); - val = NULL; - } - - (*pairs) = newpairs; - npairs++; - free(key); - } - - retval = npairs; - -quit: - if (key) - free(key); - if (val) - free(val); - if (regexp) - regfree(&iterre); - if (iterdp) - dpclose(iterdp); - if (retval < 0) - unchunk_group(label); - - return retval; -} - -/*! Sanitize regexp string. Escape '\' etc. - */ -char * -db_sanitize(char *rx, const char *label) -{ - char *new; - char *k, *p, *s; - - k = chunk_sprintf(__FUNCTION__, "%s", ""); - p = rx; - while((s = strstr(p, "\\"))) { - if ((k = chunk_sprintf(__FUNCTION__, "%s%.*s\\\\", k, s-p, p)) == NULL) - goto quit; - p = s+1; - } - if ((k = chunk_strncat(k, p, strlen(p), __FUNCTION__)) == NULL) - goto quit; - - new = (char *)chunkdup(k, strlen(k)+1, label); - unchunk_group(__FUNCTION__); - return new; - - quit: - unchunk_group(__FUNCTION__); - return NULL; -} - -#if 0 /* Test program */ -/* - * Turn this on to get an xpath test program - * Usage: clicon_xpath [] - * read xml from input - * Example compile: - gcc -g -o qdb -I. -I../clixon ./clixon_qdb.c -lclixon -lcligen -lqdbm -*/ - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, "\t%s init \n", argv0); - fprintf(stderr, "\t%s read \n", argv0); - fprintf(stderr, "\t%s write \n", argv0); - fprintf(stderr, "\t%s openread \n", argv0); - fprintf(stderr, "\t%s openwrite \n", argv0); - exit(0); -} - -int -main(int argc, char **argv) -{ - char *verb; - char *filename; - char *key; - char *val; - size_t len; - DEPOT *dp; - - if (argc < 3) - usage(argv[0]); - clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); - verb = argv[1]; - filename = argv[2]; - if (strcmp(verb, "init")==0){ - db_init(filename); - } - else if (strcmp(verb, "read")==0){ - if (argc < 4) - usage(argv[0]); - key = argv[3]; - db_get_alloc(filename, key, (void**)&val, &len); - fprintf(stdout, "%s\n", val); - } - else if (strcmp(verb, "write")==0){ - if (argc < 5) - usage(argv[0]); - key = argv[3]; - val = argv[4]; - db_set(filename, key, val, strlen(val)+1); - } - else if (strcmp(verb, "openread")==0){ - if ((dp = dpopen(filename, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dbopen: %s", - dperrmsg(dpecode)); - return -1; - } - sleep(1000000); - } - else if (strcmp(verb, "openwrite")==0){ - if ((dp = dpopen(filename, DP_OWRITER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dbopen: %s", - dperrmsg(dpecode)); - return -1; - } - sleep(1000000); - } - return 0; -} - -#endif /* Test program */ - - diff --git a/datastore/keyvalue/clixon_qdb.h b/datastore/keyvalue/clixon_qdb.h deleted file mode 100644 index d2d38785..00000000 --- a/datastore/keyvalue/clixon_qdb.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - */ - -#ifndef _CLIXON_QDB_H_ -#define _CLIXON_QDB_H_ - - -/* - * Low level API - */ - -struct db_pair { - char *dp_key; /* database key */ - char *dp_matched; /* Matched component of key */ - char *dp_val; /* pointer to vector of lvalues */ - int dp_vlen; /* length of vector of lvalues */ -}; - -/* - * Prototypes - */ -int db_init(char *file); - -int db_delete(char *file); - -int db_set(char *file, char *key, void *data, size_t datalen); - -int db_get(char *file, char *key, void *data, size_t *datalen); - -int db_get_alloc(char *file, char *key, void **data, size_t *datalen); - -int db_del(char *file, char *key); - -int db_exists(char *file, char *key); - -int db_regexp(char *file, char *regexp, const char *label, - struct db_pair **pairs, int noval); - -char *db_sanitize(char *rx, const char *label); - -#endif /* _CLIXON_QDB_H_ */ diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 5d1edaf2..8727daa0 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -748,7 +748,8 @@ text_modify(cxobj *x0, * @see text_modify */ static int -text_modify_top(cxobj *x0, +text_modify_top(struct text_handle *th, + cxobj *x0, cxobj *x1, yang_spec *yspec, enum operation_type op, @@ -759,6 +760,7 @@ text_modify_top(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; /* yang child */ + yang_stmt *ymod;/* yang module */ char *opstr; /* Assure top-levels are 'config' */ @@ -805,10 +807,23 @@ text_modify_top(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", - xml_name(x1), x1cname); + yc = NULL; + if (ys_module_by_xml(yspec, x1c, &ymod) <0) goto done; + if (ymod != NULL) + yc = yang_find_datanode((yang_node*)ymod, x1cname); + if (yc == NULL && _CLICON_XML_NS_ITERATE){ + int i; + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL) + break; + } + } + if (yc == NULL){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + goto ok; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) @@ -942,11 +957,12 @@ text_put(xmldb_handle xh, xml_name(x0)); goto done; } - - /* Add yang specification backpointer to all XML nodes */ - /* XXX: where is this created? Add yspec */ +#if 0 + /* Add yang specification backpointer to all XML nodes + * This is already done in from_client_edit_config() */ if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; +#endif #if 0 /* debug */ if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); @@ -955,7 +971,7 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(x0, x1, yspec, op, cbret) < 0) + if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) diff --git a/example/README.md b/example/README.md index 48ed4595..7621d9b5 100644 --- a/example/README.md +++ b/example/README.md @@ -191,10 +191,9 @@ state data. ## Authentication and NACM The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): -* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: adm1, wilma, and guest, according to the examples in Appendix A in the RFC. +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). * A NACM backend plugin reporting the mandatory NACM state variables. - ## Systemd files Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. diff --git a/example/example.yang b/example/example.yang index 0ee76e30..57f58fcb 100644 --- a/example/example.yang +++ b/example/example.yang @@ -9,6 +9,7 @@ module example { prefix ip; } import ietf-routing { + description "defines fib-route"; prefix rt; } import iana-if-type { @@ -72,4 +73,13 @@ module example { } } } + rpc debug { + description "Set debug level of backend. XXX should be in clixon-config"; + input { + leaf level { + type uint32; + } + } + } + } diff --git a/example/example_cli.c b/example/example_cli.c index 99c38368..987aae30 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h, /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", + if (xml_parse_va(&xtop, NULL, "%s", clicon_username_get(h), cv_string_get(instance)) < 0) goto done; diff --git a/example/example_restconf.c b/example/example_restconf.c index b7d55505..43771662 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -191,7 +191,7 @@ b64_decode(const char *src, * @retval -1 Fatal error * @retval 0 Unauth * @retval 1 Auth - * @note: Three hardwired users: adm1, wilma, guest w password "bar". + * @note: Three hardwired users: andy, wilma, guest w password "bar". * Enabled by passing -- -a to the main function */ int @@ -237,9 +237,9 @@ example_restconf_credentials(clicon_handle h, /* Here get auth sub-tree whjere all the users are */ if ((cb = cbuf_new()) == NULL) goto done; - /* Hardcoded user/passwd */ - if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 || - strcmp(user, "quest")==0){ + /* XXX Three hardcoded user/passwd (from RFC8341 A.1)*/ + if (strcmp(user, "wilma")==0 || strcmp(user, "andy")==0 || + strcmp(user, "guest")==0){ passwd2 = "bar"; } if (strcmp(passwd, passwd2)) @@ -282,7 +282,7 @@ restconf_client_rpc(clicon_handle h, /*! Start example restonf plugin. Set authentication method * Arguments are argc/argv after -- * Currently defined: -a enable http basic authentication - * Note hardwired users adm1, wilma and guest + * @note There are three hardwired users andy, wilma and guest from RFC8341 A.1 */ int example_restconf_start(clicon_handle h, diff --git a/extras/rpm/clixon.spec b/extras/rpm/clixon.spec index c6ee5f70..20e77c04 100644 --- a/extras/rpm/clixon.spec +++ b/extras/rpm/clixon.spec @@ -36,7 +36,7 @@ This package contains header files for CLIXON. %setup %build -%configure --with-cligen=%{cligen_prefix} --without-keyvalue +%configure --with-cligen=%{cligen_prefix} make %install diff --git a/lib/Makefile.in b/lib/Makefile.in index eba8991d..d34e81ac 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -80,5 +80,5 @@ distclean: clean do (cd $$i; $(MAKE) $(MFLAGS) distclean); done; \ (cd clixon; $(MAKE) $(MFLAGS) $@) -tags: +TAGS: find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h index 34e9a939..978b5486 100644 --- a/lib/clixon/clixon_nacm.h +++ b/lib/clixon/clixon_nacm.h @@ -36,9 +36,19 @@ #ifndef _CLIXON_NACM_H #define _CLIXON_NACM_H +/* + * Constants + */ +/* RFC8341 defines a "recovery session" as outside the scope. + * Clixon defines this user as having special admin rights to expemt from + * all access control enforcements + */ +#define NACM_RECOVERY_USER "_nacm_recovery" + /* * Prototypes */ -int nacm_access(clicon_handle h, char *mode, char *name, char *username, cbuf *cbret); +int nacm_access(clicon_handle h, char *rpc, char *module, + char *username, cbuf *cbret); #endif /* _CLIXON_NACM_H */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 59e25913..803e5948 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -41,6 +41,10 @@ /* * Constants */ +/* If rpc call does not have a namespace (eg w xmlns) then use the default NETCONF + * namespace (rfc6241 3.1) + */ +#define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" /* * Types @@ -81,10 +85,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg); #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ -/* Sort and binary search of XML children - * Experimental +/* Iterate through modules to find the matching datanode + * or rpc if no xmlns attribute specifies namespace. + * This is loose semantics of finding namespaces. + * And it is wrong, but is the way Clixon originally was written." + * @see CLICON_XML_NS_ITERATE clixon configure option */ -extern int xml_child_sort; +extern int _CLICON_XML_NS_ITERATE; /* * Prototypes @@ -94,6 +101,7 @@ char *xml_name(cxobj *xn); int xml_name_set(cxobj *xn, char *name); char *xml_namespace(cxobj *xn); int xml_namespace_set(cxobj *xn, char *name); +int xml2ns(cxobj *x, char *localname, char **namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -129,6 +137,8 @@ int xml_rootchild(cxobj *xp, int i, cxobj **xcp); char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); +char *xml_find_type_value(cxobj *xn_parent, char *prefix, + char *name, enum cxobj_type type); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6a5a32fe..abd1fe50 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,6 +43,7 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); +int xml_yang_validate_rpc(cxobj *xrpc); int xml_yang_validate_add(cxobj *xt, void *arg); int xml_yang_validate_all(cxobj *xt, void *arg); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); @@ -60,6 +61,7 @@ int xml_default(cxobj *x, void *arg); int xml_order(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); +int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index a49d1e94..e9aee2e6 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -49,6 +49,7 @@ /* * Types */ +struct xml; /*! YANG keywords from RFC6020. * See also keywords generated by yacc/bison in clicon_yang_parse.tab.h, but they start with K_ * instead of Y_ @@ -159,7 +160,6 @@ typedef enum yang_class yang_class; */ #define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES) - /* Yang schema node . * See RFC 7950 Sec 3: * o schema node: A node in the schema tree. One of action, container, @@ -253,15 +253,18 @@ char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); int yang_nodeid_split(char *nodeid, char **prefix, char **id); +int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); +yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace); yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); char *yang_find_myprefix(yang_stmt *ys); +char *yang_find_mynamespace(yang_stmt *ys); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 794b35f2..00778c30 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -90,22 +90,43 @@ enum childtype{ ANY_CHILD, /* eg or */ }; +/*! Number of children EXCEPT attributes + * @param[in] xn xml node + * @retval number of children in XML tree (except children of type CX_ATTR) + * @see xml_child_nr + */ +static int +xml_child_nr_noattr(cxobj *xn) +{ + cxobj *x = NULL; + int nr = 0; + + while ((x = xml_child_each(xn, x, -1)) != NULL) { + if (xml_type(x) != CX_ATTR) + nr++; + } + return nr; +} + /*! x is element and has exactly one child which in turn has none + * remove attributes from x * Clone from clixon_xml_map.c */ static enum childtype childtype(cxobj *x) { cxobj *xc1; /* the only child of x */ + int clen; /* nr of children */ + clen = xml_child_nr_noattr(x); if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ - if (xml_child_nr(x) == 0) + if (clen == 0) return NULL_CHILD; - if (xml_child_nr(x) > 1) + if (clen > 1) return ANY_CHILD; xc1 = xml_child_i(x, 0); /* From here exactly one child */ - if (xml_child_nr(xc1) == 0 && xml_type(xc1)==CX_BODY) + if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; @@ -267,13 +288,13 @@ json_str_escape(char *str) +----------+--------------+--------------+--------------+ */ static int -xml2json1_cbuf(cbuf *cb, - cxobj *x, +xml2json1_cbuf(cbuf *cb, + cxobj *x, enum array_element_type arraytype, - int level, - int pretty, - int flat, - int bodystr) + int level, + int pretty, + int flat, + int bodystr) { int retval = -1; int i; @@ -281,10 +302,30 @@ xml2json1_cbuf(cbuf *cb, enum childtype childt; enum array_element_type xc_arraytype; yang_stmt *ys; + yang_stmt *ymod; /* yang module */ + yang_spec *yspec = NULL; /* yang spec */ int bodystr0=1; + char *str; + char *prefix=NULL; /* prefix / local namespace name */ + char *namespace=NULL; /* namespace uri */ + char *modname=NULL; /* Module name */ + /* If x is labelled with a default namespace, it should be translated + * to a module name. + * Harder if x has a prefix, then that should also be translated to associated + * module name + */ + prefix = xml_namespace(x); + if (xml2ns(x, prefix, &namespace) < 0) + goto done; + if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ + yspec = ys_spec(ys); + /* Find module name associated with namspace URI */ + if (namespace && yspec && + (ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ + modname = ymod->ys_argument; + } childt = childtype(x); - ys = xml_spec(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), @@ -292,7 +333,6 @@ xml2json1_cbuf(cbuf *cb, switch(arraytype){ case BODY_ARRAY:{ if (bodystr){ - char *str; if ((str = json_str_escape(xml_value(x))) == NULL) goto done; cprintf(cb, "\"%s\"", str); @@ -300,14 +340,13 @@ xml2json1_cbuf(cbuf *cb, } else cprintf(cb, "%s", xml_value(x)); - break; } case NO_ARRAY: if (!flat){ cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (modname) /* XXX should remove this? */ + cprintf(cb, "%s:", modname); cprintf(cb, "%s\": ", xml_name(x)); } switch (childt){ @@ -326,8 +365,8 @@ xml2json1_cbuf(cbuf *cb, case FIRST_ARRAY: case SINGLE_ARRAY: cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (modname) + cprintf(cb, "%s:", modname); cprintf(cb, "%s\": ", xml_name(x)); level++; cprintf(cb, "[%s%*s", @@ -368,7 +407,7 @@ xml2json1_cbuf(cbuf *cb, break; } /* Check for typed sub-body if: - * arracytype=* but chilt-type is BODY_CHILD + * arraytype=* but child-type is BODY_CHILD * This is code for writing 42 as "a":42 and not "a":"42" */ if (childt == BODY_CHILD && ys!=NULL && @@ -393,6 +432,8 @@ xml2json1_cbuf(cbuf *cb, for (i=0; i protocol operation, then the protocol operation is permitted. @@ -280,7 +265,7 @@ nacm_access(clicon_handle h, for (j=0; jietf-netconf:candidate", yspec, &xc) < 0) goto done; @@ -1005,6 +1000,12 @@ netconf_module_load(clicon_handle h) goto done; if (xml_parse_string("ietf-netconf:xpath", yspec, &xc) < 0) goto done; + + /* Load yang spec */ + if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) + goto done; + if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) + goto done; retval = 0; done: return retval; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 74e5a645..873d3aaf 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -63,6 +63,7 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_sort.h" #include "clixon_options.h" #include "clixon_plugin.h" #include "clixon_xpath_ctx.h" @@ -242,6 +243,9 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } +#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ + _CLICON_XML_NS_ITERATE = 1; +#endif /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; @@ -267,6 +271,9 @@ clicon_options_main(clicon_handle h, xml_child_sort = 1; else xml_child_sort = 0; +#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ + _CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE"); +#endif retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9de878c0..0cf421d2 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -826,7 +826,8 @@ clicon_rpc_debug(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) + /* XXX: hardcoded example yang, should be clixon-config!!! */ + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 16183ef2..24bab41b 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -108,17 +108,13 @@ * - Namespace name: For a name N in a namespace identified by a URI I, the * "namespace name" is I. * For a name N that is not in a namespace, the "namespace name" has no value. - * - Local name: In either case the "local name" is N. + * - Local name: In either case the "local name" is N (also "prefix") * It is this combination of the universally managed URI namespace with the * vocabulary's local names that is effective in avoiding name clashes. */ struct xml{ char *x_name; /* name of node */ - char *x_namespace; /* namespace, if any */ -#ifdef notyet - char *x_namespacename; /* namespace name (or NULL) */ - char *x_localname; /* Local name N as defined above */ -#endif + char *x_prefix; /* namespace localname N, called prefix */ struct xml *x_up; /* parent node in hierarchy if any */ struct xml **x_childvec; /* vector of children nodes */ int x_childvec_len;/* length of vector */ @@ -130,6 +126,17 @@ struct xml{ reference, dont free */ }; +/* + * Variables + */ +/* Iterate through modules to find the matching datanode + * or rpc if no xmlns attribute specifies namespace. + * This is loose semantics of finding namespaces. + * And it is wrong, but is the way Clixon originally was written." + * @see CLICON_XML_NS_ITERATE clixon configure option + */ +int _CLICON_XML_NS_ITERATE = 0; + /* Mapping between xml type <--> string */ static const map_str2int xsmap[] = { {"error", CX_ERROR}, @@ -189,29 +196,31 @@ xml_name_set(cxobj *xn, /*! Get namespace of xnode * @param[in] xn xml node * @retval namespace of xml node + * XXX change to xml_localname */ char* xml_namespace(cxobj *xn) { - return xn->x_namespace; + return xn->x_prefix; } /*! Set name space of xnode, namespace is copied * @param[in] xn xml node - * @param[in] namespace new namespace, null-terminated string, copied by function + * @param[in] localname new namespace, null-terminated string, copied by function * @retval -1 on error with clicon-err set * @retval 0 OK + * XXX change to xml_localname_set */ int xml_namespace_set(cxobj *xn, - char *namespace) + char *localname) { - if (xn->x_namespace){ - free(xn->x_namespace); - xn->x_namespace = NULL; + if (xn->x_prefix){ + free(xn->x_prefix); + xn->x_prefix = NULL; } - if (namespace){ - if ((xn->x_namespace = strdup(namespace)) == NULL){ + if (localname){ + if ((xn->x_prefix = strdup(localname)) == NULL){ clicon_err(OE_XML, errno, "strdup"); return -1; } @@ -219,12 +228,57 @@ xml_namespace_set(cxobj *xn, return 0; } -/*! See if xmlns:= exists, if so return + +/*! Given an xml tree return URI namespace: default or localname given + * + * Given an XML tree and a prefix (or NULL) return URI namespace. + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then return default. + * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree + * @retval 0 OK + * @retval -1 Error + * @see xmlns_check XXX coordinate + */ +int +xml2ns(cxobj *x, + char *prefix, + char **namespace) +{ + int retval = -1; + char *ns; + cxobj *xp; + + if (prefix != NULL) /* xmlns: */ + ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); + else /* default ns */ + ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); + + /* namespace not found, try parent */ + if (ns == NULL){ + if ((xp = xml_parent(x)) != NULL){ + if (xml2ns(xp, prefix, &ns) < 0) + goto done; + } + /* If no parent, return default namespace if defined */ +#if defined(DEFAULT_XML_RPC_NAMESPACE) + else + ns = DEFAULT_XML_RPC_NAMESPACE; +#endif + } + if (namespace) + *namespace = ns; + retval = 0; + done: + return retval; +} + +/*! See if xmlns:[=] exists, if so return * * @param[in] xn XML node * @param[in] nsn Namespace name * @retval URI return associated URI if found * @retval NULL No namespace name binding found for nsn + * @see xml2ns XXX coordinate */ static char * xmlns_check(cxobj *xn, @@ -233,7 +287,7 @@ xmlns_check(cxobj *xn, cxobj *x = NULL; char *xns; - while ((x = xml_child_each(xn, x, -1)) != NULL) + while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 && strcmp(xml_name(x), nsn) == 0) return xml_value(x); @@ -248,7 +302,7 @@ xmlns_check(cxobj *xn, * @note This function is grossly inefficient */ static int -xml_namespace_check(cxobj *xn, +xml_localname_check(cxobj *xn, void *arg) { cxobj *xp = NULL; @@ -886,6 +940,42 @@ xml_body_get(cxobj *xt) return NULL; } +/*! Find and return the value of an xml child of specific type + * + * The value can be of an attribute only + * @param[in] xt xml tree node + * @param[in] prefix Prefix (namespace local name) or NULL + * @param[in] name name of xml tree node (eg attr name or "body") + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node + * @code + * char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR); + * @endcode + * @note, make a copy of the return value to use it properly + * @see xml_find_value where a body can be found as well + */ +char * +xml_find_type_value(cxobj *xt, + char *prefix, + char *name, + enum cxobj_type type) +{ + cxobj *x = NULL; + int pmatch; /* prefix match */ + char *xprefix; /* xprefix */ + + while ((x = xml_child_each(xt, x, type)) != NULL) { + xprefix = xml_namespace(x); + if (prefix) + pmatch = xprefix?strcmp(prefix,xprefix)==0:0; + else + pmatch = 1; + if (pmatch && strcmp(name, xml_name(x)) == 0) + return xml_value(x); + } + return NULL; +} + /*! Find and return the value of a sub xml node * * The value can be of an attribute or body. @@ -895,7 +985,7 @@ xml_body_get(cxobj *xt) * @retval NULL No such node or no value in node * * Note, make a copy of the return value to use it properly - * See also xml_find_body + * @see xml_find_body * Explaining picture: * xt --> x * x_name=name @@ -983,8 +1073,8 @@ xml_free(cxobj *x) free(x->x_name); if (x->x_value) free(x->x_value); - if (x->x_namespace) - free(x->x_namespace); + if (x->x_prefix) + free(x->x_prefix); for (i=0; ix_childvec_len; i++){ if ((xc = x->x_childvec[i]) != NULL){ xml_free(xc); @@ -1028,6 +1118,8 @@ clicon_xml2file(FILE *f, char *val; char *encstr = NULL; /* xml encoded string */ + if (x == NULL) + goto ok; name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ @@ -1097,6 +1189,7 @@ clicon_xml2file(FILE *f, default: break; }/* switch */ + ok: retval = 0; done: if (encstr) @@ -1302,7 +1395,7 @@ _xml_parse(const char *str, if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */ goto done; /* Verify namespaces after parsing */ - if (xml_apply0(xt, CX_ELMNT, xml_namespace_check, NULL) < 0) + if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0) goto done; /* Sort the complete tree after parsing */ if (yspec){ @@ -1507,15 +1600,20 @@ int xml_copy_one(cxobj *x0, cxobj *x1) { + char *s; + xml_type_set(x1, xml_type(x0)); - if (xml_value(x0)){ /* malloced string */ - if ((x1->x_value = strdup(x0->x_value)) == NULL){ + if ((s = xml_value(x0))){ /* malloced string */ + if ((x1->x_value = strdup(s)) == NULL){ clicon_err(OE_XML, errno, "strdup"); return -1; } } - if (xml_name(x0)) /* malloced string */ - if ((xml_name_set(x1, xml_name(x0))) < 0) + if ((s = xml_name(x0))) /* malloced string */ + if ((xml_name_set(x1, s)) < 0) + return -1; + if ((s = xml_namespace(x0))) /* malloced string */ + if ((xml_namespace_set(x1, s)) < 0) return -1; return 0; } @@ -1794,7 +1892,6 @@ xml_body_parse(cxobj *xb, if (retval < 0 && cv != NULL) cv_free(cv); return retval; - } /*! Parse an xml body as int32 diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 4dfe4f96..c3a4126a 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -70,7 +70,7 @@ /*! Load an xmldb storage plugin according to filename * If init function fails (not found, wrong version, etc) print a log and dont * add it. - * @param[in] h CLicon handle + * @param[in] h Clicon handle * @param[in] filename Actual filename including path */ int diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index d9c394cf..6794b503 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -245,7 +245,7 @@ validate_leafref(cxobj *xt, char *leafbody; if ((leafrefbody = xml_body(xt)) == NULL) - return 0; + goto ok; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); goto done; @@ -264,6 +264,7 @@ validate_leafref(cxobj *xt, leafrefbody); goto done; } + ok: retval = 0; done: if (xvec) @@ -337,6 +338,73 @@ validate_identityref(cxobj *xt, return retval; } +/*! Validate an RPC node + * @param[in] xt XML node to be validated + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * rfc7950 + * 7.14.2 + * If a leaf in the input tree has a "mandatory" statement with the + * value "true", the leaf MUST be present in an RPC invocation. + * + * If a leaf in the input tree has a default value, the server MUST use + * this value in the same cases as those described in Section 7.6.1. In + * these cases, the server MUST operationally behave as if the leaf was + * present in the RPC invocation with the default value as its value. + * + * If a leaf-list in the input tree has one or more default values, the + * server MUST use these values in the same cases as those described in + * Section 7.7.2. In these cases, the server MUST operationally behave + * as if the leaf-list was present in the RPC invocation with the + * default values as its values. + * + * Since the input tree is not part of any datastore, all "config" + * statements for nodes in the input tree are ignored. + * + * If any node has a "when" statement that would evaluate to "false", + * then this node MUST NOT be present in the input tree. + * + * 7.14.4 + * Input parameters are encoded as child XML elements to the rpc node's + * XML element, in the same order as they are defined within the "input" + * statement. + * + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element defined + * in [RFC6241]. If output parameters are returned, they are encoded as + * child elements to the element defined in [RFC6241], in + * the same order as they are defined within the "output" statement. + */ +int +xml_yang_validate_rpc(cxobj *xrpc) +{ + int retval = -1; + yang_stmt *yn=NULL; /* rpc name */ + cxobj *xn; /* rpc name */ + yang_stmt *yi=NULL; /* input name */ + cxobj *xi; /* input name */ + + assert(strcmp(xml_name(xrpc), "rpc")==0); + xn = NULL; + while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { + if ((yn = xml_spec(xn)) == NULL) + goto fail; + xi = NULL; + while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) { + if ((yi = xml_spec(xi)) == NULL) + goto fail; + } + } + // ok: /* pass validation */ + 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. @@ -418,9 +486,14 @@ xml_yang_validate_add(cxobj *xt, /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @retval 0 Valid OK + * @param[in] arg Not used * @retval -1 Validation failed + * @retval 0 Validation OK * @see xml_yang_validate_add + * @code + * if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0) + * err; + * @endcode */ int xml_yang_validate_all(cxobj *xt, @@ -1397,41 +1470,133 @@ xml_non_config_data(cxobj *xt, return retval; } - -/*! Add yang specification backpoint to XML node +/*! Add yang specification backpointer to rpc + * * @param[in] xt XML tree node * @param[in] arg Yang spec - * @note This may be unnecessary if yspec us set on creation + * @retval 0 OK + * @retval -1 Error + * @note This may be unnecessary if yspec is set on creation * @note For subs to anyxml nodes will not have spec set * @note No validation is done,... XXX * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode + * @see xml_spec_populate + */ +int +xml_spec_populate_rpc(clicon_handle h, + cxobj *xrpc, + yang_spec *yspec) +{ + int retval = -1; + yang_stmt *y=NULL; /* yang node */ + yang_stmt *ymod=NULL; /* yang module */ + yang_stmt *yi = NULL; /* input */ + yang_stmt *ya = NULL; /* arg */ + cxobj *x; + cxobj *xi; + int i; + + if ((strcmp(xml_name(xrpc), "rpc"))!=0){ + clicon_err(OE_UNIX, EINVAL, "RPC expected"); + goto done; + } + x = NULL; + while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) { + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x)); + /* Loose semantics: loop through all modules to find the node + */ + if (y == NULL && + clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) + break; + } + } + if (y){ + xml_spec_set(x, y); + if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ + xi = NULL; + while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { + if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) + xml_spec_set(xi, ya); + } + } + } + } + retval = 0; + done: + return retval; +} + +/*! Add yang specification backpointer to XML node + * @param[in] xt XML tree node + * @param[in] arg Yang spec + * @note This may be unnecessary if yspec is set on creation + * @note For subs to anyxml nodes will not have spec set + * @note No validation is done,... XXX + * @note relies on kludge _CLICON_XML_NS_ITERATE + * @code + * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) + * @endcode */ int xml_spec_populate(cxobj *x, void *arg) { int retval = -1; - yang_spec *yspec = (yang_spec*)arg; + // clicon_handle h = (clicon_handle)arg; + yang_spec *yspec=NULL; /* yang spec */ yang_stmt *y=NULL; /* yang node */ + yang_stmt *yparent; /* yang parent */ + yang_stmt *ymod; /* yang module */ + cxobj *xp; /* xml parent */ + char *name; + int i; - if (xml_child_spec(xml_name(x), xml_parent(x), yspec, &y) < 0) - goto done; -#if 0 - if ((xp = xml_parent(x)) != NULL && - (yp = xml_spec(xp)) != NULL) - y = yang_find_datanode((yang_node*)yp, xml_name(x)); - else - y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ -#endif - if (y) + yspec = (yang_spec*)arg; + if (xml_spec(x)) + goto ok; + xp = xml_parent(x); + name = xml_name(x); + if (xp && (yparent = xml_spec(xp)) != NULL) + y = yang_find_datanode((yang_node*)yparent, name); + else if (yspec){ + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find_datanode((yang_node*)ymod, name); + /* Loose semantics: loop through all modules to find the node + * XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE") + */ + if (y == NULL && _CLICON_XML_NS_ITERATE){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL) + break; + } + } + } + if (y) xml_spec_set(x, y); +#if 0 /* Add if you want validation error */ + else { + clicon_err(OE_YANG, ENOENT, "No yang top found?"); + goto done; + } +#endif + ok: retval = 0; done: return retval; } + /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec @@ -1757,7 +1922,6 @@ api_path2xml(char *api_path, * @retval 0 OK. If reason is set, Yang error * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec - * @see put in clixon_keyvalue.c */ static int xml_merge1(cxobj *x0, diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 27842b6a..7e68d2b8 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -56,7 +56,6 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_string.h" - #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" @@ -73,7 +72,6 @@ */ int xml_child_sort = 1; - /*! Given a child name and an XML object, return yang stmt of child * If no xml parent, find root yang stmt matching name * @param[in] x Child @@ -81,6 +79,8 @@ int xml_child_sort = 1; * @param[in] yspec Yang specification (top level) * @param[out] yresult Pointer to yang stmt of result, or NULL, if not found * @note special rule for rpc, ie ,look for top "foo" node. + * @note works for import prefix, but not work for generic XML parsing where + * xmlns and xmlns:ns are used. */ int xml_child_spec(char *name, @@ -88,17 +88,40 @@ xml_child_spec(char *name, yang_spec *yspec, yang_stmt **yresult) { - yang_stmt *y; /* result yang node */ + int retval = -1; + yang_stmt *y = NULL; /* result yang node */ yang_stmt *yparent; /* parent yang */ - - if (xp && (yparent = xml_spec(xp)) != NULL) - y = yang_find_datanode((yang_node*)yparent, name); - else if (yspec) - y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ + yang_stmt *ymod = NULL; + yang_stmt *yi; + int i; + + if (xp && (yparent = xml_spec(xp)) != NULL){ + if (yparent->ys_keyword == Y_RPC){ + if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL) + y = yang_find_datanode((yang_node*)yi, name); + } + else + y = yang_find_datanode((yang_node*)yparent, name); + } + else if (yspec){ + if (ys_module_by_xml(yspec, xp, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find_schemanode((yang_node*)ymod, name); + if (y == NULL && _CLICON_XML_NS_ITERATE){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) + break; + } + } + } else y = NULL; *yresult = y; - return 0; + retval = 0; + done: + return retval; } /*! Help function to qsort for sorting entries in xml child vector diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2a6b1572..8b9af6d0 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -658,15 +658,18 @@ yang_find_schemanode(yang_node *yn, return ysmatch; } -/*! Find first matching data node in all (sub)modules in a yang spec +/*! Find first matching data node in all modules in a yang spec (prefixes) * * @param[in] ysp Yang specification - * @param[in] argument Name of node. If NULL match first + * @param[in] nodeid Name of node. If NULL match first * @param[in] class See yang_class for class of yang nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for nodes. Note that if a child to a module is a choice, * the search is made recursively made to the choice's children. + * @note works for import prefix, but not work for generic XML parsing where + * xmlns and xmlns:ns are used. + * @see yang_find_top_ns */ yang_stmt * yang_find_topnode(yang_spec *ysp, @@ -677,7 +680,7 @@ yang_find_topnode(yang_spec *ysp, yang_stmt *yres = NULL; /* result */ char *prefix = NULL; char *id = NULL; - int i; + int i; if (yang_nodeid_split(nodeid, &prefix, &id) < 0) goto done; @@ -719,7 +722,7 @@ yang_find_topnode(yang_spec *ysp, } /*! Given a yang statement, find the prefix associated to this module - * @param[in] ys Yang statement + * @param[in] ys Yang statement in module tree (or module itself) * @retval NULL Not found * @retval prefix Prefix as char* pointer into yang tree * @code @@ -745,6 +748,34 @@ yang_find_myprefix(yang_stmt *ys) return prefix; } +/*! Given a yang statement, find the namespace URI associated to this module + * @param[in] ys Yang statement in module tree (or module itself) + * @retval NULL Not found + * @retval namespace Namspace URI as char* pointer into yang tree + * @code + * char *myns = yang_find_mynamespace(ys); + * @endcode + * @see yang_find_module_by_namespace + */ +char * +yang_find_mynamespace(yang_stmt *ys) +{ + yang_stmt *ymod; /* My module */ + yang_stmt *ynamespace; + char *namespace = NULL; + + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang module not found"); + goto done; + } + if ((ynamespace = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) == NULL) + goto done; + namespace = ynamespace->ys_argument; + done: + return namespace; +} + + /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -811,7 +842,52 @@ yang_key2str(int keyword) return (char*)clicon_int2str(ykmap, keyword); } -/*! Find top module or sub-module given a statement. +/*! Find top data node among all modules by namespace in xml tree + * @param[in] ysp Yang specification + * @param[in] xt XML node + * @param[out] ymod Yang module (NULL if not found) + * @retval 0 OK + * @retval -1 Error + * @note works for xml namespaces (xmlns / xmlns:ns) + */ +int +ys_module_by_xml(yang_spec *ysp, + cxobj *xt, + yang_stmt **ymodp) +{ + int retval = -1; + yang_stmt *ym = NULL; /* module */ + char *prefix = NULL; + char *namespace = NULL; /* namespace URI */ + + if (ymodp) + *ymodp = NULL; + prefix = xml_namespace(xt); + if (prefix){ + /* Get namespace for prefix */ + if (xml2ns(xt, prefix, &namespace) < 0) + goto done; + } + else{ + /* Get default namespace */ + if (xml2ns(xt, NULL, &namespace) < 0) + goto done; + } + /* No namespace found, give up */ + if (namespace == NULL) + goto ok; + /* We got the namespace, now get the module */ + ym = yang_find_module_by_namespace(ysp, namespace); + /* Set result param */ + if (ymodp && ym) + *ymodp = ym; + ok: + retval = 0; + done: + return retval; +} + +/*! Find the top module or sub-module given a statement from within a yang tree * Ultimate top is yang spec, dont return that * The routine recursively finds ancestors. * @param[in] ys Any yang statement in a yang tree @@ -840,7 +916,7 @@ ys_module(yang_stmt *ys) return ys; } -/*! Find top of tree, the yang specification +/*! Find top of tree, the yang specification from within the tree * @param[in] ys Any yang statement in a yang tree * @retval yspec The top yang specification * @see ys_module @@ -1005,6 +1081,29 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } +/*! Given a yang statement and a namespace, return yang module + * + * @param[in] yspec A yang specification + * @param[in] namespace namespace + * @retval ymod Yang module statement if found + * @retval NULL not found + */ +yang_stmt * +yang_find_module_by_namespace(yang_spec *yspec, + char *namespace) +{ + yang_stmt *ymod = NULL; + + if (namespace == NULL) + goto done; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + if (yang_find((yang_node*)ymod, Y_NAMESPACE, namespace) != NULL) + break; + } + done: + return ymod; +} + /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1984,6 +2083,7 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; + // clicon_debug(1, "%s %s", __FUNCTION__, filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -2903,6 +3003,8 @@ yang_spec_load_dir(clicon_handle h, len = b-base; else len = strlen(base); + /* remove duplicates: there may be cornercases that dont work, eg + * mix of revisions and not? */ for (j = (i+1); j < ndp; j++) if (strncmp(base, dp[j].d_name, len) == 0) break; diff --git a/test/lib.sh b/test/lib.sh index eb6bdbbd..2a66d2d5 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -7,6 +7,12 @@ testnr=0 testname= +# If set to 0, override starting of clixon_backend in test (you bring your own) +: ${BE:=1} + +# If set, enable debugging (of backend) +: ${DBG:=0} + # For memcheck #clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" clixon_cli=clixon_cli @@ -56,6 +62,7 @@ new(){ testname=$1 >&2 echo "Test$testnr [$1]" } +# No CR new2(){ testnr=`expr $testnr + 1` testname=$1 @@ -84,7 +91,7 @@ expectfn(){ # echo "retval:\"$retval\"" # echo "ret:\"$ret\"" # echo "r:\"$r\"" - if [ $r != $retval ]; then + if [ $r != $retval ]; then echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" echo -e "\e[0m:" exit -1 @@ -219,3 +226,29 @@ expectwait(){ fi } +expectmatch(){ + ret=$1 + r=$2 + expret=$3 + expect=$4 + if [ $r != $expret ]; then + echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" + echo -e "\e[0m:" + exit -1 + fi + if [ -z "$ret" -a -z "$expect" ]; then + echo > /dev/null + else + match=$(echo "$ret" | grep -Eo "$expect") + if [ -z "$match" ]; then + err "$expect" "$ret" + fi + if [ -n "$expect2" ]; then + match=`echo "$ret" | grep -EZo "$expect2"` + if [ -z "$match" ]; then + err $expect "$ret" + fi + fi + fi + +} diff --git a/test/nacm.sh b/test/nacm.sh new file mode 100755 index 00000000..01e7ad05 --- /dev/null +++ b/test/nacm.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# Library variable and functions + +USER=$(whoami) + +# Three groups from RFC8341 A.1 (admin extended with $USER) +NGROUPS=$(cat < + + admin + admin + andy + $USER + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + +EOF +) + +# Permit all rule for admin group from RFC8341 A.2 +NADMIN=$(cat < + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + +EOF +) + diff --git a/test/test_cli.sh b/test/test_cli.sh index a93cf0fb..3bf4af77 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -14,7 +14,7 @@ APPNAME=example cfg=$dir/conf_yang.xml cat < $cfg - + $cfg /usr/local/share/$APPNAME/yang /usr/local/share/clixon @@ -31,17 +31,18 @@ cat < $cfg EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -z -f $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg" -sudo $clixon_backend -s init -f $cfg - -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli configure top" @@ -114,6 +115,10 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 122b3add..bcc7b27b 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -46,7 +46,12 @@ module ietf-ip{ } EOF -db='12first-entry13second-entry23third-entryabcastring' + + +xml='12first-entry13second-entry23third-entryabcastring' + +# Without xmlns +xmlxxx='12first-entry13second-entry23third-entryabcastring' run(){ name=$1 @@ -62,12 +67,12 @@ run(){ new "datastore $name init" expectfn "$datastore $conf init" 0 "" - # Whole tree operations new "datastore $name put all replace" - expectfn "$datastore $conf put replace $db" 0 "" + ret=$($datastore $conf put replace "$xml") + expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put all remove" expectfn "$datastore $conf put remove " 0 "" @@ -76,10 +81,13 @@ run(){ expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all merge" - expectfn "$datastore $conf put merge $db" 0 "" + ret=$($datastore $conf put merge "$xml") + expectmatch "$ret" $? "0" "" + +# expectfn "$datastore $conf put merge $xml" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put all delete" expectfn "$datastore $conf put remove " 0 "" @@ -88,10 +96,11 @@ run(){ expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all create" - expectfn "$datastore $conf put create $db" 0 "" + ret=$($datastore $conf put create "$xml") + expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put top create" expectfn "$datastore $conf put create " 0 "" # error @@ -159,7 +168,6 @@ run(){ rm -rf $mydir } -#run keyvalue # cant get the put to work run text rm -rf $dir diff --git a/test/test_feature.sh b/test/test_feature.sh index 86f3ffd2..4bbb334c 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -57,19 +57,21 @@ module $APPNAME{ } } EOF -new "start backend -s init -f $cfg -y $fyang" -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli enabled feature" @@ -94,7 +96,8 @@ new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedapplicationerrorValidation failed]]>]]>$' +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -134,7 +137,7 @@ if [ -z "$match" ]; then fi new "netconf module ietf-netconf" -expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0implement" +expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" match=`echo "$ret" | grep -GZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -159,6 +162,10 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_identity.sh b/test/test_identity.sh index f9498013..1acbc764 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -106,21 +106,23 @@ cat < $fyang } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +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 -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "Set crypto to aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "aes]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' new "netconf validate " expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -180,6 +182,10 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$" new "cli validate" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 719a77e6..72faaa4e 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -72,18 +72,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -# start new backend -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "leafref base config" @@ -142,6 +143,10 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$" new "cli sender template" expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_list.sh b/test/test_list.sh index ff12015a..7e42a23b 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -64,18 +64,20 @@ module $APPNAME{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "minmax: minimal" @@ -123,6 +125,10 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" fi # NYI +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm.sh b/test/test_nacm.sh index afac1df7..c22159c2 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -6,6 +6,7 @@ APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh +. ./nacm.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang @@ -45,30 +46,17 @@ module $APPNAME{ } EOF +# The groups are slightly modified from RFC8341 A.1 +# The rule-list is from A.2 RULES=$(cat < false deny deny deny - - - admin - admin - adm1 - olof - - - limited - wilma - bam-bam - - - guest - guest - guest@example.com - - + + $NGROUPS + guest-acl guest @@ -78,7 +66,8 @@ RULES=$(cat <* deny - Do not allow guests any access to any information. + Do not allow guests any access to the NETCONF + monitoring information. @@ -106,36 +95,27 @@ RULES=$(cat < - - admin-acl - admin - - permit-all - * - * - permit - - Allow the 'admin' group complete access to all operations and data. - - - + + $NADMIN + 0 EOF ) -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -148,10 +128,10 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT new "restconf DELETE whole datastore" -expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null ' new "auth set authentication config" @@ -164,19 +144,19 @@ new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "limited get nacm" @@ -184,20 +164,24 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 997965ff..1db3949f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -7,6 +7,7 @@ APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh +. ./nacm.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang @@ -40,7 +41,12 @@ cat < $fyang module $APPNAME{ yang-version 1.1; namespace "urn:example:clixon"; + prefix ex; + import ietf-routing { + description "For fib-route"; + prefix rt; + } container authentication { description "Example code for enabling www basic auth and some example users"; @@ -82,23 +88,9 @@ cat < $nacmfile deny deny deny - - - admin - admin - adm1 - - - limited - wilma - bam-bam - - - guest - guest - guest@example.com - - + + $NGROUPS + guest-acl guest @@ -136,34 +128,27 @@ cat < $nacmfile - - admin-acl - admin - - permit-all - * - * - permit - - Allow the 'admin' group complete access to all operations and data. - - - + + $NADMIN + EOF -# kill old backend (if any) -new "kill old backend -zf $cfg -y $fyang" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -sleep 1 -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend -zf $cfg -y $fyang" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + sleep 1 + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -175,27 +160,27 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT new "restconf DELETE whole datastore" -expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' new "Set x to 0" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "admin get nacm" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "limited get nacm" @@ -203,19 +188,19 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" -expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" new "cli show conf as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" @@ -224,7 +209,7 @@ new "cli show conf as guest" expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied" new "cli rpc as admin" -expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" new "cli rpc as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny" @@ -235,6 +220,10 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh new file mode 100755 index 00000000..2ab70b4b --- /dev/null +++ b/test/test_nacm_protocol.sh @@ -0,0 +1,231 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# NACM protocol operation rules +# @see RFC 8341 A.1 and A.3 (and permit-all from A.2) +# Tests for three protocol operation rules (all apply to module ietf-netconf) +# deny-kill-session: This rule prevents the "limited" group or the +# "guest" group from invoking the NETCONF protocol +# operation. +# deny-delete-config: This rule prevents the "limited" group or the +# "guest" group from invoking the NETCONF protocol +# operation. +# permit-edit-config: This rule allows the "limited" group to invoke +# the NETCONF protocol operation. This rule will have +# no real effect unless the "exec-default" leaf is set to "deny". +# +# From RFC8040, I conclude that commit/discard should be done automatically +# BY THE SYSTEM +# Otherwise, if the device supports :candidate, all edits to +# configuration nodes in {+restconf}/data are performed in the +# candidate configuration datastore. The candidate MUST be +# automatically committed to running immediately after each successful +# edit. +# Which means that restconf -X DELETE /data translates to edit-config + commit +# WHICH IS allowed. + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh +. ./nacm.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + /usr/local/share/clixon + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + false + internal + +EOF + +cat < $fyang +module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +# The groups are slightly modified from RFC8341 A.1 +# The rule-list is from A.2 +RULES=$(cat < + false + deny + deny + deny + + $NGROUPS + + + guest-limited-acl + limited + guest + + deny-kill-session + ietf-netconf + kill-session + exec + deny + + Do not allow the 'limited' group or the 'guest' group + to kill another session. + + + + deny-delete-config + ietf-netconf + delete-config + exec + deny + + Do not allow the 'limited' group or the 'guest' group + to delete any configurations. + + + + + limited-acl + limited + + permit-edit-config + ietf-netconf + edit-config + exec + permit + + Allow the 'limited' group to edit the configuration. + + + + + $NADMIN + + + 0 +EOF +) + +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +sleep 1 +new "start restconf daemon (-a is enable basic authentication)" +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data & + +sleep $RCWAIT + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "enable nacm" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +new2 "admin get nacm" +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +# Rule 1: deny-kill-session +new "deny-kill-session: limited fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new "deny-kill-session: guest fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new "deny-kill-session: admin ok (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "44]]>]]>" "^]]>]]>$" + +# Rule 2: deny-delete-config +new "deny-delete-config: limited fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new2 "deny-delete-config: guest fail (restconf)" +expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +# In restconf delete-config is translated to edit-config which is permitted +new "deny-delete-config: limited fail (restconf) ok" +expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '' + +new2 "admin get nacm (should be null)" +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null + ' + +new "deny-delete-config: admin ok (restconf)" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" '' + +# Here the whole config is gone so we need to start again +new "auth set authentication config (restart)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "enable nacm" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +# Rule 3: permit-edit-config +new "permit-edit-config: limited ok restconf" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' + +new2 "permit-edit-config: guest fail restconf" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +if [ $BE -ne 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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +#rm -rf $dir # XXX diff --git a/test/test_netconf.sh b/test/test_netconf.sh index e66aae53..a0eae7d8 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -87,17 +87,21 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +# Bring your own backend +if [ $BE -ne 0 ]; then + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "netconf hello" @@ -222,6 +226,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +# XXX NOTE that this does not actually kill a running session - and may even kill some random process,... new "kill-session" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "44]]>]]>" "^]]>]]>$" @@ -238,10 +243,10 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' "^ipv4" -new "netconf rpc without namespace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +new "netconf rpc without namespace (iterate kludge should work)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -249,6 +254,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" new "netconf client-side rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_order.sh b/test/test_order.sh index a44586cd..a06a036b 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -107,18 +107,19 @@ cat < $dbdir/running_db EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend" -# start new backend -sudo $clixon_backend -s running -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend" + sudo $clixon_backend -s running -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi # Check as file @@ -171,6 +172,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_perf.sh b/test/test_perf.sh index a2c36cf8..d03fcbde 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -12,7 +12,7 @@ elif [ $# = 2 ]; then req=$2 else echo "Usage: $0 [ []]" - exit 1 + exit 1 # Scaling fi APPNAME=example # include err() and new() functions and creates $dir @@ -58,18 +58,20 @@ cat < $cfg EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg" -y $fyang +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -103,10 +105,10 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf commit large config again" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" @@ -151,7 +153,7 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add $req small leaf-list config" time -p for (( i=0; i<$req; i++ )); do @@ -163,7 +165,7 @@ new "netconf add small leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" new "netconf commit small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" @@ -171,6 +173,10 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "< new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_restconf.sh b/test/test_restconf.sh index f3b8fa72..f98b87e4 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -86,23 +86,25 @@ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there state='{"state": {"op": "42"}}' -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data & sleep $RCWAIT @@ -128,7 +130,7 @@ expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -157,7 +159,7 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" "" +expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} @@ -319,6 +321,10 @@ fi new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 938aa232..dbddee09 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -46,16 +46,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -140,6 +143,10 @@ expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http:/ new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_stream.sh b/test/test_stream.sh index ce0aefc5..910a29c8 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -102,16 +102,19 @@ cat < $fyang } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -275,6 +278,10 @@ sleep 5 new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_type.sh b/test/test_type.sh index d23d13cb..4ff09bbc 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -192,16 +192,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi fi new "cli set transitive string" @@ -300,6 +303,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + example + /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 + /usr/local/lib/xmldb/text.so + +EOF + +# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang +cat < $fyang3 +module example3{ + prefix ex3; + namespace "urn:example:example3"; + typedef u{ + type union { + type int32{ + range "4..44"; + } + type enumeration { + enum "unbounded"; + } + } + } + typedef t{ + type string; + } +} +EOF +cat < $fyang2 +module example2{ + import example3 { prefix ex3; } + namespace "urn:example:example2"; + prefix ex2; + grouping gr2 { + leaf talle{ + type ex3:t; + } + leaf ulle{ + type ex3:u; + } + } +} +EOF +cat < $fyang +module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import example2 { prefix ex2; } + container c{ + description "transitive type- exists in ex3"; + uses ex2:gr2; + } +} +EOF + +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi +fi + +new "cli set transitive string" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle x" 0 "^$" + +new "cli set transitive union" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 33" 0 "^$" + +new "cli set transitive union error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle kalle" 255 "" + +if [ $BE -ne 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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_when_must.sh b/test/test_when_must.sh index f83edd78..6e3b971f 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -90,18 +90,19 @@ module $APPNAME{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "when: add static route" @@ -140,6 +141,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn Ethernet MTU must be 1500]]>]]>" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_yang.sh b/test/test_yang.sh index 82b5bba2..9a3f7cfe 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -13,7 +13,6 @@ fyangerr=$dir/err.yang cat < $cfg $cfg - $dir /usr/local/share/clixon $APPNAME @@ -141,18 +140,20 @@ module $APPNAME{ ex:not-defined ARGUMENT; } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli defined extension" @@ -278,6 +279,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index ae4f56e3..9bcae5fa 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -69,28 +69,33 @@ cat < $cfg 1 EOF -# kill old backend (if any) -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 -sudo $clixon_backend -s init -f $cfg -if [ $? -ne 0 ]; then - err +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" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "1. Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' -new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' + +if [ $BE -ne 0 ]; then + exit # BE +fi new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh new file mode 100755 index 00000000..215e91f3 --- /dev/null +++ b/test/test_yang_namespace.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# Yang specifics: multi-keys and empty type +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/example.yang +fyang2=$dir/example2.yang + +# /usr/local/share/$APPNAME/yang +cat < $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + /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 + /usr/local/lib/xmldb/text.so + true + +EOF + +# For testing namespaces - +# x.y is different type. Here it is string whereas in fyang it is list. +# +cat < $fyang2 +module example2{ + yang-version 1.1; + prefix ex2; + namespace "urn:example:clixon2"; + container x { + leaf y { + type uint32; + } + } +} +EOF + +cat < $fyang +module example{ + yang-version 1.1; + prefix ex; + namespace "urn:example:clixon"; + import ietf-routing { + description "defines fib-route"; + prefix rt; + } + leaf x{ + type int32; + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + 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 + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + + + +new "netconf xmlns module ex" +expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlfn in return" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" + +new "netconf xmlns module ex2" +expecteof "$clixon_netconf -qf $cfg" 0 '99]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4299]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf xmlns:ex" +expecteof "$clixon_netconf -qf $cfg" 0 '4422]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns:ex" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4422]]>]]>$" + +new "netconf xmlns:ex2" +expecteof "$clixon_netconf -qf $cfg" 0 '9999]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns:ex2" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^44229999]]>]]>$" + +# rpc + +if [ $BE -ne 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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +rm -rf $dir diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 1f78d691..162c3c0b 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -123,7 +123,7 @@ module clixon-config { "Supported features as used by YANG feature/if-feature value is: :, where and are either names, or the special character '*'. - *:an* means enable all features + *:* means enable all features :* means enable all features in the specified module *: means enable the specific feature in all modules"; type string; @@ -150,7 +150,7 @@ module clixon-config { leaf CLICON_YANG_MAIN_DIR { type string; description - "If given, load all modules in this directory. + "If given, load all modules in this directory (all .yang files) See also CLICON_YANG_DIR which specifies a path of dirs"; } leaf CLICON_YANG_MODULE_MAIN { @@ -353,6 +353,15 @@ module clixon-config { Only works for Yang specified XML. If not set, all lists accessed via linear search."; } + leaf CLICON_XML_NS_ITERATE { + type boolean; + default true; + description + "If set, iterate through modules to find the matching datanode + or rpc if no xmlns attribute specifies namespace. + This is loose semantics of finding namespaces. + And it is wrong, but is the way Clixon originally was written."; + } leaf CLICON_USE_STARTUP_CONFIG { type int32; default 0; @@ -450,4 +459,12 @@ module clixon-config { } } + rpc debug { + description "Set debug level of backend."; + input { + leaf level { + type uint32; + } + } + } } From f872c7e295422881168465e3e6c062314a27dff0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 21 Dec 2018 01:33:41 +0100 Subject: [PATCH 15/72] * More precise Yang validation and better error messages * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed * Removed delete-config support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Some restconf error messages contained "rpc-reply" or "rpc-error" which have now been removed. * Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47) --- CHANGELOG.md | 15 +- README.md | 3 +- ROADMAP.md | 20 ++- apps/backend/backend_client.c | 31 ++-- apps/backend/backend_commit.c | 106 +++++++++----- apps/backend/backend_commit.h | 2 +- apps/backend/backend_main.c | 25 +++- apps/netconf/netconf_main.c | 31 ++-- apps/netconf/netconf_rpc.c | 37 +++-- apps/restconf/restconf_lib.c | 9 +- apps/restconf/restconf_methods.c | 161 +++++++++++++-------- example/example_backend.c | 49 ++++++- lib/clixon/clixon_netconf_lib.h | 6 +- lib/clixon/clixon_xml.h | 4 +- lib/clixon/clixon_xml_map.h | 7 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_netconf_lib.c | 81 +++++------ lib/src/clixon_options.c | 12 +- lib/src/clixon_proto_client.c | 2 +- lib/src/clixon_xml.c | 26 ++-- lib/src/clixon_xml_map.c | 235 +++++++++++++++++++++++-------- lib/src/clixon_xml_parse.y | 16 +-- lib/src/clixon_xml_sort.c | 3 + lib/src/clixon_yang.c | 2 +- test/test_cli.sh | 6 +- test/test_feature.sh | 5 +- test/test_identity.sh | 4 +- test/test_leafref.sh | 8 +- test/test_list.sh | 2 +- test/test_nacm.sh | 12 +- test/test_nacm_ext.sh | 12 +- test/test_nacm_protocol.sh | 14 +- test/test_netconf.sh | 4 +- test/test_order.sh | 4 +- test/test_perf.sh | 2 +- test/test_restconf.sh | 57 ++++---- test/test_restconf2.sh | 10 +- test/test_rpc.sh | 125 ++++++++++++++++ test/test_stream.sh | 8 +- test/test_type.sh | 6 +- test/test_union.sh | 2 +- test/test_when_must.sh | 8 +- test/test_yang.sh | 8 +- test/test_yang_load.sh | 28 ++-- test/test_yang_namespace.sh | 2 +- 45 files changed, 807 insertions(+), 405 deletions(-) create mode 100755 test/test_rpc.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 160f6254..0035a93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features - * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. @@ -20,6 +19,8 @@ * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling + * More precise Yang validation and better error messages + * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list @@ -27,18 +28,20 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * Correct XML namespace handling * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: -``` + ``` # Wrong but accepted # Correct -``` + ``` * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) * XML to JSON translator support for mapping xmlns attribute to module name prefix. * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) +* Removed delete-config support for candidate db since it is not supported in RFC6241. +* Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. * XML namespace handling is corrected (see above) * For backward compatibility set config option CLICON_XML_NS_ITERATE @@ -49,6 +52,8 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added example_rpc RPC to example backend +* Renamed xml_namespace[_set]() to xml_prefix[_set]() * Changed all make tags --> make TAGS * Keyvalue datastore removed (it has been disabled since 3.3.3) * Removed return value ymodp from yang parse functions (eg yang_parse()). @@ -60,8 +65,10 @@ * config", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) goto done; goto ok; } else{ - /* yang spec may be set to anyxml by ingress yang check,...*/ + /* yang spec may be set to anyxmly by ingress yang check,...*/ if (xml_spec(xc) != NULL) xml_spec_set(xc, NULL); /* Populate XML with Yang spec (why not do this in parser?) @@ -530,7 +530,7 @@ from_client_lock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -589,7 +589,7 @@ from_client_unlock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -651,7 +651,7 @@ from_client_kill_session(clicon_handle h, if ((x = xml_find(xe, "session-id")) == NULL || (str = xml_find_value(x, "body")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) goto done; goto ok; } @@ -709,7 +709,7 @@ from_client_copy_config(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((source = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto ok; } @@ -724,7 +724,7 @@ from_client_copy_config(clicon_handle h, goto ok; } if ((target = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -777,7 +777,7 @@ from_client_delete_config(clicon_handle h, if ((target = netconf_db_find(xe, "target")) == NULL|| strcmp(target, "running")==0){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -856,7 +856,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//stopTime")) != NULL){ if ((stoptime = xml_find_value(x, "body")) != NULL && str2time(stoptime, &stop) < 0){ - if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -864,7 +864,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//startTime")) != NULL){ if ((starttime = xml_find_value(x, "body")) != NULL && str2time(starttime, &start) < 0){ - if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -925,7 +925,7 @@ from_client_debug(clicon_handle h, char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) goto done; goto ok; } @@ -993,13 +993,10 @@ from_client_msg(clicon_handle h, /* Populate incoming XML tree with yang */ if (xml_spec_populate_rpc(h, x, yspec) < 0) goto done; - if ((ret = xml_yang_validate_rpc(x)) < 0) + if ((ret = xml_yang_validate_rpc(x, cbret)) < 0) goto done; - if (ret == 0){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) - goto done; + if (ret == 0) goto reply; - } xe = NULL; username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { @@ -1059,7 +1056,7 @@ from_client_msg(clicon_handle h, } else if (strcmp(rpc, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto reply; } diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 3aa2f82d..88ea9e99 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -81,68 +81,86 @@ * are if code comes via XML/NETCONF. * @param[in] yspec Yang spec * @param[in] td Transaction data + * @param[out] cbret Cligen buffer containing netconf error (if retval == 0) + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int generic_validate(yang_spec *yspec, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; cxobj *x1; cxobj *x2; yang_stmt *ys; int i; + int ret; /* All entries */ - if (xml_apply(td->td_target, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) goto done; - + if (ret == 0) + goto fail; /* changed entries */ for (i=0; itd_clen; i++){ x1 = td->td_scvec[i]; /* source changed */ x2 = td->td_tcvec[i]; /* target changed */ - if (xml_yang_validate_add(x2, NULL) < 0) + /* Should this be recursive? */ + if ((ret = xml_yang_validate_add(x2, cbret)) < 0) goto done; + if (ret == 0) + goto fail; } /* deleted entries */ for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); if (ys && yang_mandatory(ys)){ - clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", - xml_name(x1)); - goto done; + if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Removed mandatory variable") < 0) + goto done; + goto fail; } } /* added entries */ for (i=0; itd_alen; i++){ x2 = td->td_avec[i]; - if (xml_apply0(x2, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_add, NULL) < 0) + if ((ret = xml_yang_validate_add(x2, cbret)) < 0) goto done; + if (ret == 0) + goto fail; } - retval = 0; + // ok: + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Common code of candidate_validate and candidate_commit * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @retval 0 OK - * @retval -1 Fatal error or validation fail - * @note Need to differentiate between error and validation fail + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK + * @note Need to differentiate between error and validation fail + * (only done for generic_validate) */ static int validate_common(clicon_handle h, char *candidate, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; yang_spec *yspec; int i; cxobj *xn; - + int ret; + if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -193,9 +211,12 @@ validate_common(clicon_handle h, if (plugin_transaction_begin(h, td) < 0) goto done; - /* 5. Make generic validation on all new or changed data. */ - if (generic_validate(yspec, td) < 0) + /* 5. Make generic validation on all new or changed data. + Note this is only call that uses 3-values */ + if ((ret = generic_validate(yspec, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate(h, td) < 0) @@ -204,9 +225,12 @@ validate_common(clicon_handle h, /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete(h, td) < 0) goto done; - retval = 0; + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Do a diff between candidate and running, then start a commit transaction @@ -216,24 +240,30 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval 0 OK - * @retval -1 Fatal error or validation fail + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK * @note Need to differentiate between error and validation fail + * (only done for validate_common) */ int candidate_commit(clicon_handle h, - char *candidate) + char *candidate, + cbuf *cbret) { - int retval = -1; + int retval = -1; transaction_data_t *td = NULL; + int ret; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate) */ - if (validate_common(h, candidate, td) < 0) + /* Common steps (with validate). Note this is only call that uses 3-values */ + if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 7. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) @@ -252,21 +282,23 @@ candidate_commit(clicon_handle h, /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); - /* 8. Copy running back to candidate in case end functions updated running */ if (xmldb_copy(h, "running", candidate) < 0){ /* ignore errors or signal major setback ? */ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue"); goto done; } - retval = 0; + retval = 1; done: - /* In case of failure, call plugin transaction termination callbacks */ - if (retval < 0 && td) + /* In case of failure (or error), call plugin transaction termination callbacks */ + if (retval < 1 && td) plugin_transaction_abort(h, td); if (td) transaction_free(td); return retval; + fail: + retval = 0; + goto done; } /*! Commit changes from candidate to running @@ -283,6 +315,7 @@ from_client_commit(clicon_handle h, int retval = -1; int piddb; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; /* Check if target locked by other client */ piddb = xmldb_islocked(h, "running"); @@ -296,9 +329,10 @@ from_client_commit(clicon_handle h, goto done; goto ok; } - if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */ + if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */ clicon_debug(1, "Commit candidate failed"); - if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0) + if (ret < 0) + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; goto ok; } @@ -367,6 +401,7 @@ from_client_validate(clicon_handle h, { int retval = -1; transaction_data_t *td = NULL; + int ret; if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) @@ -379,11 +414,12 @@ from_client_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* Common steps (with commit) */ - if (validate_common(h, db, td) < 0){ + if ((ret = validate_common(h, db, td, cbret)) < 1){ clicon_debug(1, "Validate %s failed", db); - /* XXX: candidate_validate should have proper error handling */ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; + if (ret < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + } goto ok; } /* Optionally write (potentially modified) tree back to candidate */ diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 6be05858..45b0f922 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -43,6 +43,6 @@ int from_client_validate(clicon_handle h, char *db, cbuf *cbret); int from_client_commit(clicon_handle h, int pid, cbuf *cbret); int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret); -int candidate_commit(clicon_handle h, char *db); +int candidate_commit(clicon_handle h, char *db, cbuf *cbret); #endif /* _BACKEND_COMMIT_H_ */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ec4a99d7..8e433d85 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -386,6 +386,7 @@ startup_mode_running(clicon_handle h, char *extraxml_file) { int retval = -1; + cbuf *cbret = NULL; /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) @@ -405,15 +406,20 @@ startup_mode_running(clicon_handle h, /* Clear running db */ if (db_reset(h, "running") < 0) goto done; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Commit original running. Assume -1 is validate fail */ - if (candidate_commit(h, "candidate") < 0){ + if (candidate_commit(h, "candidate", cbret) < 1){ /* (1) We cannot differentiate between fatal errors and validation * failures * (2) If fatal error, we should exit * (3) If validation fails we cannot continue. How could we? * (4) Need to restore the running db since we destroyed it above */ - clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting.", __FUNCTION__); + clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); /* Reinstate original */ if (xmldb_copy(h, "candidate", "running") < 0) goto done; @@ -424,6 +430,8 @@ startup_mode_running(clicon_handle h, goto done; retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xmldb_delete(h, "tmp") < 0) goto done; return retval; @@ -455,6 +463,7 @@ startup_mode_startup(clicon_handle h, char *extraxml_file) { int retval = -1; + cbuf *cbret = NULL; /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) @@ -478,13 +487,19 @@ startup_mode_startup(clicon_handle h, /* Clear running db */ if (db_reset(h, "running") < 0) goto done; + /* Create return buffer (not used) */ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Commit startup */ - if (candidate_commit(h, "startup") < 0){ /* diff */ + if (candidate_commit(h, "startup", cbret) < 1){ /* diff */ /* We cannot differentiate between fatal errors and validation * failures * In both cases we copy back the original running and quit */ - clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting.", __FUNCTION__); + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); if (xmldb_copy(h, "backup", "running") < 0) goto done; goto done; @@ -494,6 +509,8 @@ startup_mode_startup(clicon_handle h, goto done; retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xmldb_delete(h, "backup") < 0) goto done; if (xmldb_delete(h, "tmp") < 0) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 55ee628b..cc4c9710 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -83,15 +83,17 @@ static int process_incoming_packet(clicon_handle h, cbuf *cb) { - char *str; - char *str0; - cxobj *xreq = NULL; /* Request (in) */ - int isrpc = 0; /* either hello or rpc */ - cbuf *cbret = NULL; - cxobj *xret = NULL; /* Return (out) */ - cxobj *xrpc; - cxobj *xc; + int retval = -1; + char *str; + char *str0; + cxobj *xreq = NULL; /* Request (in) */ + int isrpc = 0; /* either hello or rpc */ + cbuf *cbret = NULL; + cxobj *xret = NULL; /* Return (out) */ + cxobj *xrpc; + cxobj *xc; yang_spec *yspec; + int ret; clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); @@ -115,15 +117,12 @@ process_incoming_packet(clicon_handle h, } free(str0); if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ - int ret; isrpc++; - if ((ret = xml_yang_validate_rpc(xrpc)) < 0) + if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) - goto done; netconf_output(1, cbret, "rpc-error"); - goto done; + goto ok; } } else @@ -168,6 +167,8 @@ process_incoming_packet(clicon_handle h, } } } + ok: + retval = 0; done: if (xreq) xml_free(xreq); @@ -175,7 +176,7 @@ process_incoming_packet(clicon_handle h, xml_free(xret); if (cbret) cbuf_free(cbret); - return 0; + return retval; } /*! Get netconf message: detect end-of-msg @@ -229,7 +230,7 @@ netconf_input_cb(int s, /* Remove trailer */ *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; if (process_incoming_packet(h, cb) < 0) - goto done; + ; //goto done; // ignore errors if (cc_closed) break; cbuf_reset(cb); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index f53b8675..64001137 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -876,12 +877,16 @@ netconf_application_rpc(clicon_handle h, cbuf *cb = NULL; cbuf *cbret = NULL; int ret; - + /* First check system / netconf RPC:s */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; } + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } /* Find yang rpc statement, return yang rpc statement if found Check application RPC */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -917,15 +922,18 @@ netconf_application_rpc(clicon_handle h, xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xn, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0) goto done; - if (xml_yang_validate_add(xn, NULL) < 0) + if (ret == 0){ + netconf_output(1, cbret, "rpc-error"); + goto ok; + } + if ((ret = xml_yang_validate_add(xn, cbret)) < 0) goto done; - } - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "cbuf_new"); - goto done; + if (ret == 0){ + netconf_output(1, cbret, "rpc-error"); + goto ok; + } } /* Look for local (client-side) netconf plugins. */ if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) @@ -943,11 +951,18 @@ netconf_application_rpc(clicon_handle h, xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xoutput, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(xoutput, cbret)) < 0) goto done; - if (xml_yang_validate_add(xoutput, NULL) < 0) + if (ret == 0){ + clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret)); + goto ok; + } + if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) goto done; + if (ret == 0){ + clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret)); + goto ok; + } } retval = 1; /* handled by callback */ goto done; diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 75c06699..be214fbb 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -71,11 +71,12 @@ static const map_str2int netconf_restconf_map[] = { {"missing-attribute", 400}, {"bad-attribute", 400}, {"unknown-attribute", 400}, + {"missing-element", 400}, {"bad-element", 400}, {"unknown-element", 400}, {"unknown-namespace", 400}, - {"access-denied", 401}, - {"access-denied", 403}, + {"access-denied", 401}, /* or 403 */ + {"access-denied", 403}, {"lock-denied", 409}, {"resource-denied", 409}, {"rollback-failed", 500}, @@ -436,7 +437,8 @@ api_return_err(clicon_handle h, goto ok; } tagstr = xml_body(xtag); - code = restconf_err2code(tagstr); + if ((code = restconf_err2code(tagstr)) < 0) + code = 500; /* internal server error */ if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; if (xml_name_set(xerr, "error") < 0) @@ -448,6 +450,7 @@ api_return_err(clicon_handle h, else if (xml2json_cbuf(cb, xerr, pretty) < 0) goto done; + FCGX_SetExitStatus(code, r->out); /* Created */ FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", use_xml?"xml":"json"); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 42d4d331..b8beb068 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -190,7 +190,7 @@ api_data_get2(clicon_handle h, yang_spec *yspec; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced */ - cxobj *xe; + cxobj *xe = NULL; cxobj **xvec = NULL; size_t xlen; int i; @@ -206,8 +206,9 @@ api_data_get2(clicon_handle h, if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); @@ -215,8 +216,9 @@ api_data_get2(clicon_handle h, if (clicon_rpc_get(h, path, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* We get return via netconf which is complete tree from root @@ -434,16 +436,18 @@ api_data_post(clicon_handle h, if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the @@ -452,8 +456,9 @@ api_data_post(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -662,16 +667,18 @@ api_data_put(clicon_handle h, if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* The message-body MUST contain exactly one instance of the @@ -680,8 +687,9 @@ api_data_put(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -705,8 +713,9 @@ api_data_put(clicon_handle h, if (strcmp(xml_name(x), xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ @@ -714,8 +723,9 @@ api_data_put(clicon_handle h, if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -866,7 +876,7 @@ api_data_delete(clicon_handle h, if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, xml_operation2str(op)) < 0) + if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; if ((cbx = cbuf_new()) == NULL) goto done; @@ -1054,7 +1064,6 @@ api_operations_post(clicon_handle h, cxobj *xdata = NULL; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced must be freed */ - cxobj *xer; /* non-malloced error */ cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; @@ -1076,13 +1085,18 @@ api_operations_post(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } for (i=0; i h ?*/ if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xbot, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all(xbot, cbret)) < 0) goto done; - if (xml_yang_validate_add(xbot, NULL) < 0){ - if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) - goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (ret == 0){ /* validation failed */ + clicon_debug(1, "%s err: %s", __FUNCTION__, cbuf_get(cbret)); + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) goto done; + if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } + if ((ret = xml_yang_validate_add(xbot, cbret)) < 0){ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + if (xml_apply0(xbot, CX_ELMNT, xml_default, NULL) < 0) + goto done; } } } - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "cbuf_new"); - goto done; - } + ret = 0; xe = NULL; while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { /* Look for local (client-side) restconf plugins. */ @@ -1208,8 +1238,8 @@ api_operations_post(clicon_handle h, if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; /* Local error: return it and quit */ - if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xer, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -1219,8 +1249,8 @@ api_operations_post(clicon_handle h, if (ret == 0){ /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; - if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xer, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -1240,18 +1270,31 @@ api_operations_post(clicon_handle h, goto done; if ((xoutput=xpath_first(xret, "/")) != NULL){ xml_name_set(xoutput, "output"); -#if 0 - clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); -#endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - if (xml_apply(xoutput, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) goto done; - if (xml_yang_validate_add(xoutput, NULL) < 0) + if (ret == 0){ /* validation failed */ + clicon_debug(1, "%s output validation failed", __FUNCTION__); + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) goto done; + if (ret == 0){ /* validation failed */ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } } /* Sanity check of outgoing XML */ FCGX_SetExitStatus(200, r->out); /* OK */ diff --git a/example/example_backend.c b/example/example_backend.c index 3a420a0e..35fe20d4 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -132,6 +132,7 @@ fib_route(clicon_handle h, /* Clicon handle */ cprintf(cbret, "" "ipv4" "2.3.4.5" + "static" ""); return 0; } @@ -157,16 +158,41 @@ route_count(clicon_handle h, * in [RFC6241]. */ static int -empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* client_entry */ - void *regarg) /* Argument given at register */ +empty_rpc(clicon_handle h, /* Clicon handle */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; } +/*! More elaborate example RPC for testing + * The RPC returns the incoming parameters + */ +static int +example_rpc(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; + cxobj *x = NULL; + + cprintf(cbret, ""); + while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); + retval = 0; + done: + return retval; +} + + /*! Called to get state data from plugin * @param[in] h Clicon handle * @param[in] xpath String with XPATH syntax. or NULL for all @@ -315,22 +341,31 @@ clixon_plugin_init(clicon_handle h) if (example_stream_timer_setup(h) < 0) goto done; - /* Register callback for routing rpc calls */ + /* Register callback for routing rpc calls + */ + if (rpc_callback_register(h, fib_route, NULL, "fib-route"/* Xml tag when callback is made */ ) < 0) goto done; + /* From ietf-routing.yang */ if (rpc_callback_register(h, route_count, NULL, "route-count"/* Xml tag when callback is made */ ) < 0) goto done; - if (rpc_callback_register(h, empty, + /* From example.yang (clicon) */ + if (rpc_callback_register(h, empty_rpc, NULL, "empty"/* Xml tag when callback is made */ ) < 0) goto done; + if (rpc_callback_register(h, example_rpc, + NULL, + "example"/* Xml tag when callback is made */ + ) < 0) + goto done; /* Return plugin API */ return &api; diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index cdf570e9..b22e697b 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -46,9 +46,9 @@ int netconf_too_big(cbuf *cb, char *type, char *message); int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); -int netconf_missing_element(cbuf *cb, char *type, char *info, char *message); -int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); -int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); +int netconf_missing_element(cbuf *cb, char *type, char *element, char *message); +int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); +int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 803e5948..68f332ab 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -99,8 +99,8 @@ extern int _CLICON_XML_NS_ITERATE; char *xml_type2str(enum cxobj_type type); char *xml_name(cxobj *xn); int xml_name_set(cxobj *xn, char *name); -char *xml_namespace(cxobj *xn); -int xml_namespace_set(cxobj *xn, char *name); +char *xml_prefix(cxobj *xn); +int xml_prefix_set(cxobj *xn, char *name); int xml2ns(cxobj *x, char *localname, char **namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index abd1fe50..dba82884 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,9 +43,10 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); -int xml_yang_validate_rpc(cxobj *xrpc); -int xml_yang_validate_add(cxobj *xt, void *arg); -int xml_yang_validate_all(cxobj *xt, void *arg); +int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret); +int xml_yang_validate_add(cxobj *xt, cbuf *cbret); +int xml_yang_validate_all(cxobj *xt, cbuf *cbret); +int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 00778c30..06aa71c9 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -315,7 +315,7 @@ xml2json1_cbuf(cbuf *cb, * Harder if x has a prefix, then that should also be translated to associated * module name */ - prefix = xml_namespace(x); + prefix = xml_prefix(x); if (xml2ns(x, prefix, &namespace) < 0) goto done; if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index ccb88275..6c284fc6 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -80,8 +80,8 @@ netconf_in_use(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "in-use" "%s" + "in-use" "error", type) <0) goto err; @@ -119,8 +119,8 @@ netconf_invalid_value(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "invalid-value" "%s" + "invalid-value" "error", type) <0) goto err; @@ -159,8 +159,8 @@ netconf_too_big(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "too-big" "%s" + "too-big" "error", type) <0) goto err; @@ -200,8 +200,8 @@ netconf_missing_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "missing-attribute" "%s" + "missing-attribute" "%s" "error", type, info) <0) @@ -241,8 +241,8 @@ netconf_bad_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "bad-attribute" "%s" + "bad-attribute" "%s" "error", type, info) <0) @@ -283,8 +283,8 @@ netconf_unknown_attribute(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "unknown-attribute" "%s" + "unknown-attribute" "%s" "error", type, info) <0) @@ -318,18 +318,18 @@ netconf_unknown_attribute(cbuf *cb, int netconf_missing_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "missing-element" "%s" - "%s" + "missing-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -355,24 +355,24 @@ netconf_missing_element(cbuf *cb, * pattern mismatch. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" - * @param[in] info bad-element xml + * @param[in] elemnt Bad element name * @param[in] message Error message */ int netconf_bad_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "bad-element" "%s" - "%s" + "bad-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -397,24 +397,24 @@ netconf_bad_element(cbuf *cb, * An unexpected element is present. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" - * @param[in] info bad-element xml + * @param[in] element Bad element name * @param[in] message Error message */ int netconf_unknown_element(cbuf *cb, char *type, - char *info, + char *element, char *message) { int retval = -1; char *encstr = NULL; if (cprintf(cb, "" - "unknown-element" "%s" - "%s" + "unknown-element" + "%s" "error", - type, info) <0) + type, element) <0) goto err; if (message){ if (xml_chardata_encode(&encstr, "%s", message) < 0) @@ -452,8 +452,8 @@ netconf_unknown_namespace(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "unknown-namespace" "%s" + "unknown-namespace" "%s" "error", type, info) <0) @@ -478,7 +478,8 @@ netconf_unknown_namespace(cbuf *cb, /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Access to the requested protocol operation or data model is denied because + * authorization failed. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message @@ -492,8 +493,8 @@ netconf_access_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "access-denied" "%s" + "access-denied" "error", type) <0) goto err; @@ -517,7 +518,8 @@ netconf_access_denied(cbuf *cb, /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Access to the requested protocol operation or data model is denied because + * authorization failed. * @param[out] xret Error XML tree * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message @@ -538,8 +540,8 @@ netconf_access_denied_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "access-denied" - "%s" + if (xml_parse_va(&xerr, NULL, "%s" + "access-denied" "error", type) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -567,8 +569,8 @@ netconf_lock_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "lock-denied" "protocol" + "lock-denied" "%s" "error", info) <0) @@ -593,7 +595,7 @@ netconf_lock_denied(cbuf *cb, /*! Create Netconf resource-denied error XML tree according to RFC 6241 App A * - * An expected element is missing. + * Request could not be completed because of insufficient resources. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "transport, "rpc", "application", "protocol" * @param[in] message Error message @@ -607,8 +609,8 @@ netconf_resource_denied(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "resource-denied" "%s" + "resource-denied" "error", type) <0) goto err; @@ -647,8 +649,8 @@ netconf_rollback_failed(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "rollback-failed" "%s" + "rollback-failed" "error", type) <0) goto err; @@ -686,8 +688,8 @@ netconf_data_exists(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "data-exists" "application" + "data-exists" "error") <0) goto err; if (message){ @@ -724,8 +726,8 @@ netconf_data_missing(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "data-missing" "application" + "data-missing" "error") <0) goto err; if (message){ @@ -763,8 +765,8 @@ netconf_operation_not_supported(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "operation-not-supported" "%s" + "operation-not-supported" "error", type) <0) goto err; @@ -803,8 +805,8 @@ netconf_operation_failed(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "operation-failed" "%s" + "operation-failed" "error", type) <0) goto err; @@ -850,8 +852,8 @@ netconf_operation_failed_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "operation-failed" - "%s" + if (xml_parse_va(&xerr, NULL, "%s" + "operation-failed" "error", type) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -879,8 +881,8 @@ netconf_malformed_message(cbuf *cb, char *encstr = NULL; if (cprintf(cb, "" - "malformed-message" "rpc" + "malformed-message" "error") <0) goto err; if (message){ @@ -925,8 +927,8 @@ netconf_malformed_message_xml(cxobj **xret, goto done; if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_parse_va(&xerr, NULL, "malformed-message" - "rpc" + if (xml_parse_va(&xerr, NULL, "rpc" + "malformed-message" "error") < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", @@ -990,7 +992,6 @@ netconf_module_load(clicon_handle h) clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded"); goto done; } - /* Enable features (hardcoded here) */ if (xml_parse_string("ietf-netconf:candidate", yspec, &xc) < 0) goto done; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 873d3aaf..45c3948b 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -136,6 +136,8 @@ parse_configfile(clicon_handle h, char *name; char *body; clicon_hash_t *copt = clicon_options(h); + cbuf *cbret = NULL; + int ret; if (filename == NULL || !strlen(filename)){ clicon_err(OE_UNIX, 0, "Not specified"); @@ -167,8 +169,16 @@ parse_configfile(clicon_handle h, } if (xml_apply0(xc, CX_ELMNT, xml_default, yspec) < 0) goto done; - if (xml_apply0(xc, CX_ELMNT, xml_yang_validate_add, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); goto done; + } + if ((ret = xml_yang_validate_add(xc, cbret)) < 0) + goto done; + if (ret == 0){ + clicon_err(OE_CFG, 0, "Config file validation: %s", cbuf_get(cbret)); + goto done; + } while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { name = xml_name(x); body = xml_body(x); diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 0cf421d2..aa3ac4ac 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -444,7 +444,7 @@ clicon_rpc_delete_config(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("<%s/>", + if ((msg = clicon_msg_encode("<%s/>none", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 24bab41b..59292ba9 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -196,10 +196,9 @@ xml_name_set(cxobj *xn, /*! Get namespace of xnode * @param[in] xn xml node * @retval namespace of xml node - * XXX change to xml_localname */ char* -xml_namespace(cxobj *xn) +xml_prefix(cxobj *xn) { return xn->x_prefix; } @@ -209,11 +208,10 @@ xml_namespace(cxobj *xn) * @param[in] localname new namespace, null-terminated string, copied by function * @retval -1 on error with clicon-err set * @retval 0 OK - * XXX change to xml_localname_set */ int -xml_namespace_set(cxobj *xn, - char *localname) +xml_prefix_set(cxobj *xn, + char *localname) { if (xn->x_prefix){ free(xn->x_prefix); @@ -288,7 +286,7 @@ xmlns_check(cxobj *xn, char *xns; while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) - if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 && + if ((xns = xml_prefix(x)) && strcmp(xns, "xmlns")==0 && strcmp(xml_name(x), nsn) == 0) return xml_value(x); return NULL; @@ -311,7 +309,7 @@ xml_localname_check(cxobj *xn, yang_stmt *ys = xml_spec(xn); /* No namespace name - comply */ - if ((nsn = xml_namespace(xn)) == NULL) + if ((nsn = xml_prefix(xn)) == NULL) return 0; /* Check if NSN defined in same node */ if (xmlns_check(xn, nsn) != NULL) @@ -965,7 +963,7 @@ xml_find_type_value(cxobj *xt, char *xprefix; /* xprefix */ while ((x = xml_child_each(xt, x, type)) != NULL) { - xprefix = xml_namespace(x); + xprefix = xml_prefix(x); if (prefix) pmatch = xprefix?strcmp(prefix,xprefix)==0:0; else @@ -1121,7 +1119,7 @@ clicon_xml2file(FILE *f, if (x == NULL) goto ok; name = xml_name(x); - namespace = xml_namespace(x); + namespace = xml_prefix(x); switch(xml_type(x)){ case CX_BODY: if ((val = xml_value(x)) == NULL) /* incomplete tree */ @@ -1246,7 +1244,7 @@ clicon_xml2cbuf(cbuf *cb, char *val; name = xml_name(x); - namespace = xml_namespace(x); + namespace = xml_prefix(x); switch(xml_type(x)){ case CX_BODY: if ((val = xml_value(x)) == NULL) /* incomplete tree */ @@ -1333,10 +1331,10 @@ xmltree2cbuf(cbuf *cb, cprintf(cb, " "); if (xml_type(x) != CX_BODY) cprintf(cb, "%s", xml_type2str(xml_type(x))); - if (xml_namespace(x)==NULL) + if (xml_prefix(x)==NULL) cprintf(cb, " %s", xml_name(x)); else - cprintf(cb, " %s:%s", xml_namespace(x), xml_name(x)); + cprintf(cb, " %s:%s", xml_prefix(x), xml_name(x)); if (xml_value(x)) cprintf(cb, " value:\"%s\"", xml_value(x)); if (x->x_flags) @@ -1612,8 +1610,8 @@ xml_copy_one(cxobj *x0, if ((s = xml_name(x0))) /* malloced string */ if ((xml_name_set(x1, s)) < 0) return -1; - if ((s = xml_namespace(x0))) /* malloced string */ - if ((xml_namespace_set(x1, s)) < 0) + if ((s = xml_prefix(x0))) /* malloced string */ + if ((xml_prefix_set(x1, s)) < 0) return -1; return 0; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 6794b503..2263bde2 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -87,6 +87,7 @@ #include "clixon_xpath.h" #include "clixon_log.h" #include "clixon_err.h" +#include "clixon_netconf_lib.h" #include "clixon_xml_sort.h" #include "clixon_xml_map.h" @@ -230,10 +231,15 @@ xml2cli(FILE *f, /*! Validate xml node of type leafref, ensure the value is one of that path's reference * @param[in] xt XML leaf node of type leafref * @param[in] ytype Yang type statement belonging to the XML node + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error */ static int validate_leafref(cxobj *xt, - yang_stmt *ytype) + yang_stmt *ytype, + cbuf *cbret) { int retval = -1; yang_stmt *ypath; @@ -247,8 +253,9 @@ validate_leafref(cxobj *xt, if ((leafrefbody = xml_body(xt)) == NULL) goto ok; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ - clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); - goto done; + if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Leafref requires path statement") < 0) + goto done; + goto fail; } if (xpath_vec(xt, "%s", &xvec, &xlen, ypath->ys_argument) < 0) goto done; @@ -260,9 +267,9 @@ validate_leafref(cxobj *xt, break; } if (i==xlen){ - clicon_err(OE_DB, 0, "Leafref validation failed, no such leaf: %s", - leafrefbody); - goto done; + if (netconf_bad_element(cbret, "application", leafrefbody, "Leafref validation failed: No such leaf") < 0) + goto done; + goto fail; } ok: retval = 0; @@ -270,6 +277,9 @@ validate_leafref(cxobj *xt, if (xvec) free(xvec); return retval; + fail: + retval = 0; + goto done; } /*! Validate xml node of type identityref, ensure value is a defined identity @@ -285,14 +295,18 @@ validate_leafref(cxobj *xt, * @param[in] xt XML leaf node of type identityref * @param[in] ys Yang spec of leaf * @param[in] ytype Yang type field of type identityref + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error * @see ys_populate_identity where the derived types are set * @see RFC7950 Sec 9.10.2: - */ static int validate_identityref(cxobj *xt, yang_stmt *ys, - yang_stmt *ytype) + yang_stmt *ytype, + cbuf *cbret) { int retval = -1; char *node; @@ -305,37 +319,46 @@ validate_identityref(cxobj *xt, * Always add default prefix because derived identifiers are stored with * prefixes in the base identifiers derived-list. */ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } if ((node = xml_body(xt)) == NULL) return 0; if (strchr(node, ':') == NULL){ prefix = yang_find_myprefix(ys); - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } cprintf(cb, "%s:%s", prefix, node); node = cbuf_get(cb); } /* This is the type's base reference */ if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, no base"); - goto done; + if (netconf_missing_element(cbret, "application", ytype->ys_argument, "Identityref validation failed, no base") < 0) + goto done; + goto fail; } /* This is the actual base identity */ if ((ybaseid = yang_find_identity(ybaseref, ybaseref->ys_argument)) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, no base identity"); - goto done; + if (netconf_missing_element(cbret, "application", ybaseref->ys_argument, "Identityref validation failed, no base identity") < 0) + goto done; + goto fail; } /* Here check if node is in the derived node list of the base identity */ if (cvec_find(ybaseid->ys_cvec, node) == NULL){ - clicon_err(OE_DB, 0, "Identityref validation failed, %s not derived from %s", node, ybaseid->ys_argument); - goto done; + cbuf_reset(cb); + cprintf(cb, "Identityref validation failed, %s not derived from %s", + node, ybaseid->ys_argument); + if (netconf_operation_failed(cbret, "application", cbuf_get(cb)) < 0) + goto done; + goto fail; } - retval = 0; + retval = 1; done: if (cb) cbuf_free(cb); return retval; + fail: + retval = 0; + goto done; } /*! Validate an RPC node @@ -377,24 +400,33 @@ validate_identityref(cxobj *xt, * the same order as they are defined within the "output" statement. */ int -xml_yang_validate_rpc(cxobj *xrpc) +xml_yang_validate_rpc(cxobj *xrpc, + cbuf *cbret) { int retval = -1; yang_stmt *yn=NULL; /* rpc name */ cxobj *xn; /* rpc name */ - yang_stmt *yi=NULL; /* input name */ - cxobj *xi; /* input name */ + int ret; - assert(strcmp(xml_name(xrpc), "rpc")==0); + if (strcmp(xml_name(xrpc), "rpc")){ + clicon_err(OE_XML, EINVAL, "Expected RPC"); + goto done; + } xn = NULL; + /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { if ((yn = xml_spec(xn)) == NULL) goto fail; - xi = NULL; - while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) { - if ((yi = xml_spec(xi)) == NULL) - goto fail; - } + if ((ret = xml_yang_validate_all(xn, cbret)) < 0) + goto fail; + if (ret == 0) + goto fail; + if ((ret = xml_yang_validate_add(xn, cbret)) < 0) + goto fail; + if (ret == 0) + goto fail; + if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0) + goto done; } // ok: /* pass validation */ retval = 1; @@ -408,14 +440,24 @@ xml_yang_validate_rpc(cxobj *xrpc) /*! 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. - * @param[in] xt XML node to be validated - * @retval 0 Valid OK - * @retval -1 Validation failed + * @param[in] xt XML node to be validated + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * @code + * cxobj *x; + * cbuf *cbret = cbuf_new(); + * if ((ret = xml_yang_validate_add(x, cbret)) < 0) + * err; + * if (ret == 0) + * fail; + * @endcode * @see xml_yang_validate_all */ int xml_yang_validate_add(cxobj *xt, - void *arg) + cbuf *cbret) { int retval = -1; cg_var *cv = NULL; @@ -424,11 +466,14 @@ xml_yang_validate_add(cxobj *xt, int i; yang_stmt *ys; char *body; + int ret; + cxobj *x; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ switch (ys->ys_keyword){ + case Y_RPC: case Y_INPUT: case Y_LIST: /* fall thru */ @@ -440,9 +485,9 @@ xml_yang_validate_add(cxobj *xt, if (yang_config(yc)==0) continue; if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", - yc->ys_argument); - goto done; + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; } } break; @@ -463,12 +508,11 @@ xml_yang_validate_add(cxobj *xt, goto done; } if ((ys_cv_validate(cv, ys, &reason)) != 1){ - clicon_err(OE_DB, 0, - "validation of %s failed %s", - ys->ys_argument, reason?reason:""); + if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + goto done; if (reason) free(reason); - goto done; + goto fail; } } break; @@ -476,28 +520,43 @@ xml_yang_validate_add(cxobj *xt, break; } } - retval = 0; + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_add(x, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + retval = 1; done: if (cv) cv_free(cv); return retval; + fail: + retval = 0; + goto done; } /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @param[in] arg Not used - * @retval -1 Validation failed - * @retval 0 Validation OK + * @param[out] cbret Error buffer + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error * @see xml_yang_validate_add * @code - * if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0) + * cxobj *x; + * cbuf *cbret = cbuf_new(); + * if ((ret = xml_yang_validate_all(x, cbret)) < 0) * err; + * if (ret == 0) + * fail; * @endcode */ int xml_yang_validate_all(cxobj *xt, - void *arg) + cbuf *cbret) { int retval = -1; yang_stmt *ys; /* yang node */ @@ -505,13 +564,24 @@ xml_yang_validate_all(cxobj *xt, yang_stmt *ye; /* yang must error-message */ char *xpath; int nr; - + int ret; + cxobj *x; + /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ - if ((ys = xml_spec(xt)) != NULL && - yang_config(ys) != 0){ + ys=xml_spec(xt); + if (ys==NULL){ + if (netconf_unknown_element(cbret, "application", xml_name(xt), NULL) < 0) + goto done; + goto fail; + } + if (ys != NULL && yang_config(ys) != 0){ /* Node-specific validation */ switch (ys->ys_keyword){ + case Y_ANYXML: + case Y_ANYDATA: + goto ok; + break; case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: @@ -520,11 +590,11 @@ xml_yang_validate_all(cxobj *xt, */ if ((yc = yang_find((yang_node*)ys, Y_TYPE, NULL)) != NULL){ if (strcmp(yc->ys_argument, "leafref") == 0){ - if (validate_leafref(xt, yc) < 0) + if (validate_leafref(xt, yc, cbret) < 0) goto done; } else if (strcmp(yc->ys_argument, "identityref") == 0){ - if (validate_identityref(xt, ys, yc) < 0) + if (validate_identityref(xt, ys, yc, cbret) < 0) goto done; } } @@ -568,11 +638,11 @@ xml_yang_validate_all(cxobj *xt, if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) goto done; if (!nr){ - if ((ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL)) != NULL) - clicon_err(OE_DB, 0, "%s", ye->ys_argument); - else - clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); - goto done; + ye = yang_find((yang_node*)yc, Y_ERROR_MESSAGE, NULL); + if (netconf_operation_failed(cbret, "application", + ye?ye->ys_argument:"must xpath validation failed") < 0) + goto done; + goto fail; } } /* "when" sub-node RFC 7950 Sec 7.21.5. Can only be one. */ @@ -581,14 +651,42 @@ xml_yang_validate_all(cxobj *xt, if ((nr = xpath_vec_bool(xt, "%s", xpath)) < 0) goto done; if (!nr){ - clicon_err(OE_DB, 0, "xpath %s validation failed", xml_name(xt)); - goto done; + if (netconf_operation_failed(cbret, "application", + "when xpath validation failed") < 0) + goto done; + goto fail; } } } - retval = 0; + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(x, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + } + ok: + retval = 1; done: return retval; + fail: + retval = 0; + goto done; +} + +int +xml_yang_validate_all_top(cxobj *xt, + cbuf *cbret) +{ + int ret; + cxobj *x; + + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((ret = xml_yang_validate_all(x, cbret)) < 1) + return ret; + } + return 1; } /*! Translate a single xml node to a cligen variable vector. Note not recursive @@ -1310,6 +1408,11 @@ xml_tree_prune_flagged(cxobj *xt, /*! Add default values (if not set) * @param[in] xt XML tree with some node marked + * @param[in] arg Ignored + * Typically called in a recursive apply function: + * @code + * xml_apply(xt, CX_ELMNT, xml_default, NULL); + * @endcode */ int xml_default(cxobj *xt, @@ -1328,7 +1431,8 @@ xml_default(cxobj *xt, goto done; } /* Check leaf defaults */ - if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST){ + if (ys->ys_keyword == Y_CONTAINER || ys->ys_keyword == Y_LIST || + ys->ys_keyword == Y_INPUT){ for (i=0; iys_len; i++){ y = ys->ys_stmt[i]; if (y->ys_keyword != Y_LEAF) @@ -1493,9 +1597,9 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *y=NULL; /* yang node */ yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ - yang_stmt *ya = NULL; /* arg */ + // yang_stmt *ya = NULL; /* arg */ cxobj *x; - cxobj *xi; + // cxobj *xi; int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ @@ -1521,11 +1625,18 @@ xml_spec_populate_rpc(clicon_handle h, if (y){ xml_spec_set(x, y); if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ + /* kludge rpc -> input */ + xml_spec_set(x, yi); +#if 1 + if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; +#else xi = NULL; while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) xml_spec_set(xi, ya); - } + } +#endif } } } diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 7072ed01..7600d249 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -172,7 +172,7 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, goto done; if ((x = xml_new(name, xp, y)) == NULL) goto done; - if (xml_namespace_set(x, prefix) < 0) + if (xml_prefix_set(x, prefix) < 0) goto done; ya->ya_xelement = x; retval = 0; @@ -223,9 +223,9 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, xml_name(x), name); goto done; } - if (xml_namespace(x)!=NULL){ + if (xml_prefix(x)!=NULL){ clicon_err(OE_XML, 0, "XML parse sanity check failed: %s:%s vs %s", - xml_namespace(x), xml_name(x), name); + xml_prefix(x), xml_name(x), name); goto done; } /* Strip pretty-print. Ad-hoc algorithm @@ -263,16 +263,16 @@ xml_parse_bslash2(struct xml_parse_yacc_arg *ya, if (strcmp(xml_name(x), name)){ clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", - xml_namespace(x), + xml_prefix(x), xml_name(x), namespace, name); goto done; } - if (xml_namespace(x)==NULL || - strcmp(xml_namespace(x), namespace)){ + if (xml_prefix(x)==NULL || + strcmp(xml_prefix(x), namespace)){ clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s", - xml_namespace(x), + xml_prefix(x), xml_name(x), namespace, name); @@ -324,7 +324,7 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); - if (prefix && xml_namespace_set(xa, prefix) < 0) + if (prefix && xml_prefix_set(xa, prefix) < 0) goto done; if (xml_value_set(xa, attval) < 0) goto done; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 7e68d2b8..befb5bcb 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -118,6 +118,9 @@ xml_child_spec(char *name, } else y = NULL; + /* kludge rpc -> input */ + if (y && y->ys_keyword == Y_RPC && yang_find((yang_node*)y, Y_INPUT, NULL)) + y = yang_find((yang_node*)y, Y_INPUT, NULL); *yresult = y; retval = 0; done: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 8b9af6d0..68c9ce9f 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -862,7 +862,7 @@ ys_module_by_xml(yang_spec *ysp, if (ymodp) *ymodp = NULL; - prefix = xml_namespace(xt); + prefix = xml_prefix(xt); if (prefix){ /* Get namespace for prefix */ if (xml2ns(xt, prefix, &namespace) < 0) diff --git a/test/test_cli.sh b/test/test_cli.sh index 3bf4af77..aa1379d9 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -13,8 +13,10 @@ APPNAME=example . ./lib.sh cfg=$dir/conf_yang.xml +# Use yang in example + cat < $cfg - + $cfg /usr/local/share/$APPNAME/yang /usr/local/share/clixon @@ -115,7 +117,7 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_feature.sh b/test/test_feature.sh index 4bbb334c..dfb99172 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -96,8 +96,7 @@ new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedapplicationerrorValidation failed]]>]]>$' -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -162,7 +161,7 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_identity.sh b/test/test_identity.sh index 1acbc764..14d6b996 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -162,7 +162,7 @@ new "Set crypto to foo:bar" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" new "cli set crypto to mc:aes" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto mc:aes" 0 "^$" @@ -182,7 +182,7 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$" new "cli validate" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 72faaa4e..6e66c7f3 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -105,8 +105,8 @@ expecteof "$clixon_netconf -qf $cfg" 0 'eth3
10.0.4.6
]]>]]>" "^]]>]]>$" -new "leafref validate XXX shouldnt really be operation-failed, more work in validate code" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" +new "leafref validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -126,7 +126,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$' new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" @@ -143,7 +143,7 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$" new "cli sender template" expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_list.sh b/test/test_list.sh index 7e42a23b..37fb8c6c 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -125,7 +125,7 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" fi # NYI -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm.sh b/test/test_nacm.sh index c22159c2..afd1c29d 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -141,10 +141,10 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new2 "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} @@ -164,21 +164,21 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 1db3949f..2701b2da 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -170,10 +170,10 @@ new "Set x to 0" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" new2 "auth get (no user: access denied)" -expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} @@ -188,16 +188,16 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" @@ -220,7 +220,7 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 2ab70b4b..aebaa843 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -147,7 +147,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT @@ -166,20 +166,20 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x" # Rule 1: deny-kill-session new "deny-kill-session: limited fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new "deny-kill-session: guest fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new "deny-kill-session: admin ok (netconf)" expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "44]]>]]>" "^]]>]]>$" # Rule 2: deny-delete-config new "deny-delete-config: limited fail (netconf)" -expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^protocolaccess-deniederroraccess denied]]>]]>$" new2 "deny-delete-config: guest fail (restconf)" -expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' # In restconf delete-config is translated to edit-config which is permitted new "deny-delete-config: limited fail (restconf) ok" @@ -207,12 +207,12 @@ new "permit-edit-config: limited ok restconf" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' new2 "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_netconf.sh b/test/test_netconf.sh index a0eae7d8..7b060875 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -209,7 +209,7 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" new "netconf get state operation" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" @@ -254,7 +254,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" new "netconf client-side rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_order.sh b/test/test_order.sh index a06a036b..cb9eb780 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -139,7 +139,7 @@ new "get each ordered-by user leaf-list" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" new "delete candidate" -expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" # LEAF_LISTS @@ -172,7 +172,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_perf.sh b/test/test_perf.sh index d03fcbde..66c7a5b1 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -173,7 +173,7 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "< new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index f98b87e4..03d20bd9 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -52,14 +52,6 @@ module example{ } rpc empty { } - rpc input { - input { - } - } - rpc output { - output { - } - } rpc client-rpc { description "Example local client-side rpc"; input { @@ -104,12 +96,15 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT new "restconf tests" +new2 "restconf rpc using POST json without mandatory element" +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' + new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " @@ -125,12 +120,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:input": null,"example:output": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"example:empty": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -161,6 +156,9 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" new "restconf empty rpc" expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" +new2 "restconf empty rpc with extra args (should fail)" +expecteq "$(curl -s -X POST -d {\"input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' + new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' @@ -170,7 +168,7 @@ expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "No yang node found: badmodule:state"}}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No yang node found: badmodule:state"}}} ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) @@ -215,10 +213,10 @@ new "restconf Add subtree to datastore using POST" expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work -#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} @@ -244,13 +242,13 @@ expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} @@ -263,7 +261,7 @@ new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" @@ -273,37 +271,38 @@ expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' -new2 "restconf rpc using POST json w/o mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' -new2 "restconf rpc non-existing rpc w/o namespace" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +new2 "restconf rpc using POST json without mandatory element" +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' + +new2 "restconf rpc non-existing rpc without namespace" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' new2 "restconf rpc missing name" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "routing-instance-name"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) -expect="ipv42.3.4.5" +expect="ipv42.3.4.5static" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang module not found"}}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) @@ -321,7 +320,7 @@ fi new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index dbddee09..ee820082 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -84,16 +84,16 @@ new "restconf GET if-type" expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '"error-message": "Missing mandatory variable: type"' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" new2 "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new2 "restconf POST from top" -expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" @@ -138,12 +138,12 @@ new "restconf PUT add interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_rpc.sh b/test/test_rpc.sh new file mode 100755 index 00000000..b1b04251 --- /dev/null +++ b/test/test_rpc.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# RPC tests +# Validate parameters in restconf and netconf, check namespaces, etc +# See rfc8040 3.6 +APPNAME=example + +# include err() and new() functions and creates $dir +. ./lib.sh +cfg=$dir/conf.xml + +# Use yang in example +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + false + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +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" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg" -s /bin/sh www-data & + +sleep $RCWAIT + +new "rpc tests" + +# 1.First some positive tests vary media types +# +new2 "restconf example rpc json/json default - no headers" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc json/json change y default" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "99"}} + ' + +new2 "restconf example rpc json/json" +# XXX example:input example:output +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc xml/json" +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} + ' + +new2 "restconf example rpc json/xml" +# 042 + ' + +new2 "restconf example rpc xml/xml" +# 0' http://localhost/restconf/operations/example:example)" '042 + ' + +new "netconf example rpc" +expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' + +# 2. Then error cases +# +new2 "restconf omit mandatory" +expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' + +new2 "restconf add extra" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' + +new2 "restconf wrong method" +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' + +new2 "restconf edit-config missing mandatory" +expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' + +new "netconf kill-session missing session-id mandatory" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable$' + +# edit-config? + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_stream.sh b/test/test_stream.sh index 910a29c8..19ad9802 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -150,10 +150,10 @@ new "netconf EXAMPLE subscription with filter classifier" expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^invalid-valueapplicationerrorNo such stream]]>]]>$' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^bad-elementapplicationstartTimeerrorExpected timestamp]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorExpected timestamp]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") @@ -176,7 +176,7 @@ sleep 2 # Restconf stream subscription RFC8040 Sec 6.3 # Start Subscription w error new "restconf monitor event nonexist stream" -expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 'invalid-valueapplicationerrorNo such stream' 2 +expectwait 'curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" http://localhost/streams/NOTEXIST' 0 'applicationinvalid-valueerrorNo such stream' 2 # 2a) start subscription 8s - expect 1-2 notifications new "2a) start subscriptions 8s - expect 1-2 notifications" @@ -278,7 +278,7 @@ sleep 5 new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_type.sh b/test/test_type.sh index 4ff09bbc..1261d2ba 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -217,7 +217,7 @@ new "netconf set transitive string error" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" new "netconf validate should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of talle failed regexp match fail" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" 'applicationbad-elementtalleerrorregexp match fail: "9xx" does not match \[a-z\]\[0-9\]\*]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -244,7 +244,7 @@ new "netconf set transitive union error int" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" new "netconf validate should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorvalidation of ulle failed" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationbad-elementulleerror'55' does not match enumeration]]>]]>$" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -303,7 +303,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" new "when: validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorxpath static-routes validation failed]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -133,15 +133,15 @@ new "must: add atm interface" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" new "must: atm validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn ATM MTU must be 64 .. 17966]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn ATM MTU must be 64 .. 17966]]>]]>$" new "must: add eth interface" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" new "must: eth validate fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn Ethernet MTU must be 1500]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn Ethernet MTU must be 1500]]>]]>" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_yang.sh b/test/test_yang.sh index 9a3f7cfe..1d953dc5 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -212,7 +212,7 @@ expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" new "netconf set presence and not present" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -233,7 +233,7 @@ new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf delete candidate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'none]]>]]>' "^]]>]]>$" # Check 3-keys new "netconf add one 3-key entry" @@ -262,7 +262,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'none]]>]]>' "^]]>]]>$" new "netconf commit empty candidate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -279,7 +279,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 9bcae5fa..90fc10e4 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -88,12 +88,12 @@ new "1. Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' -if [ $BE -ne 0 ]; then +if [ $BE -eq 0 ]; then exit # BE fi @@ -140,10 +140,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' new "Kill backend" # Check if premature kill @@ -184,10 +184,10 @@ new "Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill @@ -229,10 +229,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill @@ -273,7 +273,7 @@ new "Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -318,7 +318,7 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -365,7 +365,7 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' @@ -411,10 +411,10 @@ new "Set oldex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 215e91f3..19447f80 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -122,7 +122,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " Date: Fri, 21 Dec 2018 14:44:59 +0100 Subject: [PATCH 16/72] netconf error handling and test summary script --- CHANGELOG.md | 3 +- apps/netconf/clixon_netconf.h | 1 + apps/netconf/netconf_lib.c | 37 ++++- apps/netconf/netconf_lib.h | 1 + apps/netconf/netconf_main.c | 9 +- apps/netconf/netconf_rpc.c | 8 +- apps/restconf/restconf_lib.c | 2 + apps/restconf/restconf_methods.c | 11 +- example/example.yang | 55 ++++++ lib/clixon/clixon_netconf_lib.h | 3 + lib/src/clixon_netconf_lib.c | 277 ++++++++++++++++--------------- lib/src/clixon_xml_map.c | 18 +- test/README.md | 17 +- test/all.sh | 40 ++++- test/lib.sh | 12 +- test/test_nacm.sh | 2 +- test/test_nacm_ext.sh | 2 +- test/test_nacm_protocol.sh | 2 +- test/test_perf.sh | 2 +- test/test_restconf.sh | 4 +- test/test_restconf2.sh | 2 +- test/test_rpc.sh | 8 +- test/test_stream.sh | 4 +- 23 files changed, 331 insertions(+), 189 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0035a93d..ce4188a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling * More precise Yang validation and better error messages - * For Example, adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed + * Example: adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed, bad-element instead of "yang node not found", etc. + * * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index e2fbb5bb..9fed0a1a 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -44,6 +44,7 @@ * (Duplicated. Also in netconf_*.h) */ int netconf_output(int s, cbuf *xf, char *msg); +int netconf_output_encap(int s, cbuf *xf, char *msg); int netconf_xpath(cxobj *xsearch, cxobj *xfilter, diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index fc24810b..50723de4 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -182,9 +182,9 @@ netconf_get_target(cxobj *xn, * @param[in] s * @param[in] cb Cligen buffer that contains the XML message * @param[in] msg Only for debug - * @note Assumes "cb" contains valid XML, ie encoding is correct. This is done - * if it is output by a xml render routine (xml_print et al), but NOT - * otherwise. + * @retval 0 OK + * @retval -1 Error + * @see netconf_output_encap for function with encapsulation */ int netconf_output(int s, @@ -216,3 +216,34 @@ netconf_output(int s, return retval; } + +/*! Encapsulate and send outgoing netconf packet as cbuf on socket + * @param[in] s + * @param[in] cb Cligen buffer that contains the XML message + * @param[in] msg Only for debug + * @retval 0 OK + * @retval -1 Error + * @note Assumes "cb" contains valid XML + * @see netconf_output without encapsulation + */ +int +netconf_output_encap(int s, + cbuf *cb, + char *msg) +{ + int retval = -1; + cbuf *cb1 = NULL; + + if ((cb1 = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + add_preamble(cb1); + cprintf(cb1, "%s", cbuf_get(cb)); + add_postamble(cb1); + retval = netconf_output(s, cb1, msg); + done: + if (cb1) + cbuf_free(cb1); + return retval; +} diff --git a/apps/netconf/netconf_lib.h b/apps/netconf/netconf_lib.h index f2dc73e1..6d2de111 100644 --- a/apps/netconf/netconf_lib.h +++ b/apps/netconf/netconf_lib.h @@ -75,5 +75,6 @@ int add_error_preamble(cbuf *xf, char *reason); char *netconf_get_target(cxobj *xn, char *path); int add_error_postamble(cbuf *xf); int netconf_output(int s, cbuf *xf, char *msg); +int netconf_output_encap(int s, cbuf *cb, char *msg); #endif /* _NETCONF_LIB_H_ */ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index cc4c9710..dc57126e 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -112,7 +112,7 @@ process_incoming_packet(clicon_handle h, free(str0); if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) goto done; - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto done; } free(str0); @@ -121,7 +121,7 @@ process_incoming_packet(clicon_handle h, if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } } @@ -157,11 +157,8 @@ process_incoming_packet(clicon_handle h, if (xml_addsub(xc, xa2) < 0) goto done; } - add_preamble(cbret); - clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); - add_postamble(cbret); - if (netconf_output(1, cbret, "rpc-reply") < 0){ + if (netconf_output_encap(1, cbret, "rpc-reply") < 0){ cbuf_free(cbret); goto done; } diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 64001137..4d9b354a 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -776,12 +776,10 @@ netconf_notification_cb(int s, clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - add_preamble(cb); /* Make it well-formed netconf xml */ if (clicon_xml2cbuf(cb, xn, 0, 0) < 0) goto done; - add_postamble(cb); /* Send it to listening client on stdout */ - if (netconf_output(1, cb, "notification") < 0){ + if (netconf_output_encap(1, cb, "notification") < 0){ cbuf_free(cb); goto done; } @@ -925,13 +923,13 @@ netconf_application_rpc(clicon_handle h, if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } if ((ret = xml_yang_validate_add(xn, cbret)) < 0) goto done; if (ret == 0){ - netconf_output(1, cbret, "rpc-error"); + netconf_output_encap(1, cbret, "rpc-error"); goto ok; } } diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index be214fbb..062dff65 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -450,10 +450,12 @@ api_return_err(clicon_handle h, else if (xml2json_cbuf(cb, xerr, pretty) < 0) goto done; + clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb)); FCGX_SetExitStatus(code, r->out); /* Created */ FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", use_xml?"xml":"json"); + if (use_xml){ if (pretty){ FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b8beb068..a11d55c1 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1118,15 +1118,8 @@ api_operations_post(clicon_handle h, goto ok; } if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ - if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if (yrpc == NULL){ - if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) + clicon_debug(1, "%s rpc %s not found", __FUNCTION__, id); + if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; if ((xe = xpath_first(xerr, "rpc-error")) != NULL) if (api_return_err(h, r, xe, pretty, use_xml) < 0) diff --git a/example/example.yang b/example/example.yang index 57f58fcb..749b10ff 100644 --- a/example/example.yang +++ b/example/example.yang @@ -26,6 +26,10 @@ module example { } /* Translation function example - See also example_cli */ list translate{ + key k; + leaf k{ + type string; + } leaf value{ type string; } @@ -73,6 +77,57 @@ module example { } } } + rpc empty { + description "Smallest possible RPC with no input or output"; + } + rpc example { + description "Some example input/output for testing RFC7950 7.14. + RPC simply echoes the input for debugging."; + input { + leaf x { + description + "If a leaf in the input tree has a 'mandatory' statement with + the value 'true', the leaf MUST be present in an RPC invocation."; + type string; + mandatory true; + } + leaf y { + description + "If a leaf in the input tree has a 'mandatory' statement with the + value 'true', the leaf MUST be present in an RPC invocation."; + type string; + default "42"; + } + leaf-list z { + description + "If a leaf-list in the input tree has one or more default + values, the server MUST use these values (XXX not supported)"; + type string; + } + leaf w { + description + "If any node has a 'when' statement that would evaluate to + 'false',then this node MUST NOT be present in the input tree. + (XXX not supported)"; + type string; + when "/translate/k=5/value='w'"; + } + } + output { + leaf x { + type string; + } + leaf y { + type string; + } + leaf z { + type string; + } + leaf w { + type string; + } + } + } rpc debug { description "Set debug level of backend. XXX should be in clixon-config"; input { diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index b22e697b..8f1b9da0 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -47,8 +47,11 @@ int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_missing_element(cbuf *cb, char *type, char *element, char *message); +int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message); int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); +int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element); int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); +int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message); int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 6c284fc6..8db94c4a 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -307,6 +307,45 @@ netconf_unknown_attribute(cbuf *cb, goto done; } +/*! Common Netconf element XML tree according to RFC 6241 App A + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "application" or "protocol" + * @param[in] tag Error tag + * @param[in] element bad-element xml + * @param[in] message Error message + */ +static int +netconf_element_xml_common(cxobj **xret, + char *type, + char *tag, + char *element, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "%s" + "%s" + "%s" + "error", + type, tag, element) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! Create Netconf missing-element error XML tree according to RFC 6241 App A * * An expected element is missing. @@ -321,32 +360,34 @@ netconf_missing_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "missing-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "missing-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} + + +/*! Create Netconf missing-element error XML tree according to RFC 6241 App A + * @param[out] xret Error XML tree. Free with xml_free after use + * @param[in] type Error type: "application" or "protocol" + * @param[in] element bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "missing-element", element, message); } /*! Create Netconf bad-element error XML tree according to RFC 6241 App A @@ -364,32 +405,26 @@ netconf_bad_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "bad-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "bad-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} +int +netconf_bad_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "bad-element", element, message); } /*! Create Netconf unknown-element error XML tree according to RFC 6241 App A @@ -406,32 +441,26 @@ netconf_unknown_element(cbuf *cb, char *element, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "unknown-element" - "%s" - "error", - type, element) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_element_xml_common(&xret, type, "unknown-element", element, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} +int +netconf_unknown_element_xml(cxobj **xret, + char *type, + char *element, + char *message) +{ + return netconf_element_xml_common(xret, type, "unknown-element", element, message); } /*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A @@ -476,53 +505,48 @@ netconf_unknown_namespace(cbuf *cb, goto done; } -/*! Create Netconf access-denied error XML tree according to RFC 6241 App A +/*! Create Netconf access-denied error cbuf according to RFC 6241 App A * * Access to the requested protocol operation or data model is denied because * authorization failed. * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message + * @see netconf_access_denied_xml Same but returns XML tree */ int netconf_access_denied(cbuf *cb, char *type, char *message) { - int retval = -1; - char *encstr = NULL; - - if (cprintf(cb, "" - "%s" - "access-denied" - "error", - type) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + int retval = -1; + cxobj *xret = NULL; + + if (netconf_access_denied_xml(&xret, type, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf access-denied error XML tree according to RFC 6241 App A * * Access to the requested protocol operation or data model is denied because * authorization failed. - * @param[out] xret Error XML tree + * @param[out] xret Error XML tree. Free with xml_free after use * @param[in] type Error type: "application" or "protocol" * @param[in] message Error message + * @code + * cxobj *xret = NULL; + * if (netconf_access_denied_xml(&xret, "protocol", "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_access_denied Same but returns cligen buffer */ int netconf_access_denied_xml(cxobj **xret, @@ -795,37 +819,25 @@ netconf_operation_not_supported(cbuf *cb, * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] type Error type: "rpc", "application" or "protocol" * @param[in] message Error message + * @see netconf_operation_failed_xml Same but returns XML tree */ int netconf_operation_failed(cbuf *cb, char *type, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "operation-failed" - "error", - type) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") < 0) - goto err; + if (netconf_operation_failed_xml(&xret, type, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf operation-failed error XML tree according to RFC 6241 App A @@ -835,6 +847,13 @@ netconf_operation_failed(cbuf *cb, * @param[out] xret Error XML tree * @param[in] type Error type: "rpc", "application" or "protocol" * @param[in] message Error message + * @code + * cxobj *xret = NULL; + * if (netconf_operation_failed_xml(&xret, "protocol", "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_operation_failed Same but returns cligen buffer */ int netconf_operation_failed_xml(cxobj **xret, @@ -872,35 +891,24 @@ netconf_operation_failed_xml(cxobj **xret, * @param[out] cb CLIgen buf. Error XML is written in this buffer * @param[in] message Error message * @note New in :base:1.1 + * @see netconf_malformed_message_xml Same but returns XML tree */ int netconf_malformed_message(cbuf *cb, char *message) { int retval = -1; - char *encstr = NULL; + cxobj *xret = NULL; - if (cprintf(cb, "" - "rpc" - "malformed-message" - "error") <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_malformed_message_xml(&xret, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; } /*! Create Netconf malformed-message error XML tree according to RFC 6241 App A @@ -911,10 +919,17 @@ netconf_malformed_message(cbuf *cb, * @param[out] xret Error XML tree * @param[in] message Error message * @note New in :base:1.1 + * @code + * cxobj *xret = NULL; + * if (netconf_malformed_message_xml(&xret, "Unauthorized") < 0) + * err; + * xml_free(xret); + * @endcode + * @see netconf_malformed_message Same but returns cligen buffer */ int netconf_malformed_message_xml(cxobj **xret, - char *message) + char *message) { int retval =-1; cxobj *xerr; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2263bde2..2644f494 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -398,6 +398,8 @@ validate_identityref(cxobj *xt, * in [RFC6241]. If output parameters are returned, they are encoded as * child elements to the element defined in [RFC6241], in * the same order as they are defined within the "output" statement. + * @see xml_yang_validate_all + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_rpc(cxobj *xrpc, @@ -454,6 +456,7 @@ xml_yang_validate_rpc(cxobj *xrpc, * fail; * @endcode * @see xml_yang_validate_all + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_add(cxobj *xt, @@ -503,15 +506,14 @@ xml_yang_validate_add(cxobj *xt, * needs to be reparsed when concrete type is selected */ if ((body = xml_body(xt)) != NULL){ - if (cv_parse(body, cv) <0){ - clicon_err(OE_UNIX, errno, "cv_parse"); - goto done; + if (cv_parse1(body, cv, &reason) != 1){ + if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + goto done; + goto fail; } if ((ys_cv_validate(cv, ys, &reason)) != 1){ if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) goto done; - if (reason) - free(reason); goto fail; } } @@ -531,6 +533,8 @@ xml_yang_validate_add(cxobj *xt, done: if (cv) cv_free(cv); + if (reason) + free(reason); return retval; fail: retval = 0; @@ -544,7 +548,6 @@ xml_yang_validate_add(cxobj *xt, * @retval 1 Validation OK * @retval 0 Validation failed * @retval -1 Error - * @see xml_yang_validate_add * @code * cxobj *x; * cbuf *cbret = cbuf_new(); @@ -553,6 +556,9 @@ xml_yang_validate_add(cxobj *xt, * if (ret == 0) * fail; * @endcode + * @see xml_yang_validate_add + * @see xml_yang_validate_rpc + * @note Should need a variant accepting cxobj **xret */ int xml_yang_validate_all(cxobj *xt, diff --git a/test/README.md b/test/README.md index 7ae292bb..59832663 100644 --- a/test/README.md +++ b/test/README.md @@ -1,9 +1,9 @@ # Clixon tests This directory contains testing code for clixon and the example -routing application. Assumes setup of http daemon as describe under apps/restonf -- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script -- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. +application. Assumes setup of http daemon as describe under apps/restonf +- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script +- all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. By default the script will exit on first error. Run as `all.sh summary` to continue and print a summary on all tests. - test_nacm.sh Auth tests using internal NACM - test_nacm_ext.sh Auth tests using external NACM (separate file) - test_cli.sh CLI tests @@ -13,3 +13,14 @@ routing application. Assumes setup of http daemon as describe under apps/restonf - test_leafref.sh Yang leafref tests - test_datastore.sh Datastore tests +Example runs: +``` +> run.sh +# Runs through all tests matching 'test_*.sh' in the directory. Prints test output +# and stops on first error + +> run.sh summary +# Same as above but continues after errors and does not print test output. +``` + + diff --git a/test/all.sh b/test/all.sh index d0e92410..79a0309f 100755 --- a/test/all.sh +++ b/test/all.sh @@ -1,17 +1,43 @@ #!/bin/bash # Run, eg as: -# ./run.sh 2>&1 | tee test.log +# ./all.sh 2>&1 | tee test.log # break on first test +# ./all.sh summary # to run all tests and print + +summary=0 +if [ $# -gt 0 ]; then + summary=1 +fi +if [ $# -gt 1 ]; then + echo "usage: $0 [summary] # pipe to dev/null and continue on error" + exit -1 +fi # include err() and new() functions . ./lib.sh - +err=0 for test in test*.sh; do echo "Running $test" - ./$test - errcode=$? + if [ $summary -ne 0 ]; then + ./$test > /dev/null 2>&1 + errcode=$? + else + ./$test + errcode=$? + fi if [ $errcode -ne 0 ]; then - echo "Error in $test errcode=$errcode" - exit $errcode + err=1 + echo -e "\e[31mError in $test errcode=$errcode" + echo -ne "\e[0m" + if [ $summary -eq 0 ]; then + exit $errcode + fi fi done -echo OK +if [ $err -eq 0 ]; then + echo OK +else + echo -e "\e[31mError" + echo -ne "\e[0m" +fi + + diff --git a/test/lib.sh b/test/lib.sh index 2a66d2d5..3d82e32d 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -38,7 +38,9 @@ if [ ! -d $dir ]; then fi rm -rf $dir/* -# error and exit, arg is optional extra errmsg +# error and exit, +# arg1: expected +# arg2: errmsg[optional] err(){ echo -e "\e[31m\nError in Test$testnr [$testname]:" if [ $# -gt 0 ]; then @@ -53,7 +55,7 @@ err(){ echo "$expect"| od -t c > $dir/clixon-expect diff $dir/clixon-expect $dir/clixon-ret - exit $testnr + exit -1 #$testnr } # Increment test number and print a nice string @@ -221,8 +223,9 @@ expectwait(){ done # cat /tmp/flag if [ $(cat /tmp/flag) != "ok" ]; then - cat /tmp/flag - exit +# err "ok" $(cat /tmp/flag) +# cat /tmp/flag + exit -1 fi } @@ -250,5 +253,4 @@ expectmatch(){ fi fi fi - } diff --git a/test/test_nacm.sh b/test/test_nacm.sh index afd1c29d..389ae010 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -123,7 +123,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 2701b2da..9bdee51d 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -155,7 +155,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon (-a is enable http basic auth)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index aebaa843..963218e5 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -147,7 +147,7 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf" sleep 1 new "start restconf daemon (-a is enable basic authentication)" -sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_perf.sh b/test/test_perf.sh index 66c7a5b1..4d9d6dfc 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -78,7 +78,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 03d20bd9..1874a0b3 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -282,10 +282,10 @@ new2 "restconf rpc using POST json without mandatory element" expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new2 "restconf rpc non-existing rpc without namespace" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf rpc non-existing rpc" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/example:kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index ee820082..1cf7188f 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -65,7 +65,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT diff --git a/test/test_rpc.sh b/test/test_rpc.sh index b1b04251..f28a4df9 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -46,7 +46,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -D $DBG" -s /bin/sh www-data & sleep $RCWAIT @@ -54,7 +54,7 @@ new "rpc tests" # 1.First some positive tests vary media types # -new2 "restconf example rpc json/json default - no headers" +new2 "restconf example rpc json/json default - no http media headers" expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} ' @@ -93,13 +93,13 @@ new2 "restconf add extra" expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf wrong method" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang node not found"}}} ' +expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf edit-config missing mandatory" expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "netconf kill-session missing session-id mandatory" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' # edit-config? diff --git a/test/test_stream.sh b/test/test_stream.sh index 19ad9802..87932744 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -121,7 +121,7 @@ new "kill old restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT @@ -153,7 +153,7 @@ new "netconf NONEXIST subscription" expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorExpected timestamp]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") From 0baebc93fdc86388afe3a205ea192ba8d536f1ec Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 15:18:29 +0100 Subject: [PATCH 17/72] * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` * Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. --- CHANGELOG.md | 75 +- DEVELOP.md | 3 + README.md | 20 + apps/backend/backend_client.c | 70 +- apps/backend/backend_commit.c | 36 +- apps/backend/backend_main.c | 147 +-- apps/backend/backend_plugin.c | 2 +- apps/cli/cli_common.c | 33 +- apps/cli/cli_show.c | 12 +- apps/netconf/netconf_main.c | 2 + apps/netconf/netconf_rpc.c | 10 +- apps/restconf/README.md | 50 +- apps/restconf/restconf_methods.c | 927 +++++++++++++----- apps/restconf/restconf_stream.c | 2 +- datastore/datastore_client.c | 9 +- datastore/text/clixon_xmldb_text.c | 97 +- doc/FAQ.md | 144 +-- example/README.md | 289 +++--- example/example_backend.c | 27 +- example/example_cli.c | 2 +- example/example_cli.cli | 2 +- example/example_netconf.c | 2 +- example/example_restconf.c | 2 +- include/clixon_custom.h | 5 - lib/clixon/clixon_json.h | 1 + lib/clixon/clixon_netconf_lib.h | 3 +- lib/clixon/clixon_options.h | 3 + lib/clixon/clixon_string.h | 2 +- lib/clixon/clixon_xml.h | 13 +- lib/clixon/clixon_xml_map.h | 6 +- lib/clixon/clixon_xml_sort.h | 8 +- lib/clixon/clixon_yang.h | 3 +- lib/src/clixon_err.c | 1 + lib/src/clixon_json.c | 151 ++- lib/src/clixon_netconf_lib.c | 86 +- lib/src/clixon_options.c | 10 +- lib/src/clixon_proto_client.c | 4 +- lib/src/clixon_string.c | 48 + lib/src/clixon_xml.c | 131 ++- lib/src/clixon_xml_db.c | 7 +- lib/src/clixon_xml_map.c | 596 +++++++---- lib/src/clixon_xml_parse.y | 35 +- lib/src/clixon_xml_sort.c | 37 +- lib/src/clixon_yang.c | 258 +---- lib/src/clixon_yang_module.c | 12 +- test/all.sh | 1 + test/test_cli.sh | 2 +- test/test_compat.sh | 116 +++ test/test_datastore.sh | 9 +- test/test_feature.sh | 14 +- test/test_identity.sh | 18 +- test/test_leafref.sh | 17 +- test/test_list.sh | 14 +- test/test_nacm.sh | 22 +- test/test_nacm_ext.sh | 18 +- test/test_nacm_protocol.sh | 14 +- test/test_netconf.sh | 49 +- test/test_order.sh | 67 +- test/test_perf.sh | 16 +- test/test_restconf.sh | 97 +- test/test_restconf2.sh | 55 +- test/test_rpc.sh | 48 +- test/test_startup.sh | 12 +- test/test_stream.sh | 27 +- test/test_type.sh | 8 +- test/test_when_must.sh | 12 +- test/test_yang.sh | 47 +- test/test_yang_load.sh | 54 +- test/test_yang_namespace.sh | 116 ++- yang/clixon-config@2018-10-21.yang | 13 +- .../ietf-netconf-notification@2008-07-01.yang | 3 +- 71 files changed, 2679 insertions(+), 1573 deletions(-) create mode 100755 test/test_compat.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4188a4..ad2ebd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,61 @@ # Clixon Changelog -## 3.9.0 (Preliminary Target: 31 December 2018) +## 3.9.0 (Preliminary Target: Mid-January 2019) ### Planned new features * [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) ### Major New features +* Correct XML namespace handling + * XML multiple modules was based on non-strict semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208) as well as strict Netconf and Restconf namespace handling, which causes problems with overlapping names and false positives, and most importantly, with standard conformance. + * There are still the following non-strict namespace handling: + * Everything in ietf-netconf base syntax with namespace `urn:ietf:params:xml:ns:netconf:base:1.0` is default and need not be explicitly given + * edit-config xpath select statement does not support namespaces + * notifications do not support namespaces. + * Below see netconf old (but wrong) netconf RPC: + ``` + + + + + + ipv4 + + + ``` + This is the currently correct Netconf RPC: + ``` + # xmlns may be ommitted + + + + + ipv4 + + + ``` + * Another example for restconf rpc with new correct syntax. Note that while Netconf uses xmlns attribute syntax, Restconf uses module name prefix. First the request: + ``` + POST http://localhost/restconf/operations/example:example) + Content-Type: application/yang-data+json + { + "example:input":{ + "x":0 + } + } + ``` + then the reply: + ``` + HTTP/1.1 200 OK + { + "example:output": { + "x": "0", + "y": "42" + } + } + ``` + * To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false. + * See https://github.com/clicon/clixon/issues/49 * NACM extension (RFC8341) * NACM module support (RFC8341 A1+A2) * Recovery user "_nacm_recovery" added. @@ -27,25 +77,15 @@ * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. -* Correct XML namespace handling - * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: - ``` - # Wrong but accepted - # Correct - - - ``` - * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) - * XML to JSON translator support for mapping xmlns attribute to module name prefix. - * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" - * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) -* Removed delete-config support for candidate db since it is not supported in RFC6241. +* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. + * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. +* Removed `delete-config` support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. * XML namespace handling is corrected (see above) - * For backward compatibility set config option CLICON_XML_NS_ITERATE + * For backward compatibility set config option CLICON_XML_NS_LOOSE * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. * Change all @datamodel:tree to @datamodel in all CLI specification files @@ -53,6 +93,9 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. + * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` +* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. * Added example_rpc RPC to example backend * Renamed xml_namespace[_set]() to xml_prefix[_set]() * Changed all make tags --> make TAGS @@ -273,7 +316,7 @@ translate { ### Known issues * Namespace name relabeling is not supported. - * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as: + * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as: ``` x:des3 ``` diff --git a/DEVELOP.md b/DEVELOP.md index fb66e641..74d7f4ef 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -97,6 +97,9 @@ EOF ## New release What to think about when doing a new release. +* valgrind for memory leaks +* New clixon-config.yang revision? +Tagging: * git merge --no-ff develop * change CLIXON_VERSION in configure.ac * git tag -a +``` +In 3.9, the same statement should be, for example: +``` + +``` +Note that base netconf syntax is still not enforced but recommended: +``` + + + +``` + Yang ==== YANG and XML is the heart of Clixon. Yang modules are used as a @@ -153,6 +170,9 @@ Clixon does not support the following netconf features: - edit-config config-text - edit-config operation +Some other deviations from the RFC: +- edit-config xpath select statement does not support namespaces + Restconf ======== Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index baf40269..3e1fa1ad 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -292,7 +292,7 @@ client_get_streams(clicon_handle h, * @param[in,out] xret Existing XML tree, merge x into this * @retval -1 Error (fatal) * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (clicon_err called) */ static int client_statedata(clicon_handle h, @@ -309,15 +309,15 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - (retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - (retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") && - (retval = yang_modules_state_get(h, yspec, xret)) != 0) - goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")) + if ((retval = yang_modules_state_get(h, yspec, xret)) != 0) + goto done; if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0) goto done; /* Code complex to filter out anything that is outside of xpath */ @@ -339,7 +339,7 @@ client_statedata(clicon_handle h, /* reset flag */ if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; - retval = 0; + retval = 0; /* OK */ done: if (xvec) free(xvec); @@ -363,7 +363,6 @@ from_client_get(clicon_handle h, char *xpath = "/"; cxobj *xret = NULL; int ret; - cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL) @@ -379,33 +378,24 @@ from_client_get(clicon_handle h, clicon_err_reset(); if ((ret = client_statedata(h, xpath, &xret)) < 0) goto done; - if (ret == 0){ /* OK */ - cprintf(cbret, ""); - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, "data") < 0) - goto done; - if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) - goto done; - } - cprintf(cbret, ""); - } - else { /* 1 Error from callback */ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); + if (ret == 1){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) goto done; - } - cprintf(cbx, "Internal error:%s", clicon_err_reason); - if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0) - goto done; - clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe)); + goto ok; } + cprintf(cbret, ""); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, "data") < 0) + goto done; + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); ok: retval = 0; done: - if (cbx) - cbuf_free(cbx); if (xret) xml_free(xret); return retval; @@ -433,6 +423,7 @@ from_client_edit_config(clicon_handle h, int non_config = 0; yang_spec *yspec; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec9"); @@ -491,24 +482,27 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ - if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) + if (clicon_xml_sort(h) && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xmldb_put(h, target, operation, xc, cbret) < 0){ + if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) goto done; goto ok; } + if (ret == 0) + goto ok; } + assert(cbuf_len(cbret) == 0); + cprintf(cbret, ""); ok: - if (!cbuf_len(cbret)) - cprintf(cbret, ""); retval = 0; done: if (cbx) cbuf_free(cbx); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; + } /* from_client_edit_config */ /*! Internal message: Lock database diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 88ea9e99..7c29131a 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -165,12 +165,24 @@ validate_common(clicon_handle h, clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } - /* 2. Parse xml trees */ + /* 2. Parse xml trees + * This is the state we are going from */ if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0) goto done; + /* This is the state we are going to */ if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) goto done; + /* Validate the target state. It is not completely clear this should be done + * here. It is being made in generic_validate below. + * But xml_diff requires some basic validation, at least check that yang-specs + * have been assigned + */ + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* 3. Compute differences */ if (xml_diff(yspec, td->td_src, @@ -240,7 +252,7 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval -1 Error - or validation failed (but cbret not set) + * @retval -1 Error - or validation failed * @retval 0 Validation failed (with cbret set) * @retval 1 Validation OK * @note Need to differentiate between error and validation fail @@ -259,7 +271,9 @@ candidate_commit(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate). Note this is only call that uses 3-values */ + /* Common steps (with validate). Load candidate and running and compute diffs + * Note this is only call that uses 3-values + */ if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; if (ret == 0) @@ -270,12 +284,14 @@ candidate_commit(clicon_handle h, goto done; /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL)) < 0) goto done; + if (ret == 0) + goto fail; + } /* 8. Success: Copy candidate to running */ - if (xmldb_copy(h, candidate, "running") < 0) goto done; @@ -423,9 +439,11 @@ from_client_validate(clicon_handle h, goto ok; } /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) - goto done; + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0) + goto done; + goto ok; + } cprintf(cbret, ""); ok: retval = 0; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 8e433d85..ba297852 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -176,22 +176,24 @@ db_reset(clicon_handle h, } /*! Merge db1 into db2 without commit + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int db_merge(clicon_handle h, const char *db1, - const char *db2) + const char *db2, + cbuf *cbret) { - int retval = -1; - cxobj *xt = NULL; - + int retval = -1; + cxobj *xt = NULL; + /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; /* Merge xml into db2. Without commit */ - if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret); done: if (xt) xml_free(xt); @@ -288,18 +290,22 @@ nacm_load_external(clicon_handle h) } /*! Merge xml in filename into database + * @retval -1 Error + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK */ static int load_extraxml(clicon_handle h, char *filename, - const char *db) + const char *db, + cbuf *cbret) { int retval = -1; cxobj *xt = NULL; int fd = -1; - + if (filename == NULL) - return 0; + return 1; if ((fd = open(filename, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; @@ -310,9 +316,7 @@ load_extraxml(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) - goto done; - retval = 0; + retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret); done: if (fd != -1) close(fd); @@ -385,9 +389,13 @@ static int startup_mode_running(clicon_handle h, char *extraxml_file) { - int retval = -1; - cbuf *cbret = NULL; + int retval = -1; + cbuf *cbret = NULL; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to candidate for later commit */ if (xmldb_copy(h, "running", "candidate") < 0) goto done; @@ -400,41 +408,46 @@ startup_mode_running(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } /* Commit original running. Assume -1 is validate fail */ - if (candidate_commit(h, "candidate", cbret) < 1){ - /* (1) We cannot differentiate between fatal errors and validation - * failures - * (2) If fatal error, we should exit - * (3) If validation fails we cannot continue. How could we? - * (4) Need to restore the running db since we destroyed it above - */ - clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - /* Reinstate original */ - if (xmldb_copy(h, "candidate", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "candidate", cbret) < 1) + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* (1) We cannot differentiate between fatal errors and validation + * failures + * (2) If fatal error, we should exit + * (3) If validation fails we cannot continue. How could we? + * (4) Need to restore the running db since we destroyed it above + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + /* Reinstate original */ + if (xmldb_copy(h, "candidate", "running") < 0) + goto done; + goto done; } /*! Clixon startup startup mode: Commit startup configuration into running state @@ -460,11 +473,16 @@ startup -------------------------+--| */ static int startup_mode_startup(clicon_handle h, - char *extraxml_file) + char *extraxml_file) { int retval = -1; cbuf *cbret = NULL; + /* Create return buffer for netconf xml errors */ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } /* Stash original running to backup */ if (xmldb_copy(h, "running", "backup") < 0) goto done; @@ -481,34 +499,25 @@ startup_mode_startup(clicon_handle h, /* Application may define extra xml in its reset function*/ if (clixon_plugin_reset(h, "tmp") < 0) goto done; + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Get application extra xml from file */ - if (load_extraxml(h, extraxml_file, "tmp") < 0) - goto done; + if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1) + goto fail; /* Clear running db */ if (db_reset(h, "running") < 0) goto done; - /* Create return buffer (not used) */ - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } + /* Commit startup */ - if (candidate_commit(h, "startup", cbret) < 1){ /* diff */ - /* We cannot differentiate between fatal errors and validation - * failures - * In both cases we copy back the original running and quit - */ - clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", - __FUNCTION__, cbuf_get(cbret)); - if (xmldb_copy(h, "backup", "running") < 0) - goto done; - goto done; - } + if (candidate_commit(h, "startup", cbret) < 1) /* diff */ + goto fail; /* Merge user reset state and extra xml file (no commit) */ - if (db_merge(h, "tmp", "running") < 0) - goto done; + if (db_merge(h, "tmp", "running", cbret) < 1) + goto fail; retval = 0; done: + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); if (cbret) cbuf_free(cbret); if (xmldb_delete(h, "backup") < 0) @@ -516,6 +525,20 @@ startup_mode_startup(clicon_handle h, if (xmldb_delete(h, "tmp") < 0) goto done; return retval; + fail: + /* We cannot differentiate between fatal errors and validation + * failures + * In both cases we copy back the original running and quit + */ + if (strlen(cbuf_get(cbret))) + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, cbuf_get(cbret)); + else + clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.", + __FUNCTION__, clicon_err_reason); + if (xmldb_copy(h, "backup", "running") < 0) + goto done; + goto done; } int @@ -540,7 +563,6 @@ main(int argc, int sockfamily; char *xmldb_plugin; int xml_cache; - int xml_pretty; char *xml_format; char *nacm_mode; int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; @@ -808,9 +830,10 @@ main(int argc, if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0) if (xmldb_setopt(h, "format", (void*)xml_format) < 0) goto done; - if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) - if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0) - goto done; + if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0) + goto done; + if (xmldb_setopt(h, "sort", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XML_SORT")) < 0) + goto done; /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index e8ce2972..996a27cb 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -117,7 +117,7 @@ clixon_plugin_reset(clicon_handle h, * @param[in,out] xtop State XML tree is merged with existing tree. * @retval -1 Error * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (xret set with netconf-error) * @note xtop can be replaced */ int diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 7e6f1307..f6796f58 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,8 +236,8 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) + goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -293,7 +293,7 @@ cli_set(clicon_handle h, cvec *cvv, cvec *argv) { - int retval = 1; + int retval = -1; if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0) goto done; @@ -501,53 +501,54 @@ cli_start_shell(clicon_handle h, cvec *vars, cvec *argv) { - char *cmd; + char *cmd; struct passwd *pw; - int retval; - char bcmd[128]; - cg_var *cv1 = cvec_i(vars, 1); + int retval = -1; + char bcmd[128]; + cg_var *cv1 = cvec_i(vars, 1); cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL); if ((pw = getpwuid(getuid())) == NULL){ fprintf(stderr, "%s: getpwuid: %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } if (chdir(pw->pw_dir) < 0){ fprintf(stderr, "%s: chdir(%s): %s\n", __FUNCTION__, pw->pw_dir, strerror(errno)); endpwent(); - return -1; + goto done; } endpwent(); cli_signal_flush(h); cli_signal_unblock(h); if (cmd){ snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd); - if ((retval = system(bcmd)) < 0){ + if (system(bcmd) < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash -c): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } } else - if ((retval = system("bash -l")) < 0){ + if (system("bash -l") < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash): %s\n", __FUNCTION__, strerror(errno)); - return -1; + goto done; } cli_signal_block(h); #if 0 /* Allow errcodes from bash */ if (retval != 0){ fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval); - return -1; + goto done; } #endif - - return 0; + retval = 0; + done: + return retval; } /*! Generic quit callback diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2e56f329..ec39ac4e 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -171,7 +171,7 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1) goto done; if (y==NULL) goto ok; @@ -443,6 +443,7 @@ cli_show_config(clicon_handle h, cxobj *xc; cxobj *xerr; enum genmodel_type gt; + yang_spec *yspec; if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,]", cvec_len(argv)); @@ -496,6 +497,13 @@ cli_show_config(clicon_handle h, clicon_rpc_generate_error("Get configuration", xerr); goto done; } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + /* Some formats (eg cli) require yang */ + if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* Print configuration according to format */ switch (format){ case FORMAT_XML: @@ -516,7 +524,7 @@ cli_show_config(clicon_handle h, if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) goto done; xc = NULL; /* Dont print xt itself */ - while ((xc = xml_child_each(xt, xc, -1)) != NULL) + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) xml2cli(stdout, xc, NULL, gt); /* cli syntax */ break; case FORMAT_NETCONF: diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index dc57126e..4cdef1a0 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -118,6 +118,8 @@ process_incoming_packet(clicon_handle h, free(str0); if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ isrpc++; + if (xml_spec_populate_rpc(h, xrpc, yspec) < 0) + goto done; if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0) goto done; if (ret == 0){ diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 4d9b354a..7945110e 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -905,13 +905,9 @@ netconf_application_rpc(clicon_handle h, goto ok; } yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); - if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL) - break; - } + if ((yrpc==NULL) && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(xn, yspec, &yrpc) < 0) /* Y_RPC */ + goto done; } /* Check if found */ if (yrpc != NULL){ diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 972ab99f..2295e3fa 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -1,11 +1,11 @@ # Clixon Restconf - * [Installation](#Installation) - * [Streams](Streams) - * [Nchan Streams](Nchan) - * [Debugging](Debugging) + * [Installation](#installation) + * [Streams](#streams) + * [Nchan Streams](#nchan-streams) + * [Debugging](#debugging) -### 1. Installation +## 1. Installation The examples are based on Nginx. Other reverse proxies should work but are not verified. @@ -44,39 +44,39 @@ sudo systemctl start start.service Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data +> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data ``` Make restconf calls with curl ``` -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces +> curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces [ { - "interfaces": { + "ietf-interfaces:interfaces": { "interface":[ { - "name": "eth0", - "type": "eth", - "enabled": "true", "name": "eth9", - "type": "eth", - "enabled": "true" + "type": "ex:eth", + "enabled": true, } ] } } ] -olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type -[ - { - "type": "eth" - } -] - -curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data +``` +Get the type of a specific interface: +``` +> curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type +{ + "ietf-interfaces:type": "eth" +} +``` +Example of writing a new interfaces specification: +``` +curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}' ``` -### 2. Streams +## 2. Streams Clixon have two experimental restconf event stream implementations following RFC8040 Section 6 using SSE. One native and one using Nginx @@ -112,7 +112,7 @@ Add the following to extend the nginx configuration file with the following stat AN example of a stream access is as follows: ``` -vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE +> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE data: 2018-11-04T14:47:11.373124faultEthernet0major data: 2018-11-04T14:47:16.375265faultEthernet0major @@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing See (stream tests)[../test/test_streams.sh] for more examples. -### 3. Nchan +## 3. Nchan As an alternative streams implementation, Nginx/Nchan can be used. Nginx uses pub/sub channels and can be configured in a variety of @@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. -### 4. Debugging +## 4. Debugging Start the restconf fastcgi program with debug flag: ``` diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index a11d55c1..b2910a1b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -93,6 +93,7 @@ Mapping netconf error-tag -> status code | malformed-message | 400 | +-------------------------+-------------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H @@ -195,20 +196,26 @@ api_data_get2(clicon_handle h, size_t xlen; int i; cxobj *x; - + int ret; + clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbpath = cbuf_new()) == NULL) goto done; cprintf(cbpath, "/"); - clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); /* We know "data" is element pi-1 */ - if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ + if ((ret = api_path2xpath(yspec, pcvec, pi, cbpath)) < 0) + goto done; + if (ret == 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); @@ -216,24 +223,29 @@ api_data_get2(clicon_handle h, if (clicon_rpc_get(h, path, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } + if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; /* We get return via netconf which is complete tree from root * We need to cut that tree to only the object. */ -#if 1 /* DEBUG */ - { +#if 0 /* DEBUG */ + if (debug){ cbuf *cb = cbuf_new(); clicon_xml2cbuf(cb, xret, 0, 0); clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); cbuf_free(cb); } #endif - /* Check if error return XXX this needs more work */ - if ((xe = xpath_first(xret, "/rpc-error")) != NULL){ + /* Check if error return */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -241,11 +253,12 @@ api_data_get2(clicon_handle h, /* Normal return, no error */ if ((cbx = cbuf_new()) == NULL) goto done; - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); - FCGX_FPrintF(r->out, "\r\n"); - if (head) + if (head){ + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); goto ok; + } if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */ if (use_xml){ if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */ @@ -261,16 +274,32 @@ api_data_get2(clicon_handle h, goto done; if (use_xml){ for (i=0; i0
+ * Out: {"example:x": {"0"}} + */ if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; + } } - // clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); ok: @@ -414,7 +443,8 @@ api_data_post(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; /* dont free */ char *username; - + int ret; + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); @@ -429,25 +459,45 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the @@ -456,9 +506,12 @@ api_data_post(clicon_handle h, if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -471,6 +524,19 @@ api_data_post(clicon_handle h, /* Replace xbot with x, ie bottom of api-path with data */ if (xml_addsub(xbot, x) < 0) goto done; + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -509,7 +575,7 @@ api_data_post(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xe, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) /* Use original xe */ goto done; goto ok; } @@ -644,6 +710,8 @@ api_data_put(clicon_handle h, cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; char *username; + int ret; + char *namespace0; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); @@ -659,46 +727,91 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) + else{ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; - goto ok; - } + goto ok; + } + } /* The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); + if (!parse_xml){ /* If JSON, translate namespace from module:name to xmlns=uri */ + if (json2xml_ns(yspec, x, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } /* Add operation (create/replace) as attribute */ if ((xa = xml_new("operation", x, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif /* Replace xparent with x, ie bottom of api-path with data */ if (api_path==NULL && strcmp(xml_name(x),"data")==0){ if (xml_addsub(NULL, x) < 0) @@ -713,9 +826,12 @@ api_data_put(clicon_handle h, if (strcmp(xml_name(x), xml_name(xbot))){ if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ @@ -723,9 +839,12 @@ api_data_put(clicon_handle h, if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -733,8 +852,15 @@ api_data_put(clicon_handle h, xml_purge(xbot); if (xml_addsub(xparent, x) < 0) goto done; + /* If we already have that default namespace, remove it in child */ + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL){ + if (xml2ns(xparent, NULL, &namespace0) < 0) + goto done; + /* Set xmlns="" default namespace attribute (if diff from default) */ + if (strcmp(namespace0, xml_value(xa))==0) + xml_purge(xa); + } } - /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; @@ -859,6 +985,8 @@ api_data_delete(clicon_handle h, cxobj *xretdis = NULL; /* return from discard */ cxobj *xerr = NULL; char *username; + int ret; + cxobj *xe; /* xml error, no free */ clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -871,8 +999,22 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) - goto done; + if (api_path){ + if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + } if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); @@ -891,8 +1033,8 @@ api_data_delete(clicon_handle h, cprintf(cbx, "
"); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -905,7 +1047,7 @@ api_data_delete(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ cbuf_reset(cbx); cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); @@ -914,7 +1056,7 @@ api_data_delete(clicon_handle h, /* log errors from discard, but ignore */ if ((xpath_first(xretdis, "//rpc-error")) != NULL) clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -981,36 +1123,41 @@ api_operations_get(clicon_handle h, char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; + int i; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "{\"operations\": "); ymod = NULL; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); - yc = NULL; + yc = NULL; i=0; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + if (use_xml) + cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); + else{ + if (i==0) + cprintf(cbx, "{"); + if (i) + cprintf(cbx, ","); + cprintf(cbx, "\"%s:%s\": null", ymod->ys_argument, yc->ys_argument); + } + i++; } + if (!use_xml && i) + cprintf(cbx, "}"); } - cprintf(cbx, ""); - if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) - goto done; - if (xml_rootchild(xt, 0, &xt) < 0) - goto done; - cbuf_reset(cbx); /* reuse same cbuf */ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ - goto done; - } - else{ - if (xml2json_cbuf(cbx, xt, pretty) < 0) - goto done; - } + if (use_xml) + cprintf(cbx, ""); + else + cprintf(cbx, "}"); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); @@ -1027,6 +1174,298 @@ api_operations_get(clicon_handle h, return retval; } +/*! Handle input data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] data Stream input data + * @param[in] yspec Yang top-level specification + * @param[in] yrpc Yang rpc spec + * @param[in] xrpc XML pointer to rpc method + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * + * RFC8040 3.6.1 + * If the "rpc" or "action" statement has an "input" section, then + * instances of these input parameters are encoded in the module + * namespace where the "rpc" or "action" statement is defined, in an XML + * element or JSON object named "input", which is in the module + * namespace where the "rpc" or "action" statement is defined. + * (Any other input is assumed as error.) + */ +static int +api_operations_post_input(clicon_handle h, + FCGX_Request *r, + char *data, + yang_spec *yspec, + yang_stmt *yrpc, + cxobj *xrpc, + int pretty, + int use_xml, + int parse_xml) +{ + int retval = -1; + cxobj *xdata = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; + cxobj *xinput; + cxobj *x; + cbuf *cbret = NULL; + + clicon_debug(1, "%s %s", __FUNCTION__, data); + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Parse input data as json or xml into xml */ + if (parse_xml){ + if (xml_parse_string(data, yspec, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + else { /* JSON */ + if (json_parse_str(data, &xdata) < 0){ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* Special case for JSON: It looks like: + * Need to translate to + */ + if (json2xml_ns(yspec, xdata, &xerr) < 0) + goto done; + if (xerr){ + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + } + xml_name_set(xdata, "data"); + /* Here xdata is: + * ... + * Validate that exactly only tag + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0) + goto done; + clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xinput),"input") != 0 || + xml_child_nr_type(xdata, CX_ELMNT) != 1){ + + if (xml_child_nr_type(xdata, CX_ELMNT) == 0){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have input statement") < 0) + goto done; + } + else + if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + // clicon_debug(1, "%s input validation passed", __FUNCTION__); + /* Add all input under path */ + x = NULL; + while ((x = xml_child_i_type(xinput, 0, CX_ELMNT)) != NULL) + if (xml_addsub(xrpc, x) < 0) + goto done; + /* Here xrpc is: 42 + */ + // ok: + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + if (xdata) + xml_free(xdata); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Handle output data to api_operations_post + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] xret XML reply messages from backend/handler + * @param[in] yspec Yang top-level specification + * @param[in] youtput Yang rpc output specification + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[out] xoutputp Restconf JSON/XML output + * @retval 1 OK + * @retval 0 Fail, Error message sent + * @retval -1 Fatal error, clicon_err called + * xret should like: 0 + */ +static int +api_operations_post_output(clicon_handle h, + FCGX_Request *r, + cxobj *xret, + yang_spec *yspec, + yang_stmt *youtput, + char *namespace, + int pretty, + int use_xml, + cxobj **xoutputp) + +{ + int retval = -1; + cxobj *xoutput = NULL; + cxobj *xerr = NULL; /* assumed malloced, will be freed */ + cxobj *xe; /* just pointer */ + cxobj *xa; /* xml attribute (xmlns) */ + cxobj *x; + cxobj *xok; + cbuf *cbret = NULL; + int ret; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; + } + /* Validate that exactly only tag */ + if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xoutput),"rpc-reply") != 0 || + xml_child_nr_type(xret, CX_ELMNT) != 1){ + if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + /* xoutput should now look: 0 */ + /* 9. Translate to restconf RPC data */ + xml_name_set(xoutput, "output"); + /* xoutput should now look: 0 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0) + goto done; + clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* Validate output (in case handlers are wrong) */ + if (youtput==NULL){ + /* Special case, no yang output + * RFC 7950 7.14.4 + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element + * RFC 8040 3.6.2 + * If the "rpc" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + if ((xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) == NULL || + strcmp(xml_name(xok),"ok") != 0 || + xml_child_nr_type(xoutput, CX_ELMNT) != 1){ + /* Internal error - invalid output from rpc handler */ + if (xok){ + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply is not ok") < 0) + goto done; + } + else + if (netconf_operation_failed_xml(&xerr, "application", + "Internal error: Empty RPC reply should have OK") < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "Status: 204 No Content\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + goto fail; + } + else{ + xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) + goto done; + if (ret == 1 && + (ret = xml_yang_validate_add(xoutput, cbret)) < 0) + goto done; + if (ret == 0){ /* validation failed */ + if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) + goto done; + if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto fail; + } + + /* Clear namespace of parameters */ + x = NULL; + while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) { + if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_purge(xa) < 0) + goto done; + } + } + /* Set namespace on output */ + if (xmlns_set(xoutput, NULL, namespace) < 0) + goto done; + *xoutputp = xoutput; + retval = 1; + done: + clicon_debug(1, "%s retval: %d", __FUNCTION__, retval); + if (cbret) + cbuf_free(cbret); + if (xerr) + xml_free(xerr); + return retval; + fail: + retval = 0; + goto done; +} + /*! REST operation POST method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle @@ -1040,7 +1479,24 @@ api_operations_get(clicon_handle h, * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * See RFC 8040 Sec 3.6 / 4.4.2 * @note We map post to edit-config create. - POST {+restconf}/operations/ + * POST {+restconf}/operations/ + * 1. Initialize + * 2. Get rpc module and name from uri (oppath) and find yang spec + * 3. Build xml tree with user and rpc: + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + * 5. Translate input args to Netconf RPC, add to xml tree: + * 42 + * 6. Validate outgoing RPC and fill in default values + * 4299 + * 7. Send to RPC handler, either local or backend + * 8. Receive reply from local/backend handler as Netconf RPC + * 0 + * 9. Translate to restconf RPC data: + * JSON: {"example:output":{"x":0}} + * XML: 0 + * 10. Validate and send reply to originator */ int api_operations_post(clicon_handle h, @@ -1057,20 +1513,15 @@ api_operations_post(clicon_handle h, int retval = -1; int i; char *oppath = path; - yang_stmt *yrpc = NULL; yang_spec *yspec; - yang_stmt *yinput; - yang_stmt *youtput; - cxobj *xdata = NULL; + yang_stmt *youtput = NULL; + yang_stmt *yrpc = NULL; cxobj *xret = NULL; cxobj *xerr = NULL; /* malloced must be freed */ - cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; yang_node *y = NULL; - cxobj *xinput; - cxobj *xoutput; - cxobj *x; + cxobj *xoutput = NULL; cxobj *xa; cxobj *xe; char *username; @@ -1079,8 +1530,10 @@ api_operations_post(clicon_handle h, char *prefix = NULL; char *id = NULL; yang_stmt *ys = NULL; + char *namespace = NULL; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); + /* 1. Initialize */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -1094,216 +1547,188 @@ api_operations_post(clicon_handle h, if (oppath == NULL || strcmp(oppath,"/")==0){ if (netconf_operation_failed_xml(&xerr, "protocol", "Operation name expected") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); - - /* Find yang rpc statement, return yang rpc statement if found + /* 2. Get rpc module and name from uri (oppath) and find yang spec * POST {+restconf}/operations/ * * The field identifies the module name and rpc identifier * string for the desired operation. */ - if (yang_nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ + if (nodeid_split(oppath+1, &prefix, &id) < 0) /* +1 skip / */ goto done; if ((ys = yang_find((yang_node*)yspec, Y_MODULE, prefix)) == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang module not found") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } if ((yrpc = yang_find((yang_node*)ys, Y_RPC, id)) == NULL){ - clicon_debug(1, "%s rpc %s not found", __FUNCTION__, id); if (netconf_missing_element_xml(&xerr, "application", id, "RPC not defined") < 0) goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - /* Create an xml message: - * <"rpc">... - * eg + /* 3. Build xml tree with user and rpc: + * */ - /* Create config top-of-tree */ if ((xtop = xml_new("rpc", NULL, NULL)) == NULL) goto done; xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ + /* Here xtop is: */ if ((username = clicon_username_get(h)) != NULL){ if ((xa = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, username) < 0) goto done; + /* Here xtop is: */ } - /* XXX: something strange for rpc user */ - if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) + if ((ret = api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y)) < 0) goto done; + if (ret == 0){ /* validation failed */ + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + clicon_err_reset(); + if ((xe = xpath_first(xerr, "rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is: + * xbot is + * 4. Parse input data (arguments): + * JSON: {"example:input":{"x":0}} + * XML: 0 + */ + namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR); + clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data); if (data && strlen(data)){ - /* Parse input data as json or xml into xml */ - if (parse_xml){ - if (xml_parse_string(data, NULL, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - else if (json_parse_str(data, &xdata) < 0){ - if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) - goto done; - if ((xe = xpath_first(xerr, "rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; + if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot, + pretty, use_xml, parse_xml)) < 0) + goto done; + if (ret == 0) goto ok; - } - yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL); - /* xdata should have format */ - if ((xinput = xpath_first(xdata, "/input")) == NULL){ - xml_name_set(xdata, "input"); - xml_spec_set(xdata, yinput); /* needed for xml_spec_populate */ - if (yinput){ - if ((ret = xml_yang_validate_add(xdata, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - } - else{ - /* Add all input under path */ - x = NULL; - while (xml_child_nr(xinput)){ - x = xml_child_i(xinput, 0); - if (xml_addsub(xbot, x) < 0) - goto done; - } - if (yinput){ - xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ - /* XXX yinput <-> h ?*/ - if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xbot, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s err: %s", __FUNCTION__, cbuf_get(cbret)); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xbot, cbret)) < 0){ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe=xpath_first(xerr, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if (xml_apply0(xbot, CX_ELMNT, xml_default, NULL) < 0) - goto done; - } - } } - ret = 0; - xe = NULL; - while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { - /* Look for local (client-side) restconf plugins. */ - if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + /* Here xtop is: + 42 */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) goto done; - if (ret == 1){ /* Handled locally */ - if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) - goto done; - /* Local error: return it and quit */ - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - break; /* Just one if local */ + clicon_debug(1, "%s 5. Translate input args: %s", + __FUNCTION__, cbuf_get(ccc)); } - if (ret == 0){ /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) +#endif + /* 6. Validate outgoing RPC and fill in defaults */ + if (xml_spec_populate_rpc(h, xtop, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_rpc(xtop, cbret)) < 0) + goto done; + if (ret == 0){ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){ + if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) == NULL){ + clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)"); + goto done; + } + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } + /* Here xtop is (default values): + * 4299 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xtop, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 6. Validate and defaults:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + /* 7. Send to RPC handler, either local or backend + * Note (1) xtop is xbot is + * (2) local handler wants and backend wants + */ + /* Look for local (client-side) restconf plugins. + * -1:Error, 0:OK local, 1:OK backend + */ + if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) + goto done; + /* Local error: return it and quit */ + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } } - /* Check if RPC output section */ - if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) == NULL){ - /* If the RPC operation is invoked without errors and if the "rpc" or - * "action" statement has no "output" section, the response message - * MUST NOT include a message-body and MUST send a "204 No Content" - * status-line instead. - */ - FCGX_SetExitStatus(204, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - goto ok; + else { /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((xe = xpath_first(xret, "rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; + } } - if ((cbx = cbuf_new()) == NULL) + /* 8. Receive reply from local/backend handler as Netconf RPC + * 0 + */ +#if 0 + if (debug){ + cbuf *ccc=cbuf_new(); + if (clicon_xml2cbuf(ccc, xret, 0, 0) < 0) + goto done; + clicon_debug(1, "%s 8. Receive reply:%s", __FUNCTION__, cbuf_get(ccc)); + } +#endif + youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL); + if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace, + pretty, use_xml, &xoutput)) < 0) goto done; - if ((xoutput=xpath_first(xret, "/")) != NULL){ - xml_name_set(xoutput, "output"); - cbuf_reset(cbx); - xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; - if ((ret = xml_yang_validate_all(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - clicon_debug(1, "%s output validation failed", __FUNCTION__); - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0) - goto done; - if (ret == 0){ /* validation failed */ - if (xml_parse_string(cbuf_get(cbret), yspec, &xerr) < 0) - goto done; - if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL) - if (api_return_err(h, r, xe, pretty, use_xml) < 0) - goto done; - goto ok; - } - } - /* Sanity check of outgoing XML */ + if (ret == 0) + goto ok; + /* xoutput should now look: 0 */ FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); - if (xoutput){ - if (use_xml){ - if (clicon_xml2cbuf(cbx, xoutput, 0, pretty) < 0) - goto done; - } - else - if (xml2json_cbuf(cbx, xoutput, pretty) < 0) - goto done; - FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + cbuf_reset(cbret); + if (use_xml){ + if (clicon_xml2cbuf(cbret, xoutput, 0, pretty) < 0) + goto done; + /* xoutput should now look: 0 */ } + else{ + if (xml2json_cbuf(cbret, xoutput, pretty) < 0) + goto done; + /* xoutput should now look: {"example:output": {"x":0,"y":42}} */ + } + FCGX_FPrintF(r->out, "%s", cbuf_get(cbret)); + FCGX_FPrintF(r->out, "\r\n\r\n"); ok: retval = 0; done: @@ -1312,16 +1737,12 @@ api_operations_post(clicon_handle h, free(prefix); if (id) free(id); - if (xdata) - xml_free(xdata); if (xtop) xml_free(xtop); if (xret) xml_free(xret); if (xerr) xml_free(xerr); - if (cbx) - cbuf_free(cbx); if (cbret) cbuf_free(cbret); return retval; diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 08424521..ec20f373 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -249,7 +249,7 @@ restconf_stream(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cb, "%s", name); + cprintf(cb, "%s", name); /* Print all fields */ for (i=0; ith_format; else if (strcmp(optname, "pretty") == 0) *value = &th->th_pretty; + else if (strcmp(optname, "sort") == 0) + *value = &th->th_sort; else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -288,6 +292,9 @@ text_setopt(xmldb_handle xh, else if (strcmp(optname, "pretty") == 0){ th->th_pretty = (intptr_t)value; } + else if (strcmp(optname, "sort") == 0){ + th->th_sort = (intptr_t)value; + } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -358,6 +365,16 @@ xml_copy_marked(cxobj *x0, assert(x0 && x1); yt = xml_spec(x0); /* can be null */ + /* Copy all attributes */ + x = NULL; + while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) { + name = xml_name(x); + if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL) + goto done; + if (xml_copy(x, xcopy) < 0) + goto done; + } + /* Go through children to detect any marked nodes: * (3) Special case: key nodes in lists are copied if any * node in list is marked @@ -470,6 +487,10 @@ text_get(xmldb_handle xh, if (singleconfigroot(xt, &xt) < 0) goto done; } + /* XXX: should we validate file if read from disk? + * Argument against: we may want to have a semantically wrong file and wish + * to edit? + */ } /* xt == NULL */ /* Here xt looks like: ... */ @@ -530,12 +551,15 @@ text_get(xmldb_handle xh, /* Add default values (if not set) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; - /* Order XML children according to YANG */ - if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) - goto done; -#if 0 /* debug */ - if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) - clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__); + /* Order XML children according to YANG. + * XXX: should this be !th->th_sort? + */ + if (!th->th_sort) + if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) + goto done; +#if 1 /* debug */ + if (th->th_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) + clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); #endif if (debug>1) clicon_xml2file(stderr, xt, 0, 1); @@ -555,6 +579,7 @@ text_get(xmldb_handle xh, } /*! Modify a base tree x0 with x1 with yang spec y according to operation op + * @param[in] th text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 @@ -562,10 +587,11 @@ text_get(xmldb_handle xh, * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * Assume x0 and x1 are same on entry and that y is the spec - * @see put in clixon_keyvalue.c + * @see text_modify_top */ static int -text_modify(cxobj *x0, +text_modify(struct text_handle *th, + cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1, @@ -576,6 +602,8 @@ text_modify(cxobj *x0, char *opstr; char *x1name; char *x1cname; /* child name */ + cxobj *x0a; /* attribute */ + cxobj *x1a; /* attribute */ cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ @@ -607,6 +635,13 @@ text_modify(cxobj *x0, // int iamkey=0; if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } #if 0 /* If it is key I dont want to mark it */ if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0) @@ -679,6 +714,13 @@ text_modify(cxobj *x0, if (x0==NULL){ if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) goto done; + /* Copy xmlns attributes */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } @@ -699,7 +741,7 @@ text_modify(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; x0vec[i++] = x0c; } @@ -709,7 +751,7 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -740,6 +782,7 @@ text_modify(cxobj *x0, } /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 + * @param[in] th text handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) @@ -812,23 +855,19 @@ text_modify_top(struct text_handle *th, goto done; if (ymod != NULL) yc = yang_find_datanode((yang_node*)ymod, x1cname); - if (yc == NULL && _CLICON_XML_NS_ITERATE){ - int i; - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL) - break; - } + if (yc == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0) + goto done; } if (yc == NULL){ - if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) goto done; goto ok; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) + if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) @@ -840,7 +879,7 @@ text_modify_top(struct text_handle *th, return retval; } /* text_modify_top */ -/*! For containers without presence and no children, remove +/*! For containers without presence and no children(except attrs), remove * @param[in] x XML tree node * See section 7.5.1 in rfc6020bis-02.txt: * No presence: @@ -869,7 +908,7 @@ xml_container_presence(cxobj *x, } /* Mark node that is: container, have no children, dont have presence */ if (y->ys_keyword == Y_CONTAINER && - xml_child_nr(x)==0 && + xml_child_nr_notype(x, CX_ATTR)==0 && yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL) xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */ retval = 0; @@ -964,7 +1003,7 @@ text_put(xmldb_handle xh, goto done; #endif #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); #endif /* @@ -975,7 +1014,7 @@ text_put(xmldb_handle xh, goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) - goto ok; + goto fail; /* Remove NONE nodes if all subs recursively are also NONE */ if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) @@ -990,7 +1029,7 @@ text_put(xmldb_handle xh, if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) goto done; #if 0 /* debug */ - if (xml_child_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) + if (th->th_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); #endif /* Write back to datastore cache if first time */ @@ -1025,8 +1064,7 @@ text_put(xmldb_handle xh, } else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) goto done; - ok: - retval = 0; + retval = 1; done: if (cbretlocal && cbret) cbuf_free(cbret); @@ -1041,6 +1079,9 @@ text_put(xmldb_handle xh, if (!th->th_cache && x0) xml_free(x0); return retval; + fail: + retval = 0; + goto done; } /*! Copy database from db1 to db2 @@ -1434,7 +1475,7 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) + if (xmldb_put(h, db, op, NULL, xn, NULL) < 1) goto done; } else diff --git a/doc/FAQ.md b/doc/FAQ.md index 9ecf46ff..7ab690e0 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -42,9 +42,9 @@ The example: sudo make install ``` -## How do you run Clixon example commands? +## How do I run Clixon example commands? -- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml` +- Start a backend server: `clixon_backend -F -s init -f /usr/local/etc/example.xml` - Start a cli session: `clixon_cli -f /usr/local/etc/example.xml` - Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml` - Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data` @@ -72,6 +72,82 @@ grep clicon /etc/group clicon:x:1001:,www-data ``` +## How do I use the CLI? + +The easiest way to use Clixon is via the CLI. Once the backend is started +Example: +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## How do I use netconf? + +As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. +Example: +``` +clixon_netconf -qf /usr/local/etc/example.xml +]]>]]> +eth9ex:ethtrue]]>]]> +``` + +However, more useful is to run clixon_netconf as an SSH +subsystem. Register the subsystem in /etc/sshd_config: +``` + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml +``` +and then invoke it from a client using +``` + ssh -s netconf +``` + +## How do I use restconf? + +You can access clixon via REST API using restconf, such as using +curl. GET, PUT, POST are supported. + +You need a web-server, such as nginx, and start a restconf fcgi +daemon, clixon_restconf. + +For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: +``` +server { + ... + location /restconf { + fastcgi_pass unix:/www-data/fastcgi_restconf.sock; + include fastcgi_params; + } +} +``` +Start nginx daemon +``` +sudo /etc/init.d/nginx start +``` + +Example: +``` + curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type + [ + { + "ietf-interfaces:type": "ex:eth" + } + ] +``` +Read more in the (restconf)[../apps/restconf] docs. ## What about reference documentation? Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. You need to install doxygen and graphviz on your system. @@ -161,59 +237,6 @@ sudo docker run -td olofhagsand/clixon_example ``` Look in the example documentation for more info. -## How do I use netconf? - -As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. -Example: -``` - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml -``` - -However, more useful is to run clixon_netconf as an SSH -subsystem. Register the subsystem in /etc/sshd_config: -``` - Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml -``` -and then invoke it from a client using -``` - ssh -s netconf -``` - -## How do I use restconf? - -You can access clixon via REST API using restconf, such as using -curl. GET, PUT, POST are supported. - -You need a web-server, such as nginx, and start a restconf fcgi -daemon, clixon_restconf. - -For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default: -``` -server { - ... - location /restconf { - fastcgi_pass unix:/www-data/fastcgi_restconf.sock; - include fastcgi_params; - } -} -``` -Start nginx daemon -``` -sudo /etc/init.d/nginx start -``` - -Example: -``` - curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type - [ - { - "type": "eth" - } - ] -``` -Read more in the (restconf)[../apps/restconf] docs. - - ## Does Clixon support event streams? Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s. @@ -233,7 +256,7 @@ severity major; or via NETCONF: ``` clixon_netconf -qf /usr/local/etc/example.xml -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> 2018-09-30T12:44:59.657276faultEthernet0major]]>]]> ... @@ -242,11 +265,11 @@ or via restconf: ``` curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE ``` -Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io]. +Consult [clixon restconf](../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as [Nginx Nchan](https://nchan.io). ## How should I start the backend daemon? -There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it: +There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you start the daemon and how loading the configuration affects it: - none - Do not touch running state. Typically after crash when running state and db are synched. - init - Initialize running state. Start with a completely clean running state. - running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists. @@ -277,7 +300,7 @@ There are two ways to add extra XML to running database after start. Note that The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag): ``` - extra + extra ``` You add this via the -c option: @@ -289,12 +312,13 @@ The second way is by programming the plugin_reset() in the backend plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). ## I want to program. How do I extend the example? -See [../apps/example] +See [../apps/example](../apps/example) - example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. - example_cli.cli - Change the fixed part of the CLI commands - example_cli.c - Cli C-commands are placed here. - example_backend.c - Commit and validate functions. +- example_backend_nacm.c - Secondary example plugin (for authorization) - example_netconf.c - Netconf plugin - example_restconf.c - Add restconf authentication, etc. diff --git a/example/README.md b/example/README.md index 7621d9b5..a0a1d621 100644 --- a/example/README.md +++ b/example/README.md @@ -1,19 +1,33 @@ # Clixon example + * [Content](#content) + * [Compile and run](#compile) + * [Using the CLI](#using-the-cli) + * [Using netconf](#using-netconf) + * [Streams](#streams) + * [RPC Operations](#rpc-operations) + * [State data](#state-data) + * [Authentication and NACM](#authentication-and-nacm) + * [Systemd](#systemd) + * [Docker](#docker) + * [Plugins](#plugins) + +## Content + This directory contains a Clixon example which includes a simple example. It contains the following files: -* example.xml The configuration file. See yang/clixon-config@.yang for all available fields. -* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. -* example_cli.cli CLIgen specification. -* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). -* example_backend.c Backend callback plugin including example of: +* `example.xml` The configuration file. See (yang/clixon-config@.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields. +* `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules. +* `example_cli.cli` CLIgen specification. +* `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). +* `example_backend.c` Backend callback plugin including example of: * transaction callbacks (validate/commit), * notification, * rpc handler * state-data handler, ie non-config data -* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. -* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback -* example_netconf.c Netconf callback plugin -* Makefile.in Example makefile where plugins are built and installed +* `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically. +* `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback +* `example_netconf.c` Netconf callback plugin +* `Makefile.in` Example makefile where plugins are built and installed ## Compile and run @@ -47,10 +61,37 @@ Send restconf command curl -G http://127.0.0.1/restconf/data ``` -## Setting data example using netconf +## Using the CLI + +The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code. +There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details. + +The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it. +``` +clixon_cli -f /usr/local/etc/example.xml +cli> set interfaces interface eth9 ? + description enabled ipv4 + ipv6 link-up-down-trap-enable type +cli> set interfaces interface eth9 type ex:eth +cli> validate +cli> commit +cli> show configuration xml + + + eth9 + ex:eth + true + + +cli> delete interfaces interface eth9 +``` + +## Using Netconf + +The following example shows how to set data using netconf: ``` - + eth1 true @@ -65,13 +106,13 @@ Send restconf command ]]>]]> ``` -## Getting data using netconf +### Getting data using netconf ``` ]]>]]> ]]>]]> ]]>]]> -]]>]]> -]]>]]> +eth9ex:eth]]>]]> +]]>]]> ]]>]]> ``` @@ -80,23 +121,134 @@ Send restconf command The example has an EXAMPLE stream notification triggering every 5s. To start a notification stream in the session using netconf, create a subscription: ``` -EXAMPLE]]>]]> +EXAMPLE]]>]]> ]]>]]> -Routing notification]]>]]> -Routing notification]]>]]> +2019-01-02T10:20:05.929272faultEthernet0major]]>]]> ... ``` This can also be triggered via the CLI: ``` -cli> notify -cli> Routing notification -Routing notification +clixon_cli -f /usr/local/etc/example.xml +cli> notify +cli> event-class fault; +reportingEntity { + card Ethernet0; +} +severity major; ... +cli> no notify +cli> ``` -Restconf support is also supported, see [../apps/restconf/README.md]. +Restconf support is also supported, see (restc)[../apps/restconf/README.md]. -## Initializing a plugin + +## RPC Operations + +Clixon implements Yang RPC operations by an extension mechanism. The +extension mechanism enables you to add application-specific +operations. It works by adding user-defined callbacks for added +netconf operations. It is possible to use the extension mechanism +independent of the yang rpc construct, but it is recommended. The example includes an example: + +Example using CLI: +``` +cli> rpc ipv4 + rpc-reply { + route { + address-family ipv4; + next-hop { + next-hop-list 2.3.4.5; + } + source-protocol static; + } + } +``` +Netconf: +``` +ipv4]]>]]> +ipv42.3.4.5static]]>]]> +``` +Restconf: +``` +curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' +``` + +### Details + +The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)). + +In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data): +``` +static int +fib_route(clicon_handle h, + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ +{ + cprintf(cbret, "" + "ipv4" + "2.3.4.5" + "static" + ""); + return 0; +} +int +clixon_plugin_init(clicon_handle h) +{ +... + rpc_callback_register(h, fib_route, NULL, "fib-route"); +... +} +``` + +## State data + +Netconf and restconf GET also returns state data(not only configuration data). + +In YANG state data is specified with `config false;`. In the example, +`state` is state data, see (example.yang)[example.yang] + +To return state data, you need to write a backend state data callback +with the name "plugin_statedata" where you return an XML tree with +state. This is then merged with config data by the system. + +A static example of returning state data is in the example. Note that +a real example would poll or get the interface counters via a system +call, as well as use the "xpath" argument to identify the requested +state data. + +## Authentication and NACM +The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). +* A NACM backend plugin reporting the mandatory NACM state variables. + +## Systemd + +Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. + +## Docker + +Run the example as a docker container and access it from a host CLI as follows: +``` +ID=$(sudo docker run -td olofhagsand/clixon_example) +IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) +clixon_cli -a IPv4 -u $IP -f ./example.xml +``` + +Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: +``` +make docker +make push +sudo docker run -ti --rm you/clixon_example +``` + +Note that the configuration database is internal in the container, so +it is deleted if the container is restarted. To make the configuration +database persistent, you need to mount running_db using `-v` + +## Plugins The example includes a restonf, netconf, CLI and two backend plugins. Each plugin is initiated with an API struct followed by a plugin init function. @@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h) return &api; /* Return NULL on error */ } ``` - -## Operation data - -Clixon implements Yang RPC operations by an extension mechanism. The -extension mechanism enables you to add application-specific -operations. It works by adding user-defined callbacks for added -netconf operations. It is possible to use the extension mechanism -independent of the yang rpc construct, but it is recommended. The example includes an example: - -Example: -``` -cli> rpc ipv4 - - - -``` - -The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function). -``` - - - ipv4 - - -``` - -In the backend, a callback is registered (fib_route()) which handles the RPC. -``` -static int -fib_route(clicon_handle h, - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* Client session */ - void *regarg) /* Argument given at register */ -{ - cprintf(cbret, ""); - return 0; -} -int -clixon_plugin_init(clicon_handle h) -{ -... - rpc_callback_register(h, fib_route, NULL, "fib-route"); -... -} -``` -## State data - -Netconf and restconf GET also returns state data, in contrast to -config data. -p -In YANG state data is specified with "config false;". In the example, interface-state is state data. - -To return state data, you need to write a backend state data callback -with the name "plugin_statedata" where you return an XML tree with -state. This is then merged with config data by the system. - -A static example of returning state data is in the example. Note that -a real example would poll or get the interface counters via a system -call, as well as use the "xpath" argument to identify the requested -state data. - -## Authentication and NACM -The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): -* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). -* A NACM backend plugin reporting the mandatory NACM state variables. - -## Systemd files - -Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. - -## Docker - -Run the example as a docker container and access it from a host CLI as follows: -``` -ID=$(sudo docker run -td olofhagsand/clixon_example) -IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID) -clixon_cli -a IPv4 -u $IP -f ./example.xml -``` - -Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push: -``` -make docker -make push -sudo docker run -ti --rm you/clixon_example -``` - -Note that the configuration database is internal in the container, so -it is deleted if the container is restarted. To make the configuration -database persistent, you need to mount running_db using `-v` - - - diff --git a/example/example_backend.c b/example/example_backend.c index 35fe20d4..ae1f51d4 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -96,7 +96,7 @@ example_stream_timer(int fd, int retval = -1; clicon_handle h = (clicon_handle)arg; - /* XXX Change to actual netconf notifications */ + /* XXX Change to actual netconf notifications and namespace */ if (stream_notify(h, "EXAMPLE", "faultEthernet0major") < 0) goto done; if (example_stream_timer_setup(h) < 0) @@ -129,7 +129,7 @@ fib_route(clicon_handle h, /* Clicon handle */ void *arg, /* Client session */ void *regarg) /* Argument given at register */ { - cprintf(cbret, "" + cprintf(cbret, "" "ipv4" "2.3.4.5" "static" @@ -147,7 +147,7 @@ route_count(clicon_handle h, void *arg, void *regarg) /* Argument given at register */ { - cprintf(cbret, "42"); + cprintf(cbret, "42"); return 0; } @@ -180,9 +180,17 @@ example_rpc(clicon_handle h, /* Clicon handle */ { int retval = -1; cxobj *x = NULL; + char *namespace; + /* get namespace from rpc name, return back in each output parameter */ + if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){ + clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe)); + goto done; + } cprintf(cbret, ""); while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) { + if (xmlns_set(x, NULL, namespace) < 0) + goto done; if (clicon_xml2cbuf(cbret, x, 0, 0) < 0) goto done; } @@ -222,7 +230,7 @@ example_statedata(clicon_handle h, * Note this state needs to be accomanied by yang snippet * above */ - if (xml_parse_string("" + if (xml_parse_string("" "42" "", NULL, &xstate) < 0) goto done; @@ -251,17 +259,22 @@ example_reset(clicon_handle h, { int retval = -1; cxobj *xt = NULL; + int ret; - if (xml_parse_string("" + if (xml_parse_string("" "loex:loopback" "", NULL, &xt) < 0) goto done; - /* Replace parent w fiorst child */ + /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) + if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0) goto done; + if (ret == 0){ + clicon_err(OE_XML, 0, "Error when writing to XML database"); + goto done; + } retval = 0; done: if (xt != NULL) diff --git a/example/example_cli.c b/example/example_cli.c index 987aae30..01a375a3 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h, goto done; } /* Print result */ - xml_print(stdout, xml_child_i(xret, 0)); + xml2txt(stdout, xml_child_i(xret, 0), 1); retval = 0; done: if (xret) diff --git a/example/example_cli.cli b/example/example_cli.cli index 302a6cf3..892c687a 100644 --- a/example/example_cli.cli +++ b/example/example_cli.cli @@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{ } copy("Copy and create a new object") { interface("Copy interface"){ - ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); + ("name of interface to copy from") to("Copy to interface") ("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); } } discard("Discard edits (rollback 0)"), discard_changes(); diff --git a/example/example_netconf.c b/example/example_netconf.c index 9993e851..b70128de 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h, void *regarg) { clicon_debug(1, "%s restconf", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/example/example_restconf.c b/example/example_restconf.c index 43771662..ec0e8d27 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h, { // FCGX_Request *r = (FCGX_Request *)arg; clicon_debug(1, "%s", __FUNCTION__); - cprintf(cbret, "ok"); + cprintf(cbret, "ok"); return 0; } diff --git a/include/clixon_custom.h b/include/clixon_custom.h index cbb3f1df..8b7c5ae6 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -51,11 +51,6 @@ int strverscmp (__const char *__s1, __const char *__s2); */ #define XMLNS_YANG_ONLY 1 -/* Set for full XML namespace code in XML, NETCONF and YANG - * Experimental - */ -#undef ENABLE_XMLNS - /* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. * This is a backward compatible fix for 3.9 for CLIgen specification files * using model generation (CLIXON_CLI_GENMODEL). diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 68b19559..0a644680 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -43,6 +43,7 @@ int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json(FILE *f, cxobj *x, int pretty); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); +int json2xml_ns(yang_spec *yspec, cxobj *x, cxobj **xerr); int json_parse_str(char *str, cxobj **xt); int json_parse_file(int fd, yang_spec *yspec, cxobj **xt); diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 8f1b9da0..a615c663 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -52,7 +52,8 @@ int netconf_bad_element(cbuf *cb, char *type, char *info, char *element); int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element); int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message); int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message); -int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_namespace(cbuf *cb, char *type, char *namespace, char *message); +int netconf_unknown_namespace_xml(cxobj **xret, char *type, char *namespace, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message); int netconf_lock_denied(cbuf *cb, char *info, char *message); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index b5cb55c3..a849c6cd 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -153,6 +153,9 @@ static inline char *clicon_xmldb_dir(clicon_handle h){ static inline char *clicon_xmldb_plugin(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } +static inline int clicon_xml_sort(clicon_handle h){ + return clicon_option_bool(h, "CLICON_XML_SORT"); +} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index cebb0871..dd2eb290 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -86,7 +86,7 @@ int xml_chardata_encode(char **escp, char *fmt, ...); int uri_percent_decode(char *enc, char **str); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); - +int nodeid_split(char *nodeid, char **prefix, char **id); #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 68f332ab..2117bfef 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -45,6 +45,8 @@ * namespace (rfc6241 3.1) */ #define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" +/* default namespace statement, such as in */ +#define DEFAULT_XMLNS "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" /* * Types @@ -84,14 +86,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg); #define XML_FLAG_CHANGE 0x08 /* Node is changed (commits) or child changed rec */ #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ - /* Iterate through modules to find the matching datanode * or rpc if no xmlns attribute specifies namespace. - * This is loose semantics of finding namespaces. + * This is lazy non-strict semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -extern int _CLICON_XML_NS_ITERATE; +extern int _CLICON_XML_NS_STRICT; /* * Prototypes @@ -102,6 +103,7 @@ int xml_name_set(cxobj *xn, char *name); char *xml_prefix(cxobj *xn); int xml_prefix_set(cxobj *xn, char *name); int xml2ns(cxobj *x, char *localname, char **namespace); +int xmlns_set(cxobj *x, char *prefix, char *namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -117,7 +119,9 @@ int xml_type_set(cxobj *xn, enum cxobj_type type); int xml_child_nr(cxobj *xn); int xml_child_nr_type(cxobj *xn, enum cxobj_type type); +int xml_child_nr_notype(cxobj *xn, enum cxobj_type type); cxobj *xml_child_i(cxobj *xn, int i); +cxobj *xml_child_i_type(cxobj *xn, int i, enum cxobj_type type); cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc); cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); @@ -139,6 +143,7 @@ char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); char *xml_find_type_value(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); +cxobj *xml_find_type(cxobj *xn_parent, char *prefix, char *name, enum cxobj_type type); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index dba82884..9eba5bfd 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,10 +43,13 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); +int xml_yang_root(cxobj *x, cxobj **xr); +int xmlns_assign(cxobj *x); int xml_yang_validate_rpc(cxobj *xrpc, cbuf *cbret); int xml_yang_validate_add(cxobj *xt, cbuf *cbret); int xml_yang_validate_all(cxobj *xt, cbuf *cbret); int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret); +int xml_yang_find_non_strict(cxobj *x, yang_spec *yspec, yang_stmt **y); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, @@ -64,8 +67,7 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); int xml_spec_populate(cxobj *x, void *arg); -int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); -int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); +int api_path2xpath(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec, char **reason); diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 620456a0..c4ffeab3 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -37,14 +37,16 @@ #define _CLIXON_XML_SORT_H /* Sort and binary search of XML children - * Experimental + * XXX This variable is a kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ extern int xml_child_sort; /* * Prototypes */ -int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp); +int xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yp); int xml_cmp(const void* arg1, const void* arg2); int xml_sort(cxobj *x0, void *arg); cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); @@ -53,6 +55,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, int xml_sort, yang_stmt *yc); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index e9aee2e6..a9291583 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -252,17 +252,16 @@ yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); -int yang_nodeid_split(char *nodeid, char **prefix, char **id); int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace); +yang_stmt *yang_find_module_by_name(yang_spec *yspec, char *name); yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); int yang_order(yang_stmt *y); diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index aa4576df..6ec992c6 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -140,6 +140,7 @@ clicon_err_reset(void) * @param[in] err Error number, typically errno * @param[in] suberr Sub-error number * @param[in] reason Error string, format with argv + * @see clicon_err_reser Resetting the global error variables. */ int clicon_err_fn(const char *fn, diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 06aa71c9..1cd99af1 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -55,10 +55,12 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_queue.h" +#include "clixon_string.h" #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_netconf_lib.h" #include "clixon_json.h" #include "clixon_json_parse.h" @@ -90,43 +92,31 @@ enum childtype{ ANY_CHILD, /* eg or */ }; -/*! Number of children EXCEPT attributes - * @param[in] xn xml node - * @retval number of children in XML tree (except children of type CX_ATTR) - * @see xml_child_nr - */ -static int -xml_child_nr_noattr(cxobj *xn) -{ - cxobj *x = NULL; - int nr = 0; - - while ((x = xml_child_each(xn, x, -1)) != NULL) { - if (xml_type(x) != CX_ATTR) - nr++; - } - return nr; -} - /*! x is element and has exactly one child which in turn has none * remove attributes from x * Clone from clixon_xml_map.c */ static enum childtype -childtype(cxobj *x) +child_type(cxobj *x) { - cxobj *xc1; /* the only child of x */ + cxobj *xc; /* the only child of x */ int clen; /* nr of children */ - clen = xml_child_nr_noattr(x); + clen = xml_child_nr_notype(x, CX_ATTR); if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ if (clen == 0) return NULL_CHILD; if (clen > 1) return ANY_CHILD; - xc1 = xml_child_i(x, 0); /* From here exactly one child */ - if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY) + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -2; /* n/a */ + if (xml_child_nr_notype(xc, CX_ATTR) == 0 && xml_type(xc)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; @@ -175,6 +165,9 @@ arraytype2str(enum array_element_type lt) return ""; } +/*! Check typeof x in array + * Some complexity when x is in different namespaces + */ static enum array_element_type array_eval(cxobj *xprev, cxobj *x, @@ -184,7 +177,10 @@ array_eval(cxobj *xprev, int eqprev=0; int eqnext=0; yang_stmt *ys; + char *nsx; /* namespace of x */ + char *ns2; + nsx = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); if (xml_type(x)!=CX_ELMNT){ array=BODY_ARRAY; goto done; @@ -192,12 +188,18 @@ array_eval(cxobj *xprev, ys = xml_spec(x); if (xnext && xml_type(xnext)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xnext))==0) - eqnext++; + strcmp(xml_name(x),xml_name(xnext))==0){ + ns2 = xml_find_type_value(xnext, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqnext++; + } if (xprev && xml_type(xprev)==CX_ELMNT && - strcmp(xml_name(x),xml_name(xprev))==0) - eqprev++; + strcmp(xml_name(x),xml_name(xprev))==0){ + ns2 = xml_find_type_value(xprev, NULL, "xmlns", CX_ATTR); + if (nsx && ns2 && strcmp(nsx,ns2)==0) + eqprev++; + } if (eqprev && eqnext) array = MIDDLE_ARRAY; else if (eqprev) @@ -316,8 +318,8 @@ xml2json1_cbuf(cbuf *cb, * module name */ prefix = xml_prefix(x); - if (xml2ns(x, prefix, &namespace) < 0) - goto done; + namespace = xml_find_type_value(x, prefix, "xmlns", CX_ATTR); + if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ yspec = ys_spec(ys); /* Find module name associated with namspace URI */ @@ -325,7 +327,7 @@ xml2json1_cbuf(cbuf *cb, (ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ modname = ymod->ys_argument; } - childt = childtype(x); + childt = child_type(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), @@ -442,7 +444,7 @@ xml2json1_cbuf(cbuf *cb, xc_arraytype, level+1, pretty, 0, bodystr0) < 0) goto done; - if (i%s" "%s" - "%s" + "<%s>%s" "error", - type, tag, element) < 0) + type, tag, infotag, element, infotag) < 0) goto done; if (message && xml_parse_va(&xerr, NULL, "%s", message) < 0) @@ -363,7 +364,8 @@ netconf_missing_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "missing-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "missing-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -387,7 +389,8 @@ netconf_missing_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "missing-element", element, message); + return netconf_common_xml(xret, type, "missing-element", + "bad-element", element, message); } /*! Create Netconf bad-element error XML tree according to RFC 6241 App A @@ -408,7 +411,8 @@ netconf_bad_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "bad-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "bad-element", + "bad-element",element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -424,7 +428,7 @@ netconf_bad_element_xml(cxobj **xret, char *element, char *message) { - return netconf_element_xml_common(xret, type, "bad-element", element, message); + return netconf_common_xml(xret, type, "bad-element", "bad-element", element, message); } /*! Create Netconf unknown-element error XML tree according to RFC 6241 App A @@ -444,7 +448,8 @@ netconf_unknown_element(cbuf *cb, int retval = -1; cxobj *xret = NULL; - if (netconf_element_xml_common(&xret, type, "unknown-element", element, message) < 0) + if (netconf_common_xml(&xret, type, "unknown-element", + "bad-element", element, message) < 0) goto done; if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) goto done; @@ -454,13 +459,23 @@ netconf_unknown_element(cbuf *cb, xml_free(xret); return retval; } + +/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A + * + * An unexpected element is present. + * @param[out] xret XML buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] element Bad element name + * @param[in] message Error message + */ int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message) { - return netconf_element_xml_common(xret, type, "unknown-element", element, message); + return netconf_common_xml(xret, type, "unknown-element", + "bad-element", element, message); } /*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A @@ -474,35 +489,32 @@ netconf_unknown_element_xml(cxobj **xret, int netconf_unknown_namespace(cbuf *cb, char *type, - char *info, + char *namespace, char *message) { - int retval = -1; - char *encstr = NULL; + int retval = -1; + cxobj *xret = NULL; - if (cprintf(cb, "" - "%s" - "unknown-namespace" - "%s" - "error", - type, info) <0) - goto err; - if (message){ - if (xml_chardata_encode(&encstr, "%s", message) < 0) - goto done; - if (cprintf(cb, "%s", encstr) < 0) - goto err; - } - if (cprintf(cb, "") <0) - goto err; + if (netconf_common_xml(&xret, type, "unknown-namespace", + "bad-namespace", namespace, message) < 0) + goto done; + if (clicon_xml2cbuf(cb, xret, 0, 0) < 0) + goto done; retval = 0; done: - if (encstr) - free(encstr); + if (xret) + xml_free(xret); return retval; - err: - clicon_err(OE_XML, errno, "cprintf"); - goto done; +} + +int +netconf_unknown_namespace_xml(cxobj **xret, + char *type, + char *namespace, + char *message) +{ + return netconf_common_xml(xret, type, "unknown-namespace", + "bad-namespace", namespace, message); } /*! Create Netconf access-denied error cbuf according to RFC 6241 App A @@ -989,7 +1001,7 @@ netconf_trymerge(cxobj *x, } /*! Load ietf netconf yang module and set enabled features - * The features added are: + * The features added are (in order): * candidate (8.3) * validate (8.6) * startup (8.7) diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 45c3948b..e6410ad8 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -253,9 +253,8 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = 1; -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = 0; /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; @@ -281,9 +280,8 @@ clicon_options_main(clicon_handle h, xml_child_sort = 1; else xml_child_sort = 0; -#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ - _CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE"); -#endif + /* XXX Kludge to low-level functions to search for xml in all yang modules */ + _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index aa3ac4ac..396a0665 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -351,7 +351,7 @@ clicon_rpc_edit_config(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); @@ -787,7 +787,7 @@ clicon_rpc_create_subscription(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("" + if ((msg = clicon_msg_encode("" "%s" "" "", diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 480805fb..a7c05f39 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -562,6 +562,54 @@ clicon_str2int(const map_str2int *mstab, return -1; } +/*! Split colon-separated node identifier into prefix and name + * @param[in] node-id + * @param[out] prefix Malloced string. May be NULL. + * @param[out] id Malloced identifier. + * @retval 0 OK + * @retval -1 Error + * @code + * char *prefix = NULL; + * char *id = NULL; + * if (nodeid_split(nodeid, &prefix, &id) < 0) + * goto done; + * if (prefix) + * free(prefix); + * if (id) + * free(id); + * @note caller need to free id and prefix after use + */ +int +nodeid_split(char *nodeid, + char **prefix, + char **id) +{ + int retval = -1; + char *str; + + if ((str = strchr(nodeid, ':')) == NULL){ + if ((*id = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + else{ + if ((*prefix = strdup(nodeid)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + (*prefix)[str-nodeid] = '\0'; + str++; + if ((*id = strdup(str)) == NULL){ + clicon_err(OE_YANG, errno, "strdup"); + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! strndup() for systems without it, such as xBSD */ #ifndef HAVE_STRNDUP diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 59292ba9..6849e94d 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -62,6 +62,8 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" /* xml_spec_populate */ +#include "clixon_xml_map.h" /* xml_spec_populate */ #include "clixon_xml_sort.h" #include "clixon_xml_parse.h" @@ -133,9 +135,9 @@ struct xml{ * or rpc if no xmlns attribute specifies namespace. * This is loose semantics of finding namespaces. * And it is wrong, but is the way Clixon originally was written." - * @see CLICON_XML_NS_ITERATE clixon configure option + * @see CLICON_XML_NS_STRICT clixon configure option */ -int _CLICON_XML_NS_ITERATE = 0; +int _CLICON_XML_NS_STRICT = 1; /* Mapping between xml type <--> string */ static const map_str2int xsmap[] = { @@ -226,8 +228,7 @@ xml_prefix_set(cxobj *xn, return 0; } - -/*! Given an xml tree return URI namespace: default or localname given +/*! Given an xml tree return URI namespace recursively : default or localname given * * Given an XML tree and a prefix (or NULL) return URI namespace. * @param[in] x XML tree @@ -235,7 +236,7 @@ xml_prefix_set(cxobj *xn, * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree * @retval 0 OK * @retval -1 Error - * @see xmlns_check XXX coordinate + * @see xmlns_check XXX can these be merged? */ int xml2ns(cxobj *x, @@ -246,9 +247,9 @@ xml2ns(cxobj *x, char *ns; cxobj *xp; - if (prefix != NULL) /* xmlns: */ + if (prefix != NULL) /* xmlns:="" */ ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); - else /* default ns */ + else /* xmlns="" */ ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); /* namespace not found, try parent */ @@ -270,6 +271,40 @@ xml2ns(cxobj *x, return retval; } +/*! Add a namespace attribute to an XML node, either default or specific prefix + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then set default xmlns + * @param[out] namespace URI namespace (or NULL). Will be copied + * @retval 0 OK + * @retval -1 Error + * @see xml2ns + */ +int +xmlns_set(cxobj *x, + char *prefix, + char *namespace) +{ + int retval = -1; + cxobj *xa; + + if (prefix != NULL){ /* xmlns:="" */ + if ((xa = xml_new(prefix, x, NULL)) == NULL) + goto done; + if (xml_prefix_set(xa, "xmlns") < 0) + goto done; + } + else{ /* xmlns="" */ + if ((xa = xml_new("xmlns", x, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + } + if (xml_value_set(xa, namespace) < 0) + goto done; + retval = 0; + done: + return retval; +} + /*! See if xmlns:[=] exists, if so return * * @param[in] xn XML node @@ -483,6 +518,8 @@ xml_type_set(cxobj *xn, /*! Get number of children * @param[in] xn xml node * @retval number of children in XML tree + * @see xml_child_nr_type + * @see xml_child_nr_notype */ int xml_child_nr(cxobj *xn) @@ -490,10 +527,33 @@ xml_child_nr(cxobj *xn) return xn->x_childvec_len; } +/*! Get number of children of EXCEPT specific type + * @param[in] xn xml node + * @param[in] type XML type or -1 for all + * @retval number of typed children in XML tree (except type) + * @see xml_child_nr + * @see xml_child_nr_type + */ +int +xml_child_nr_notype(cxobj *xn, + enum cxobj_type type) +{ + cxobj *x = NULL; + int nr = 0; + + while ((x = xml_child_each(xn, x, -1)) != NULL) { + if (xml_type(x) != type) + nr++; + } + return nr; +} + /*! Get number of children of specific type * @param[in] xn xml node * @param[in] type XML type or -1 for all * @retval number of typed children in XML tree + * @see xml_child_nr + * @see xml_child_nr_notype */ int xml_child_nr_type(cxobj *xn, @@ -521,6 +581,28 @@ xml_child_i(cxobj *xn, return NULL; } +/*! Get a specific child of a specific type + * @param[in] xn xml node + * @param[in] i the number of the child of specific type + * @param[in] type Child type + * @retval child in XML tree, or NULL if no such child, or empty child + * @see xml_child_i + */ +cxobj * +xml_child_i_type(cxobj *xn, + int i, + enum cxobj_type type) +{ + cxobj *x = NULL; + int it = 0; + + while ((x = xml_child_each(xn, x, type)) != NULL) { + if (x->x_type == type && (i == it++)) + return x; + } + return NULL; +} + /*! Set specific child * @param[in] xn xml node * @param[in] i the number of the child, eg order in children vector @@ -938,7 +1020,7 @@ xml_body_get(cxobj *xt) return NULL; } -/*! Find and return the value of an xml child of specific type +/*! Find and return the value of an xml child of specific type given prefix and name * * The value can be of an attribute only * @param[in] xt xml tree node @@ -950,6 +1032,7 @@ xml_body_get(cxobj *xt) * char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR); * @endcode * @note, make a copy of the return value to use it properly + * @see xml_find_type return the xml object * @see xml_find_value where a body can be found as well */ char * @@ -957,6 +1040,32 @@ xml_find_type_value(cxobj *xt, char *prefix, char *name, enum cxobj_type type) +{ + cxobj *x; + + if ((x = xml_find_type(xt, prefix, name, type)) != NULL) + return xml_value(x); + return NULL; +} + +/*! Find and return the xml child of specific type given prefix and name + * + * The value can be of an attribute only + * @param[in] xt xml tree node + * @param[in] prefix Prefix (namespace local name) or NULL + * @param[in] name name of xml tree node (eg attr name or "body") + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node + * @code + * cxobj *x = xml_find_type(x, "prefix", "name", CX_ATTR); + * @endcode + * @see xml_find_value where a body can be found as well + */ +cxobj * +xml_find_type(cxobj *xt, + char *prefix, + char *name, + enum cxobj_type type) { cxobj *x = NULL; int pmatch; /* prefix match */ @@ -969,7 +1078,7 @@ xml_find_type_value(cxobj *xt, else pmatch = 1; if (pmatch && strcmp(name, xml_name(x)) == 0) - return xml_value(x); + return x; } return NULL; } @@ -1397,6 +1506,8 @@ _xml_parse(const char *str, goto done; /* Sort the complete tree after parsing */ if (yspec){ + if (xml_apply0(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + goto done; if (xml_apply0(xt, CX_ELMNT, xml_sort, NULL) < 0) goto done; if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) @@ -1592,7 +1703,7 @@ xml_parse_va(cxobj **xtop, return retval; } -/*! Copy single xml node without copying children +/*! Copy single xml node frm x0 to x1 without copying children */ int xml_copy_one(cxobj *x0, diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index c3a4126a..32aab817 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -380,7 +380,8 @@ xmldb_get(clicon_handle h, * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". - * @retval 0 OK + * @retval 1 OK + * @retval 0 Failed, cbret contains error xml message * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code @@ -388,8 +389,10 @@ xmldb_get(clicon_handle h, * cxobj *xret = NULL; * if (xml_parse_string("17", yspec, &xt) < 0) * err; - * if (xmldb_put(xh, "running", OP_MERGE, xt, cbret) < 0) + * if ((ret = xmldb_put(xh, "running", OP_MERGE, xt, cbret)) < 0) * err; + * if (ret==0) + * cbret contains netconf error message * @endcode * @note that you can add both config data and state data. In comparison, * xmldb_get has a parameter to get config data only. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 2644f494..5d210644 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -50,6 +50,7 @@ * +---------+ * | file | * +---------+ + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -95,14 +96,20 @@ static int tleaf(cxobj *x) { - cxobj *c; + cxobj *xc; if (xml_type(x) != CX_ELMNT) return 0; - if (xml_child_nr(x) != 1) + if (xml_child_nr_notype(x, CX_ATTR) != 1) return 0; - c = xml_child_i(x, 0); - return (xml_child_nr(c) == 0); + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -1; /* n/a */ + return (xml_child_nr_notype(xc, CX_ATTR) == 0); } /*! Translate XML -> TEXT @@ -114,39 +121,42 @@ xml2txt(FILE *f, cxobj *x, int level) { - cxobj *xe = NULL; + cxobj *xc = NULL; int children=0; - char *term = NULL; int retval = -1; - xe = NULL; /* count children */ - while ((xe = xml_child_each(x, xe, -1)) != NULL) - children++; - if (!children){ - if (xml_type(x) == CX_BODY){ - term = xml_value(x); + xc = NULL; /* count children (elements and bodies, not attributes) */ + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + children++; + if (!children){ /* If no children print line */ + switch (xml_type(x)){ + case CX_BODY: + fprintf(f, "%s;\n", xml_value(x)); + break; + case CX_ELMNT: + fprintf(f, "%*s;\n", 4*level, xml_name(x)); + break; + default: + break; } - else{ - fprintf(f, "%*s", 4*level, ""); - term = xml_name(x); - } - fprintf(f, "%s;\n", term); - retval = 0; - goto done; + goto ok; } fprintf(f, "%*s", 4*level, ""); fprintf(f, "%s ", xml_name(x)); if (!tleaf(x)) fprintf(f, "{\n"); - xe = NULL; - while ((xe = xml_child_each(x, xe, -1)) != NULL){ - if (xml2txt(f, xe, level+1) < 0) - break; + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + if (xml2txt(f, xc, level+1) < 0) + break; } if (!tleaf(x)) fprintf(f, "%*s}\n", 4*level, ""); + ok: retval = 0; - done: + // done: return retval; } @@ -172,7 +182,10 @@ xml2cli(FILE *f, int match; char *body; - ys = xml_spec(x); + if (xml_type(x)==CX_ATTR) + goto ok; + if ((ys = xml_spec(x)) == NULL) + goto ok; if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); @@ -361,6 +374,37 @@ validate_identityref(cxobj *xt, goto done; } +/*! Given an XML node, return root node + * A root node is an ancestor xr of x with one or both of the following properties + * - its XML parent is NULL parent, + * - its associated yang specification's parent is a yang module. + * @param[in] x XML node + * @param[out] xr XML root + */ +int +xml_yang_root(cxobj *x, + cxobj **xr) +{ + int retval = -1; + cxobj *xp; + yang_stmt *y; + yang_stmt *yp; + + while ((xp = xml_parent(x)) != NULL){ + if ((y = xml_spec(x)) != NULL && + (yp = (yang_stmt*)y->ys_parent) != NULL) + /* Actually, maybe only the Y_MODULE clause is relevant */ + if (yp==NULL || + yp->ys_keyword == Y_MODULE || + yp->ys_keyword == Y_SUBMODULE) + break; /* x is the root */ + x = xp; + } + *xr = x; + retval = 0; + return retval; +} + /*! Validate an RPC node * @param[in] xt XML node to be validated * @retval 1 Validation OK @@ -408,7 +452,6 @@ xml_yang_validate_rpc(cxobj *xrpc, int retval = -1; yang_stmt *yn=NULL; /* rpc name */ cxobj *xn; /* rpc name */ - int ret; if (strcmp(xml_name(xrpc), "rpc")){ clicon_err(OE_XML, EINVAL, "Expected RPC"); @@ -417,16 +460,15 @@ xml_yang_validate_rpc(cxobj *xrpc, xn = NULL; /* xn is name of rpc, ie */ while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { - if ((yn = xml_spec(xn)) == NULL) - goto fail; - if ((ret = xml_yang_validate_all(xn, cbret)) < 0) - goto fail; - if (ret == 0) - goto fail; - if ((ret = xml_yang_validate_add(xn, cbret)) < 0) - goto fail; - if (ret == 0) + if ((yn = xml_spec(xn)) == NULL){ + if (netconf_unknown_element(cbret, "application", xml_name(xn), NULL) < 0) + goto done; goto fail; + } + if ((retval = xml_yang_validate_all(xn, cbret)) < 1) + goto done; /* error or validation fail */ + if ((retval = xml_yang_validate_add(xn, cbret)) < 1) + goto done; /* error or validation fail */ if (xml_apply0(xn, CX_ELMNT, xml_default, NULL) < 0) goto done; } @@ -443,9 +485,9 @@ xml_yang_validate_rpc(cxobj *xrpc, * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -456,6 +498,7 @@ xml_yang_validate_rpc(cxobj *xrpc, * fail; * @endcode * @see xml_yang_validate_all + * @see xml_yang_validate_rpc * @note Should need a variant accepting cxobj **xret */ int @@ -544,9 +587,9 @@ xml_yang_validate_add(cxobj *xt, /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @param[out] cbret Error buffer + * @param[out] cbret Error buffer (set w netconf error if retval == 0) * @retval 1 Validation OK - * @retval 0 Validation failed + * @retval 0 Validation failed (cbret set) * @retval -1 Error * @code * cxobj *x; @@ -680,6 +723,8 @@ xml_yang_validate_all(cxobj *xt, goto done; } +/*! Translate a single xml node to a cligen variable vector. Note not recursive + */ int xml_yang_validate_all_top(cxobj *xt, cbuf *cbret) @@ -695,6 +740,47 @@ xml_yang_validate_all_top(cxobj *xt, return 1; } +/*! Given XML node x, find yang spec in _any_ module matching name + * This is non-struct namespace semantics (not correct) but necessary + * in historic Clixon code. + * Also, add a proper default namespaces statement (xmlns="uri") in x + * @param[in] x XML node (find yang statement on this one) + * @param[in] yspec Top-level yang spec + * @param[out] y Yang stmt associated to x. NULL i not found + * @retval 0 OK + * @see CLICON_XML_NS_STRICT clixon config option + */ +int +xml_yang_find_non_strict(cxobj *x, + yang_spec *yspec, + yang_stmt **yp) +{ + int retval = -1; + char *name; + yang_stmt *ymod; + int i; + yang_stmt *y=NULL; + char *ns; + + name = xml_name(x); + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) + break; + } + if (y){ + *yp = y; + if ((ns = yang_find_mynamespace(ymod)) != NULL){ + if (xml_find_type_value(x, NULL, "xmlns", CX_ATTR) == NULL) + if (xmlns_set(x, NULL, ns) < 0) + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! Translate a single xml node to a cligen variable vector. Note not recursive * @param[in] xt XML tree containing one top node * @param[in] ys Yang spec containing type specification of top-node of xt @@ -861,28 +947,6 @@ cvec2xml_1(cvec *cvv, return retval; } - -/*! Find next yang node, either start from yang_spec or some yang-node - * @param[in] y Node spec or sny yang-node - * @param[in] name Name of childnode to find - * @retval ys yang statement - * @retval NULL Error: no node found - */ -static yang_stmt * -yang_next(yang_node *y, - char *name) -{ - yang_stmt *ys; - - if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); - else - ys = yang_find_datanode(y, name); - if (ys == NULL) - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - return ys; -} - /*! Recursive help function to compute differences between two xml trees * @param[in] x1 First XML tree * @param[in] x2 Second XML tree @@ -920,9 +984,12 @@ xml_diff1(yang_stmt *ys, */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x1c))) == NULL) + if ((yc = xml_spec(x1c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; - if (match_base_child(x2, x1c, &x2c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x2, x1c, &x2c, xml_child_sort, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) @@ -954,9 +1021,12 @@ xml_diff1(yang_stmt *ys, */ x2c = NULL; while ((x2c = xml_child_each(x2, x2c, CX_ELMNT)) != NULL){ - if ((yc = yang_next((yang_node*)ys, xml_name(x2c))) == NULL) + if ((yc = xml_spec(x2c)) == NULL){ + clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; - if (match_base_child(x1, x2c, &x1c, yc) < 0) + } + /* XXX xml_child_sort is global */ + if (match_base_child(x1, x2c, &x1c, xml_child_sort, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1025,10 +1095,11 @@ xml_diff(yang_spec *yspec, * Recursively construct it to the top. * Example: * yang: container a -> list b -> key c -> leaf d - * xpath: /a/b/%s/d + * xpath: /modname:a/b/%s/d * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] cb api_path_fmt, + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ static int yang2api_path_fmt_1(yang_stmt *ys, @@ -1040,28 +1111,35 @@ yang2api_path_fmt_1(yang_stmt *ys, cvec *cvk = NULL; /* vector of index keys */ int retval = -1; - yp = ys->ys_parent; - if (yp != NULL && + if ((yp = ys->ys_parent) == NULL){ + clicon_err(OE_YANG, EINVAL, "yang expected parent %s", ys->ys_argument); + goto done; + } + if (yp != NULL && /* XXX rm */ yp->yn_keyword != Y_MODULE && yp->yn_keyword != Y_SUBMODULE){ - if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) + if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; + cprintf(cb, "/"); } + else /* top symbol - mark with name prefix */ + cprintf(cb, "/%s:", yp->yn_argument); + if (inclkey){ if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } else{ #if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) - cprintf(cb, "/%s", ys->ys_argument); /* Not if leaf and key */ + cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else #endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) - cprintf(cb, "/%s", ys->ys_argument); + cprintf(cb, "%s", ys->ys_argument); } switch (ys->ys_keyword){ @@ -1095,6 +1173,7 @@ yang2api_path_fmt_1(yang_stmt *ys, * @param[in] ys Yang statement * @param[in] inclkey If set include key leaf (eg last leaf d in ex) * @param[out] api_path_fmt XML api path. Needs to be freed after use. + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int yang2api_path_fmt(yang_stmt *ys, @@ -1148,6 +1227,8 @@ yang2api_path_fmt(yang_stmt *ys, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * api_path: /subif-entry=foo/subid + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2api_path(char *api_path_fmt, @@ -1211,7 +1292,6 @@ api_path_fmt2api_path(char *api_path_fmt, return retval; } - /*! Transform an xml key format and a vector of values to an XML path * Used to input xmldb_get() or xmldb_get_vec * @param[in] api_path_fmt XML key format @@ -1230,6 +1310,8 @@ api_path_fmt2api_path(char *api_path_fmt, * api_path_fmt: /subif-entry=%s,%s/subid * cvv: foo * xpath: /subif-entry[if-name=foo]/subid" + * + * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 */ int api_path_fmt2xpath(char *api_path_fmt, @@ -1470,6 +1552,9 @@ xml_default(cxobj *xt, /*! Order XML children according to YANG * @param[in] xt XML top of tree + * @param[in] arg Dummy (so it can be called from xml_apply) + * @see xml_sort XXX: how do they relate? + * Called from text_get *only* */ int xml_order(cxobj *xt, @@ -1603,9 +1688,7 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *y=NULL; /* yang node */ yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ - // yang_stmt *ya = NULL; /* arg */ cxobj *x; - // cxobj *xi; int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ @@ -1621,7 +1704,7 @@ xml_spec_populate_rpc(clicon_handle h, /* Loose semantics: loop through all modules to find the node */ if (y == NULL && - clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){ + !clicon_option_bool(h, "CLICON_XML_NS_STRICT")){ for (i=0; iyp_len; i++){ ymod = yspec->yp_stmt[i]; if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) @@ -1633,16 +1716,8 @@ xml_spec_populate_rpc(clicon_handle h, if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ /* kludge rpc -> input */ xml_spec_set(x, yi); -#if 1 if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; -#else - xi = NULL; - while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { - if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) - xml_spec_set(xi, ya); - } -#endif } } } @@ -1651,13 +1726,14 @@ xml_spec_populate_rpc(clicon_handle h, return retval; } + /*! Add yang specification backpointer to XML node * @param[in] xt XML tree node * @param[in] arg Yang spec * @note This may be unnecessary if yspec is set on creation * @note For subs to anyxml nodes will not have spec set * @note No validation is done,... XXX - * @note relies on kludge _CLICON_XML_NS_ITERATE + * @note relies on kludge _CLICON_XML_NS_STRICT * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode @@ -1674,7 +1750,6 @@ xml_spec_populate(cxobj *x, yang_stmt *ymod; /* yang module */ cxobj *xp; /* xml parent */ char *name; - int i; yspec = (yang_spec*)arg; if (xml_spec(x)) @@ -1688,15 +1763,12 @@ xml_spec_populate(cxobj *x, goto done; if (ymod != NULL) y = yang_find_datanode((yang_node*)ymod, name); - /* Loose semantics: loop through all modules to find the node - * XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE") + /* Non-strict semantics: loop through all modules to find the node + * XXX clicon_option_bool(h, "CLICON_XML_NS_STRICT") */ - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) + goto done; } } if (y) @@ -1713,15 +1785,19 @@ xml_spec_populate(cxobj *x, return retval; } - /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] - * @param[in] yspec Yang spec - * @param[in] pcvec api-path as cvec - * @param[in] pi Offset of cvec, where api-path starts - * @param[out] path The xpath as cbuf variable string, must be initializeed - * The api-path has some wierd encoding, use api_path2xpath() if you are - * confused. + * eg example:a/b -> ex:a/b + * @param[in] yspec Yang spec + * @param[in] cvv api-path as cvec + * @param[in] offset Offset of cvec, where api-path starts + * @param[out] xpath The xpath as cbuf variable string, must be initializeed + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * + * @note both retval 0 and -1 set clicon_err, but the later is fatal + * @note Not proper namespace translation from api-path 2 xpath * It works like this: * Assume origin incoming path is * "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is: @@ -1729,46 +1805,63 @@ xml_spec_populate(cxobj *x, * which means the api-path is ["a" "b=c"] corresponding to "a/b=c" * @code * cbuf *xpath = cbuf_new(); - * if (api_path2xpath_cvv(yspec, cvv, i, xpath) + * cvec *cvv = NULL; + * if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0) * err; + * if ((ret = api_path2xpath(yspec, cvv, 0, cxpath)) < 0) + * err; + * if (ret == 0){ + * ... access error string in clicon_err_reason + * clicon_err_reset(); + * return; + * } * ... access xpath as cbuf_get(xpath) * cbuf_free(xpath) * @endcode - * @see api_path2xpath for string api-path argument + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xml For api-path to xml tree */ int -api_path2xpath_cvv(yang_spec *yspec, - cvec *cvv, - int offset, - cbuf *xpath) +api_path2xpath(yang_spec *yspec, + cvec *cvv, + int offset, + cbuf *xpath) { int retval = -1; int i; cg_var *cv; - char *name; + char *nodeid; + char *prefix = NULL; + char *name = NULL; cvec *cvk = NULL; /* vector of index keys */ yang_stmt *y = NULL; + yang_stmt *ymod; char *val; char *v; cg_var *cvi; for (i=offset; iyn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, nodeclass); - } - else { - y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): - yang_find_datanode((yang_node*)y0, name); - } - if (y == NULL){ - clicon_err(OE_YANG, errno, "No yang node found: %s", name); + /* Split into prefix and localname */ + if (nodeid_split(nodeid, &prefix, &name) < 0) goto done; + if (y0->yn_keyword == Y_SPEC){ /* top-node */ + if (prefix == NULL){ + clicon_err(OE_XML, EINVAL, "api-path element '%s', expected prefix:name", nodeid); + goto fail; + } + if ((ymod = yang_find_module_by_name((yang_spec*)y0, prefix)) == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path element prefix: '%s', no such yang module", prefix); + goto fail; + } + namespace = yang_find_mynamespace(ymod); + y0 = (yang_node*)ymod; + + } + y = (nodeclass==YC_SCHEMANODE)? + yang_find_schemanode((yang_node*)y0, name): + yang_find_datanode((yang_node*)y0, name); + if (y == NULL){ + clicon_err(OE_YANG, EINVAL, "api-path name: '%s', no such yang element", name); + goto fail; } switch (y->ys_keyword){ case Y_LEAF_LIST: if (0 && restval==NULL){ - clicon_err(OE_XML, 0, "malformed key, expected '='"); + clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); goto done; } if ((x = xml_new(y->ys_argument, x0, y)) == NULL) @@ -1924,21 +2014,21 @@ api_path2xml_vec(char **vec, } if (restval==NULL){ // XXX patch to allow for lists without restval to be backward compat - // clicon_err(OE_XML, 0, "malformed key, expected '='"); + // clicon_err(OE_XML, 0, "malformed key, expected '=restval'"); // goto done; } else{ if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) goto done; if (nvalvec > cvec_len(cvk)){ - clicon_err(OE_XML, errno, "List %s key length mismatch", name); - goto done; + clicon_err(OE_XML, EINVAL, "List key %s length mismatch", name); + goto fail; } } cvi = NULL; /* create list object */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); j = 0; /* Create keys */ @@ -1957,31 +2047,47 @@ api_path2xml_vec(char **vec, break; default: /* eg Y_CONTAINER, Y_LEAF */ if ((x = xml_new(name, x0, y)) == NULL) - goto done; + goto done; xml_type_set(x, CX_ELMNT); break; } - if (api_path2xml_vec(vec+1, nvec-1, - x, (yang_node*)y, - nodeclass, - xpathp, ypathp) < 0) + if (x && namespace){ + if (xmlns_set(x, NULL, namespace) < 0) + goto done; + } + if ((retval = api_path2xml_vec(vec+1, nvec-1, + x, (yang_node*)y, + nodeclass, + xpathp, ypathp)) < 1) goto done; - retval = 0; + ok: + retval = 1; /* OK */ done: + if (prefix) + free(prefix); + if (name) + free(name); if (restval) free(restval); if (valvec) free(valvec); return retval; + fail: + retval = 0; /* invalid api-path or XML */ + goto done; } /*! Create xml tree from api-path - * @param[in] api_path API-path as defined in RFC 8040 + * @param[in] api_path (Absolute) API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp + * @retval 1 OK + * @retval 0 Invalid api_path or associated XML, clicon_err called + * @retval -1 Fatal error, clicon_err called + * @note both retval 0 and -1 set clicon_err, but the later is fatal * @example * api_path: /subif-entry=foo/subid * xtop[in] @@ -1990,7 +2096,8 @@ api_path2xml_vec(char **vec, * * xbotp: * ybotp: Y_LEAF subid - * @see api_path2xml_vec + * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 + * @see api_path2xpath For api-path to xml xpath translation */ int api_path2xml(char *api_path, @@ -2003,10 +2110,13 @@ api_path2xml(char *api_path, int retval = -1; char **vec = NULL; int nvec; + cxobj *xroot; + clicon_debug(1, "%s", __FUNCTION__); if (*api_path!='/'){ - clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path); - goto ok; + clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')", + api_path); + goto fail; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; @@ -2014,19 +2124,70 @@ api_path2xml(char *api_path, if (nvec > 1 && !strlen(vec[nvec-1])) nvec--; if (nvec < 1){ - clicon_err(OE_XML, 0, "Malformed key: %s", api_path); - goto done; + clicon_err(OE_XML, EINVAL, "Malformed api-path: %s", api_path); + goto fail; } nvec--; /* NULL-terminated */ - if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, nodeclass, - xbotp, ybotp) < 0) + if ((retval = api_path2xml_vec(vec+1, nvec, + xtop, (yang_node*)yspec, nodeclass, + xbotp, ybotp)) < 1) + goto done; + xml_yang_root(*xbotp, &xroot); + if (xmlns_assign(xroot) < 0) + goto done; + // ok: + retval = 1; + done: + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Check if the module tree x is in is assigned right XML namespace, assign if not + * @param[in] x XML node + *(0. You should probably find the XML root and apply this function to that.) + * 1. Check which namespace x should have (via yang). This is correct namespace. + * 2. Check which namespace x has via its XML tree + * 3. If equal, OK + * 4. Assign the correct namespace to the XML node + * Assign default namespace to x. Create an "xmlns" + * @code + * xml_yang_root(x, &xroot); + * xmlns_assign(xroot); + * @endcode + */ +int +xmlns_assign(cxobj *x) +{ + int retval = -1; + yang_stmt *y; + char *ns_correct; /* correct uri */ + char *ns_xml; /* may be null or incorrect */ + + if ((y = xml_spec(x)) == NULL){ + clicon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x)); + goto done; + } + /* 1. Check which namespace x should have (via yang). This is correct namespace. */ + if ((ns_correct = yang_find_mynamespace(y)) == NULL){ + clicon_err(OE_YANG, ENOENT, "yang node %s does not have namespace", y->ys_argument); + goto done; + } + /* 2. Check which namespace x has via its XML tree */ + if (xml2ns(x, NULL, &ns_xml) < 0) + goto done; + /* 3. If equal, OK, 4. Else, find root of XML tree */ + if (ns_xml && strcmp(ns_xml, ns_correct)==0) + goto ok; + /* 4. Assign the correct namespace */ + if (xmlns_set(x, NULL, ns_correct) < 0) goto done; ok: retval = 0; done: - if (vec) - free(vec); return retval; } @@ -2053,6 +2214,8 @@ xml_merge1(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x0b; /* base body */ cxobj *x1c; /* mod child */ + cxobj *x0a; /* x0 xmlns attribute */ + cxobj *x1a; /* x1 xmlns attribute */ char *x1bstr; /* mod body string */ yang_stmt *yc; /* yang child */ cbuf *cbr = NULL; /* Reason buffer */ @@ -2109,7 +2272,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2117,6 +2280,17 @@ xml_merge1(cxobj *x0, goto ok; } } /* else Y_CONTAINER */ + assert(x0); + /* Copy xmlns attributes (if it does not already exist) */ + if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != NULL) + if (xml_find_type(x0, NULL, "xmlns", CX_ATTR)==NULL){ + if ((x0a = xml_dup(x1a)) == NULL) + goto done; + if (xml_addsub(x0, x0a) < 0) + goto done; + } + + ok: retval = 0; done: @@ -2147,20 +2321,31 @@ xml_merge(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; + yang_stmt *ymod; cbuf *cbr = NULL; /* Reason buffer */ /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + if ((ys_module_by_xml(yspec, x1c, &ymod)) < 0) + goto done; + if (ymod == NULL){ + if (reason && + (*reason = strdup("No namespace in XML tree found")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + goto ok; + } /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ + if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) == NULL){ if (reason){ if ((cbr = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", xml_name(x1), x1cname); + cprintf(cbr, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?)", xml_name(x1), x1cname); if ((*reason = strdup(cbuf_get(cbr))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; @@ -2169,13 +2354,14 @@ xml_merge(cxobj *x0, break; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, &x0c, xml_child_sort, yc) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; if (*reason != NULL) break; } + ok: retval = 0; /* OK */ done: if (cbr) diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 7600d249..d6a1200f 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -130,6 +130,7 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, * @param[in] ya XML parser yacc handler struct * @param[in] prefix Prefix, namespace, or NULL * @param[in] localpart Name + * @note the call to xml_child_spec() may not have xmlns attribute read yet XXX */ static int xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, @@ -138,12 +139,14 @@ xml_parse_unprefixed_name(struct xml_parse_yacc_arg *ya, int retval = -1; cxobj *x; yang_stmt *y = NULL; /* yang node */ - cxobj *xp; /* xml parent */ + cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; ya->ya_xelement = x; retval = 0; @@ -168,9 +171,11 @@ xml_parse_prefixed_name(struct xml_parse_yacc_arg *ya, cxobj *xp; /* xml parent */ xp = ya->ya_xparent; - if (xml_child_spec(name, xp, ya->ya_yspec, &y) < 0) + if ((x = xml_new(name, xp, NULL)) == NULL) goto done; - if ((x = xml_new(name, xp, y)) == NULL) + if (xml_child_spec(x, xp, ya->ya_yspec, &y) < 0) + goto done; + if (y && xml_spec_set(x, y) < 0) goto done; if (xml_prefix_set(x, prefix) < 0) goto done; @@ -313,19 +318,15 @@ xml_parse_attr(struct xml_parse_yacc_arg *ya, char *attval) { int retval = -1; - cxobj *xa; + cxobj *xa = NULL; -#ifdef ENABLE_XMLNS - if (prefix && strcmp(prefix,"xmlns")==0) - fprintf(stderr, "PrefixedAttName NCNAME:%s = %s\n", name, attval); - if (prefix==NULL && strcmp(name,"xmlns")==0) - fprintf(stderr, "DefaultAttName = %s\n", attval); -#endif /* notyet */ - if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (prefix && xml_prefix_set(xa, prefix) < 0) - goto done; + if ((xa = xml_find_type(ya->ya_xelement, prefix, name, CX_ATTR)) == NULL){ + if ((xa = xml_new(name, ya->ya_xelement, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (prefix && xml_prefix_set(xa, prefix) < 0) + goto done; + } if (xml_value_set(xa, attval) < 0) goto done; retval = 0; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index befb5bcb..265d82a5 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -61,6 +61,8 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_options.h" +#include "clixon_xml_map.h" #include "clixon_xml_sort.h" /* @@ -68,7 +70,9 @@ */ /* Sort and binary search of XML children - * Experimental + * XXX kludge since low-level functions xml_merge/xml_diff calls + * match_base_child without handle + * @see clicon_xml_sort */ int xml_child_sort = 1; @@ -83,7 +87,7 @@ int xml_child_sort = 1; * xmlns and xmlns:ns are used. */ int -xml_child_spec(char *name, +xml_child_spec(cxobj *x, cxobj *xp, yang_spec *yspec, yang_stmt **yresult) @@ -93,8 +97,9 @@ xml_child_spec(char *name, yang_stmt *yparent; /* parent yang */ yang_stmt *ymod = NULL; yang_stmt *yi; - int i; + char *name; + name = xml_name(x); if (xp && (yparent = xml_spec(xp)) != NULL){ if (yparent->ys_keyword == Y_RPC){ if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL) @@ -108,12 +113,9 @@ xml_child_spec(char *name, goto done; if (ymod != NULL) y = yang_find_schemanode((yang_node*)ymod, name); - if (y == NULL && _CLICON_XML_NS_ITERATE){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) - break; - } + if (y == NULL && !_CLICON_XML_NS_STRICT){ + if (xml_yang_find_non_strict(x, yspec, &y) < 0) /* schemanode */ + goto done; } } else @@ -265,6 +267,7 @@ xml_cmp1(cxobj *x, * Assume populated by yang spec. * @param[in] x0 XML node * @param[in] arg Dummy so it can be called by xml_apply() + * @see xml_order XXX: how do they relate? */ int xml_sort(cxobj *x, @@ -560,9 +563,10 @@ xml_sort_verify(cxobj *x0, } /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child + * param[in] x0 Base tree node + * param[in] x1c Modification tree child + * param[in] yc Yang spec of tree child + * param[in] xml_sort Value of CLICON_XML_SORT option * param[out] x0cp Matching base tree child (if any) * @note XXX: room for optimization? on 1K calls we have 1M body calls and 500K xml_child_each/cvec_each calls. @@ -575,6 +579,7 @@ int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, + int xml_sort, yang_stmt *yc) { int retval = -1; @@ -632,7 +637,7 @@ match_base_child(cxobj *x0, break; } /* Get match. Sorting mode(optimized) or not?*/ - if (xml_child_sort==0) + if (xml_sort==0) *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); else{ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ @@ -640,12 +645,6 @@ match_base_child(cxobj *x0, *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); } else{ -#if 1 /* This is just a warning, but a catcher for when xml tree is not - populated with yang spec. If you see this, a previous invacation of, - for example xml_spec_populate() may be missing - */ - clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__); -#endif *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } } diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 68c9ce9f..2298235a 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -469,94 +469,6 @@ yang_match(yang_node *yn, } return match; } -#ifdef NOTYET -/*! Prototype more generic than yang_find_datanode and yang_find_schemanode - */ -yang_stmt * -yang_find_class(yang_node *yn, - char *argument, - yang_class class) -{ - yang_stmt *ys = NULL; - yang_stmt *yc = NULL; - yang_stmt *ysmatch = NULL; - int i, j; - int ok; - - for (i=0; iyn_len; i++){ - ys = yn->yn_stmt[i]; - switch(class){ - case YC_NONE: - ok = 1; - break; - case YC_DATANODE: - ok = yang_datanode(ys); - break; - case YC_DATADEFINITION: - ok = yang_datadefinition(ys); - break; - case YC_SCHEMANODE: - ok = yang_schemanode(ys); - break; - } - if (!ok) - continue; - switch(class){ - case YC_NONE: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_DATANODE: - case YC_DATADEFINITION: - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - break; - case YC_SCHEMANODE: - if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ - for (j=0; jys_len; j++){ - yc = ys->ys_stmt[j]; - if (yc->ys_keyword == Y_CASE) /* Look for its children */ - ysmatch = yang_find_class((yang_node*)yc, argument, class); - else{ - if (yang_schemanode(yc)){ - if (argument == NULL) - ysmatch = yc; - else - if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) - ysmatch = yc; - } - } - if (ysmatch) - goto match; - } - } /* Y_CHOICE */ - else{ - if (argument == NULL) - ysmatch = ys; - else - if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) - ysmatch = ys; - if (ysmatch) - goto match; - - } - break; - } /* switch */ - } /* for */ - match: - return ysmatch; -} -#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -658,69 +570,6 @@ yang_find_schemanode(yang_node *yn, return ysmatch; } -/*! Find first matching data node in all modules in a yang spec (prefixes) - * - * @param[in] ysp Yang specification - * @param[in] nodeid Name of node. If NULL match first - * @param[in] class See yang_class for class of yang nodes - * A yang specification has modules as children which in turn can have - * syntax-nodes as children. This function goes through all the modules to - * look for nodes. Note that if a child to a module is a choice, - * the search is made recursively made to the choice's children. - * @note works for import prefix, but not work for generic XML parsing where - * xmlns and xmlns:ns are used. - * @see yang_find_top_ns - */ -yang_stmt * -yang_find_topnode(yang_spec *ysp, - char *nodeid, - yang_class class) -{ - yang_stmt *ymod = NULL; /* module */ - yang_stmt *yres = NULL; /* result */ - char *prefix = NULL; - char *id = NULL; - int i; - - if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - goto done; - if (prefix){ - if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL || - (ymod = yang_find((yang_node*)ysp, Y_SUBMODULE, prefix)) != NULL){ - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - goto done; - } - } - else /* No prefix given - loop through and find first */ - for (i=0; iyp_len; i++){ - ymod = ysp->yp_stmt[i]; - switch (class){ - case YC_NONE: - if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL) - goto ok; - break; - case YC_DATANODE: - if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_SCHEMANODE: - if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL) - goto ok; - break; - case YC_DATADEFINITION: - break; /* nyi */ - } - } - ok: - done: - if (prefix) - free(prefix); - if (id) - free(id); - return yres; -} - /*! Given a yang statement, find the prefix associated to this module * @param[in] ys Yang statement in module tree (or module itself) * @retval NULL Not found @@ -775,7 +624,6 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } - /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -972,60 +820,15 @@ yarg_prefix(yang_stmt *ys) return prefix; } -/*! Split yang node identifier into prefix and identifer. - * @param[in] node-id - * @param[out] prefix Malloced string. May be NULL. - * @param[out] id Malloced identifier. - * @retval 0 OK - * @retval -1 Error - * @code - * char *prefix = NULL; - * char *id = NULL; - * if (yang_nodeid_split(nodeid, &prefix, &id) < 0) - * goto done; - * if (prefix) - * free(prefix); - * if (id) - * free(id); - * @note caller need to free id and prefix after use - */ -int -yang_nodeid_split(char *nodeid, - char **prefix, - char **id) -{ - int retval = -1; - char *str; - - if ((str = strchr(nodeid, ':')) == NULL){ - if ((*id = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - else{ - if ((*prefix = strdup(nodeid)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - (*prefix)[str-nodeid] = '\0'; - str++; - if ((*id = strdup(str)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); - goto done; - } - } - retval = 0; - done: - return retval; -} - -/*! Given a yang statement and a prefix, return yang module to that prefix +/*! Given a yang statement and a prefix, return yang module to that relative prefix * Note, not the other module but the proxy import statement only * @param[in] ys A yang statement * @param[in] prefix prefix * @retval ymod Yang module statement if found * @retval NULL not found + * @node Prefixes are relative to the module they are defined + * @see yang_find_module_by_name + * @see yang_find_module_by_namespace */ yang_stmt * yang_find_module_by_prefix(yang_stmt *ys, @@ -1081,12 +884,14 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } -/*! Given a yang statement and a namespace, return yang module +/*! Given a yang spec and a namespace, return yang module * * @param[in] yspec A yang specification * @param[in] namespace namespace * @retval ymod Yang module statement if found * @retval NULL not found + * @see yang_find_module_by_name + * @see yang_find_module_by_prefix module-specific prefix */ yang_stmt * yang_find_module_by_namespace(yang_spec *yspec, @@ -1104,6 +909,28 @@ yang_find_module_by_namespace(yang_spec *yspec, return ymod; } +/*! Given a yang spec and a module name, return yang module + * + * @param[in] yspec A yang specification + * @param[in] name Name of module + * @retval ymod Yang module statement if found + * @retval NULL not found + * @see yang_find_module_by_namespace + * @see yang_find_module_by_prefix module-specific prefix + */ +yang_stmt * +yang_find_module_by_name(yang_spec *yspec, + char *name) +{ + yang_stmt *ymod = NULL; + + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) + if ((ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE) && + strcmp(ymod->ys_argument, name)==0) + return ymod; + return NULL; +} + /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1541,7 +1368,7 @@ ys_populate_feature(clicon_handle h, if (strcmp(xml_name(xc), "CLICON_FEATURE") != 0) continue; /* get m and f from configuration feature rules */ - if (yang_nodeid_split(xml_body(xc), &m, &f) < 0) + if (nodeid_split(xml_body(xc), &m, &f) < 0) goto done; if (m && f && (strcmp(m,"*")==0 || @@ -1558,7 +1385,8 @@ ys_populate_feature(clicon_handle h, } cv_name_set(cv, feature); cv_bool_set(cv, found); - clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found); + if (found) + clicon_debug(1, "%s %s:%s", __FUNCTION__, module, feature); ys->ys_cv = cv; ok: retval = 0; @@ -1936,7 +1764,7 @@ yang_parse_str(char *str, } /*! Parse yang spec from an open file descriptor - * @param[in] fd File descriptor containing the YANG file as ASCII characters + * @param[in] fd File descriptor containing the YANG file as ASCII characters * @param[in] name For debug, eg filename * @param[in] ysp Yang specification. Should have been created by caller using yspec_new * @retval ymod Top-level yang (sub)module @@ -2083,7 +1911,7 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; - // clicon_debug(1, "%s %s", __FUNCTION__, filename); + clicon_debug(1, "%s %s", __FUNCTION__, filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -2258,7 +2086,7 @@ yang_features(clicon_handle h, while (iys_len){ /* Note, children may be removed */ ys = yt->ys_stmt[i]; if (ys->ys_keyword == Y_IF_FEATURE){ - if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0) + if (nodeid_split(ys->ys_argument, &prefix, &feature) < 0) goto done; /* Specifically need to handle? strcmp(prefix, myprefix)) */ if (prefix == NULL) @@ -2680,7 +2508,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, char *id; char *prefix = NULL; yang_stmt *yprefix; - yang_stmt *ys; /* check absolute schema_nodeid */ if (schema_nodeid[0] != '/'){ @@ -2719,21 +2546,6 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } } - if (ymod == NULL){ /* Try find id from topnode without prefix XXX remove?*/ - if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - if ((ymod = ys_module(ys)) == NULL){ - clicon_err(OE_YANG, 0, "Module with id:%s:%s not found2", prefix,id); - goto done; - } - if ((yprefix = yang_find((yang_node*)ymod, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) != 0){ - clicon_err(OE_YANG, 0, "Module with id:\"%s:%s\" not found", prefix,id); - goto done; - } - } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: /* yres may not be set */ diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 6e08c96e..c0b1e408 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -81,7 +81,7 @@ int yang_modules_init(clicon_handle h) { - int retval = -1; + int retval = -1; yang_spec *yspec; yspec = clicon_dbspec_yang(h); @@ -203,7 +203,7 @@ yang_modules_state_get(clicon_handle h, cprintf(cb,"%s", ys->ys_argument); else cprintf(cb,""); - cprintf(cb, "implement"); + /* This follows order in rfc 7895: feature, conformance-type, submodules */ yc = NULL; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { switch(yc->ys_keyword){ @@ -211,6 +211,14 @@ yang_modules_state_get(clicon_handle h, if (yc->ys_cv && cv_bool_get(yc->ys_cv)) cprintf(cb,"%s", yc->ys_argument); break; + default: + break; + } + } + cprintf(cb, "implement"); + yc = NULL; + while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { + switch(yc->ys_keyword){ case Y_SUBMODULE: cprintf(cb,""); cprintf(cb,"%s", yc->ys_argument); diff --git a/test/all.sh b/test/all.sh index 79a0309f..1e036ab1 100755 --- a/test/all.sh +++ b/test/all.sh @@ -38,6 +38,7 @@ if [ $err -eq 0 ]; then else echo -e "\e[31mError" echo -ne "\e[0m" + exit -1 fi diff --git a/test/test_cli.sh b/test/test_cli.sh index aa1379d9..b4005433 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -59,7 +59,7 @@ expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" new "cli show configuration delete top" expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" -new "cli configure" +new "cli configure set interfaces" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" new "cli show configuration" diff --git a/test/test_compat.sh b/test/test_compat.sh new file mode 100755 index 00000000..77875410 --- /dev/null +++ b/test/test_compat.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Test of backward compatibility +# 1) Load <3.9 startup/running/extra files without namespaces - ensure it returns namespaces +# + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh +cfg=$dir/conf_startup.xml + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + example + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + 0 + init + true + + +EOF + + +run(){ + mode=$1 + expect=$2 + + dbdir=$dir/db + cat < $dbdir + + + + run + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/running_db + + cat < $dbdir + + + + startup + ex:eth + + + +EOF + sudo mv $dbdir /usr/local/var/$APPNAME/startup_db + + cat < $dir/config + + + + extra + ex:eth + + + +EOF + + new "test params: -f $cfg -s $mode -c $dir/config" + + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + + new "start backend -f $cfg -s $mode -c $dir/config" + sudo $clixon_backend -f $cfg -s $mode -c $dir/config + if [ $? -ne 0 ]; then + err + fi + fi + + new "Check $mode" + expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^$expect]]>]]>$" + + 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 + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err "kill backend" + fi + +} # run + +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' + +rm -rf $dir diff --git a/test/test_datastore.sh b/test/test_datastore.sh index bcc7b27b..9744413d 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -50,9 +50,6 @@ EOF xml='12first-entry13second-entry23third-entryabcastring' -# Without xmlns -xmlxxx='12first-entry13second-entry23third-entryabcastring' - run(){ name=$1 mydir=$dir/$name @@ -72,7 +69,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all remove" expectfn "$datastore $conf put remove " 0 "" @@ -87,7 +84,7 @@ run(){ # expectfn "$datastore $conf put merge $xml" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put all delete" expectfn "$datastore $conf put remove " 0 "" @@ -100,7 +97,7 @@ run(){ expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$xmlxxx$" + expectfn "$datastore $conf get /" 0 "^$xml$" new "datastore $name put top create" expectfn "$datastore $conf put create " 0 "" # error diff --git a/test/test_feature.sh b/test/test_feature.sh index dfb99172..20adfa3f 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -90,13 +90,13 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf enabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' "^]]>]]>$" new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo]]>]]>' '^applicationunknown-elementyerrorUnassigned yang spec]]>]]>$' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -105,6 +105,13 @@ ret=$($clixon_netconf -qf $cfg -y $fyang<]]>]]> EOF ) +new "netconf modules-state header" +expect='^' +match=`echo "$ret" | grep -GZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + new "netconf module A" expect="exampleurn:example:clixonAimplement" match=`echo "$ret" | grep -GZo "$expect"` @@ -135,8 +142,9 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +# Note order of features in ietf-netconf yang is alphabetically: candidate, startup, validate, xpath new "netconf module ietf-netconf" -expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" +expect="ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" match=`echo "$ret" | grep -GZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/test/test_identity.sh b/test/test_identity.sh index 14d6b996..eeaa6198 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -122,44 +122,44 @@ if [ $BE -ne 0 ]; then fi new "Set crypto to aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' new "netconf validate " expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:aes]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:aes]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to mc:foo" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "mc:foo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'mc:foo]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to des:des3 using xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "des:des3]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'des:des3]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" # XXX this is not supported #new "Set crypto to x:des3 using xmlns" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "x:des3]]>]]>" "^]]>]]>$" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'x:des3]]>]]>' "^]]>]]>$" -#new "netconf validate" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +new "netconf validate" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "Set crypto to foo:bar" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo:bar]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'foo:bar]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorIdentityref validation failed, foo:bar not derived from crypto-alg]]>]]>$" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 6e66c7f3..63c6a270 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,22 +88,19 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " -eth0ex:eth
192.0.2.1
192.0.2.2
-loex:lo
127.0.0.1
-
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref base commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "leafref get config" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' new "leafref add wrong ref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth3
10.0.4.6
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth3
10.0.4.6
]]>]]>' '^]]>]]>$' new "leafref validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth3errorLeafref validation failed: No such leaf]]>]]>$' @@ -112,10 +109,10 @@ new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "leafref add correct absref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' new "leafref add correct relref" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^]]>]]>$' # XXX add address @@ -123,7 +120,7 @@ new "leafref validate (ok)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" new "leafref delete leaf" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth0]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0]]>]]>' '^' new "leafref validate (should fail)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementeth0errorLeafref validation failed: No such leaf]]>]]>$' diff --git a/test/test_list.sh b/test/test_list.sh index 37fb8c6c..1b9bdc2e 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -81,19 +81,19 @@ if [ $BE -ne 0 ]; then fi new "minmax: minimal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace00]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace00]]>]]>' "^]]>]]>$" new "minmax: minimal validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: maximal" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace01unbounded0101unbounded01]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace01unbounded0101unbounded01]]>]]>' '^]]>]]>$' new "minmax: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: empty" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace]]>]]>' '^]]>]]>$' # NYI if false; then @@ -101,25 +101,25 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: no leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0120]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0120]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "minmax: Too large leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "replace0012]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'replace0012]]>]]>' '^]]>]]>$' new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_nacm.sh b/test/test_nacm.sh index 389ae010..aac4be87 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -49,7 +49,7 @@ EOF # The groups are slightly modified from RFC8341 A.1 # The rule-list is from A.2 RULES=$(cat < + false deny deny @@ -99,7 +99,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -131,7 +131,7 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "auth set authentication config" @@ -147,33 +147,33 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 9bdee51d..b706b96f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -163,11 +163,11 @@ new "restconf DELETE whole datastore" expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new "Set x to 0" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 0}' http://localhost/restconf/data/example:x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' @@ -176,28 +176,28 @@ new2 "auth get (wrong passwd: access denied)" expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "limited get nacm" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" "" new2 "limited edit nacm" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 963218e5..b4e5bc2f 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -122,7 +122,7 @@ RULES=$(cat < - 0 + 0 EOF ) @@ -158,10 +158,10 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0} ' # Rule 1: deny-kill-session @@ -186,7 +186,7 @@ new "deny-delete-config: limited fail (restconf) ok" expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '' new2 "admin get nacm (should be null)" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null ' new "deny-delete-config: admin ok (restconf)" @@ -200,14 +200,14 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" "" # Rule 3: permit-edit-config new "permit-edit-config: limited ok restconf" -expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '' new2 "permit-edit-config: guest fail restconf" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 7b060875..ced3047d 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -114,14 +114,13 @@ new "netconf get-config single quotes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'noneeth/0/0]]>]]>' "^]]>]]>$" new "Check nothing added" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" - +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" # Too many quotes, (single inside double inside single) need to fool bash cat < $tmp # new @@ -129,22 +128,22 @@ cat < $tmp # new EOF new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0ex:ethtrue]]>]]>$' new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^]]>]]>$' new "Check deleted eth/0/0 (non-presence container)" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:ethnone ]]>]]>' '^' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0eth1true
9.2.3.424
]]>]]>' "^]]>]]>$" # Too many quotes cat < $tmp # new @@ -152,7 +151,7 @@ cat < $tmp # new EOF new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth1true]]>]]>$' # Too many quotes cat < $tmp # new @@ -160,7 +159,7 @@ cat < $tmp # new EOF new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$(cat $tmp)" '^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$' new "netconf validate missing type" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -172,13 +171,13 @@ new "netconf get empty config2" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth1ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth1ex:eth]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -187,32 +186,32 @@ new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit config merge" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth2ex:ethmerge]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth2ex:ethmerge]]>]]>' "^]]>]]>$" new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth&t<>]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth&t<>]]>]]>' '^]]>]]>$' new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth&t<>trueeth1ex:ethtrueeth2ex:ethtrue]]>]]>$' new "cli show configuration eth& - encoding tests" expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" 0 "interfaces interface eth& type t<> interfaces interface eth& enabled true" new "netconf edit CDATA" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "eth/0/0ex:eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth/0/0ex:eth]]>]]>' "^]]>]]>$" #new "netconf get CDATA" -#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "eth/0/0true]]>]]>" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'eth/0/0true]]>]]>' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' "^protocolinvalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^42]]>]]>$' new "netconf lock/unlock" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" @@ -234,7 +233,7 @@ new "copy startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^eth1ex:ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^eth1ex:ethtrue]]>]]>$' new "netconf delete startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -243,16 +242,16 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' '^ipv4' -new "netconf rpc without namespace (iterate kludge should work)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +#new "netconf rpc without namespace (iterate kludge should work)" +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf client-side rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'example]]>]]>' '^ok]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_order.sh b/test/test_order.sh index cb9eb780..4db73e9c 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -16,7 +16,6 @@ fyang=$dir/order.yang dbdir=$dir/order -new "Set up $dbdir" rm -rf $dbdir if [ ! -d $dbdir ]; then mkdir $dbdir @@ -84,30 +83,32 @@ module example{ } EOF +rm -f $dbdir/candidate_db +# alt cat < $dbdir/running_db - d - d - dbar - dbar - b - b - hej - c - c - abar - abar - hopp - a - a - cbar - cbar - bbar - bbar + d + d + dbar + dbar + b + b + hej + c + c + abar + abar + hopp + a + a + cbar + cbar + bbar + bbar EOF -new "test params: -f $cfg -y $fyang" +new "test params: -s running -f $cfg -y $fyang" if [ $BE -ne 0 ]; then new "kill old backend" @@ -123,20 +124,20 @@ if [ $BE -ne 0 ]; then fi # Check as file -new "verify running from start, should be: l,c,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if XML_SORT set to false" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$" +new "verify running from start, should be: c,l,y0,y1,y2,y3; y1 and y3 sorted. Note this fails if CLICON_XML_SORT set to false" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhoppdbcaabcddbarabarcbarbbarabarbbarcbardbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^abar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^abar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "get each ordered-by user leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^bbar]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^bbar]]>]]>$' new "delete candidate" expecteof "$clixon_netconf -qf $cfg" 0 'none]]>]]>' "^]]>]]>$" @@ -144,33 +145,33 @@ expecteof "$clixon_netconf -qf $cfg" 0 'cb]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cb]]>]]>' "^]]>]]>$" new "add one entry to leaf-list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "add one entry to leaf-list user order after commit" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '0]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "verify leaf-list user order in running (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cba0]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cba0]]>]]>$' # LISTS new "add two entries to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "cbarbfoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'cbarbfoo]]>]]>' "^]]>]]>$" new "add one entry to list user order" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afie]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afie]]>]]>' "^]]>]]>$" new "verify list user order (as entered)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^cbarbfooafie]]>]]>$' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_perf.sh b/test/test_perf.sh index 4d9d6dfc..eb1fe687 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -83,7 +83,7 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & sleep $RCWAIT new "generate 'large' config with $number list entries" -echo -n "" > $fconfig +echo -n "" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i$i" >> $fconfig done @@ -111,7 +111,7 @@ new "netconf commit large config again" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'xy]]>]]>' "^]]>]]>$" new "netconf get $req small config" time -p for (( i=0; i<$req; i++ )); do @@ -128,7 +128,7 @@ done new "netconf add $req small config" time -p for (( i=0; i<$req; i++ )); do rnd=$(( ( RANDOM % $number ) )) - echo "$rnd$rnd]]>]]>" + echo "$rnd$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add $req restconf small config" @@ -138,10 +138,10 @@ time -p for (( i=0; i<$req; i++ )); do done new "netconf get large config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^0011" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^0011' new "generate large leaf-list config" -echo -n "replace" > $fconfig +echo -n "replace" > $fconfig for (( i=0; i<$number; i++ )); do echo -n "$i" >> $fconfig done @@ -158,17 +158,17 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "$rnd]]>]]>" + echo "$rnd]]>]]>" done | $clixon_netconf -qf $cfg -y $fyang > /dev/null new "netconf add small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 'x]]>]]>' "^]]>]]>$" new "netconf commit small leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^01' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 1874a0b3..e7451d0a 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -76,7 +76,7 @@ module example{ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there -state='{"state": {"op": "42"}}' +state='{"example:state": {"op": "42"}}' new "test params: -f $cfg -y $fyang" if [ $BE -ne 0 ]; then @@ -102,9 +102,6 @@ sleep $RCWAIT new "restconf tests" -new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' - new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " @@ -119,13 +116,14 @@ new2 "restconf get restconf resource. RFC 8040 3.3 (xml)" expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 ' +# Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"example:empty": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null}{"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -142,8 +140,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895" -expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} +new2 "restconf schema resource, RFC 8040 sec 3.7 according to RFC 7895 (explicit resource)" +expecteq "$(curl -s -H 'Accept: application/yang-data+json' -G http://localhost/restconf/data/ietf-yang-library:modules-state/module=ietf-routing,2014-10-26/)" '{"ietf-yang-library:module": [{"name": "ietf-routing","revision": "2014-10-26","namespace": "urn:ietf:params:xml:ns:yang:ietf-routing","conformance-type": "implement"}]} ' new "restconf options. RFC 8040 4.1" @@ -154,132 +152,133 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" +expecteq "$(curl -s -X POST -d {\"example:input\":null} http://localhost/restconf/operations/example:empty)" "" new2 "restconf empty rpc with extra args (should fail)" -expecteq "$(curl -s -X POST -d {\"input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d {\"example:input\":{\"extra\":null}} http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf get empty config + state json" -expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json + module" -expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"state": {"op": "42"}} +expecteq "$(curl -sSG http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf get empty config + state json with wrong module name" -expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No yang node found: badmodule:state"}}} ' +expecteq "$(curl -sSG http://localhost/restconf/data/badmodule:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "No such yang module: badmodule"}}} ' new "restconf get empty config + state xml" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get data/ json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf get state operation eth0 type json" -expecteq "$(curl -s -G http://localhost/restconf/data/state/op=42)" '{"op": "42"} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state/op=42)" '{"example:op": "42"} ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/state/op=42) -expect="42" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:state/op=42) +expect='42' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf GET datastore" -expecteq "$(curl -s -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -X GET http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' # Exact match new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expectfn 'curl -s -X POST -d {"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}} http://localhost/restconf/data' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work #expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"ex:eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" 0 '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]},"state": {"op": "42"}} - ' +expectfn "curl -s -G http://localhost/restconf/data" 0 '"ietf-interfaces:interfaces": {"interface": \[{"name": "eth/0/0","type": "ex:eth","enabled": true}\]}' new "restconf delete interfaces" -expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" +expecteq $(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data/state" 0 "$state" +expectfn "curl -sG http://localhost/restconf/data/example:state" 0 "$state" +# XXX: gives +# new "restconf Add interfaces subtree eth/0/0 using POST" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/interfaces' 0 "" +expectfn 'curl -s -X POST -d {"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}} http://localhost/restconf/data/ietf-interfaces:interfaces' 0 "" # XXX cant get this to work #expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new2 "restconf Check eth/0/0 added config" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf Check eth/0/0 added state" -expecteq "$(curl -s -G http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -s -G http://localhost/restconf/data/example:state)" '{"example:state": {"op": "42"}} ' new2 "restconf Re-post eth/0/0 which should generate error" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" -expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X POST -d '{"ietf-interfaces:description":"The-first-interface"}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' +expectfn 'curl -s -X POST http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0' 0 '"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "ex:eth","enabled": true}]}} ' new "restconf delete eth/0/0" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' 0 $state new2 "restconf Re-Delete eth/0/0 using none should generate error" -expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-missing","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" +expecteq "$(curl -s -X PUT -d '{"ietf-interfaces:interface":{"name":"eth/0/0","type":"ex:eth","enabled":true}}' http://localhost/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" -expecteq "$(curl -s -G http://localhost/restconf/data/interfaces)" '{"interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} +expecteq "$(curl -s -G http://localhost/restconf/data/ietf-interfaces:interfaces)" '{"ietf-interfaces:interfaces": {"interface": [{"name": "eth/0/0","type": "ex:eth","enabled": true}]}} ' new2 "restconf rpc using POST json" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv6"}}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-routing:output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"},"source-protocol": "static"}}} ' # Cant get this to work due to quoting #new2 "restconf rpc using POST wrong JSON" -#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' +#expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}} ' new2 "restconf rpc using POST json without mandatory element" -expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"ietf-routing:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "wrongelement"},"error-severity": "error"}}} ' new2 "restconf rpc non-existing rpc without namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "kalle"},"error-severity": "error","error-message": "RPC not defined"}}} ' @@ -291,29 +290,29 @@ new2 "restconf rpc missing name" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "Operation name expected"}}} ' new2 "restconf rpc missing input" -expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "routing-instance-name"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) -expect="ipv42.3.4.5static" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) +expect='ipv42.3.4.5static' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi new2 "restconf rpc using wrong prefix" -expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"wrong:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/wrong:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "restconf local client rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) -expect="ok" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"example:input":{"request":"example"}}' http://localhost/restconf/operations/example:client-rpc) +expect='ok' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi # XXX cant get -H to work -#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"ietf-routing:input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/ietf-routing:fib-route' 'ipv42.3.4.5' # Cant get shell macros to work, inline matching from lib.sh diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 1cf7188f..68677250 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -72,73 +72,82 @@ sleep $RCWAIT new "restconf tests" new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore intial" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' -new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" 0 '{"interface": \[{"name": "local0","type": "regular"}\]}' +new "restconf GET interface subtree" +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0" 0 '{"example:interface": \[{"name": "local0","type": "regular"}\]}' + +new "restconf GET interface subtree xml" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data/example:cont1/interface=local0) +expect='local0regular' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" 0 '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1/interface=local0/type" 0 '{"example:type": "regular"}' new "restconf POST interface without mandatory type" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/example:cont1' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "type"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X POST -d {"example:interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1' 0 "" +# XXX should it be example:interface? new2 "restconf POST again" -expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/example:cont1)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new2 "restconf POST from top" -expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +expecteq "$(curl -s -X POST -d '{"example:cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "data-exists","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' 0 "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/example:cont1' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X POST -d {"example:cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' 0 "" new "restconf GET initial tree" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf DELETE whole datastore" expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 "" new "restconf GET null datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 'null' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 'null' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}' new "restconf PUT replace datastore" -expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont2":{"name":"foo"}}} http://localhost/restconf/data' 0 "" new "restconf GET replaced datastore" -expectfn "curl -s -X GET http://localhost/restconf/data/cont2" 0 '{"cont2": {"name": "foo"}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont2" 0 '{"example:cont2": {"name": "foo"}}' new "restconf PUT initial datastore again" -expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" +expectfn 'curl -s -X PUT -d {"data":{"example:cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' 0 "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/example:cont1/interface=local0' 0 "" new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data/cont1" 0 '{"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' +expectfn "curl -s -X GET http://localhost/restconf/data/example:cont1" 0 '{"example:cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/example:cont1/interface=TEST' 0 '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "api-path keys do not match data keys"}}}' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f28a4df9..f033dfb0 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -53,50 +53,70 @@ sleep $RCWAIT new "rpc tests" # 1.First some positive tests vary media types -# +# extra complex because pattern matching on return haders +new "restconf empty rpc" +ret=$(curl -is -X POST http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "netconf empty rpc" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^]]>]]>$' + new2 "restconf example rpc json/json default - no http media headers" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/json change y default" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "99"}} +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","y":"99"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "99"}} ' new2 "restconf example rpc json/json" # XXX example:input example:output -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Content-Type: application/yang-data+json' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc xml/json" -expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"output": {"x": "0","y": "42"}} +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Content-Type: application/yang-data+json' -d '0' http://localhost/restconf/operations/example:example)" '{"example:output": {"x": "0","y": "42"}} ' new2 "restconf example rpc json/xml" -# 042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+json' -H 'Accept: application/yang-data+xml' -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:example)" '042 ' new2 "restconf example rpc xml/xml" -# 0' http://localhost/restconf/operations/example:example)" '042 +expecteq "$(curl -s -X POST -H 'Content-Type: application/yang-data+xml' -H 'Accept: application/yang-data+xml' -d '0' http://localhost/restconf/operations/example:example)" '042 ' new "netconf example rpc" -expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 '0]]>]]>' '^042]]>]]>$' # 2. Then error cases # +new "restconf empy rpc with null input" +ret=$(curl -is -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:empty) +expect="204 No Content" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new2 "restconf empy rpc with input x" +expecteq "$(curl -s -X POST -d '{"example:input":{"x":0}}' http://localhost/restconf/operations/example:empty)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "x"},"error-severity": "error"}}} ' + new2 "restconf omit mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "x"},"error-severity": "error","error-message": "Mandatory variable"}}} ' new2 "restconf add extra" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/example:example)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "unknown-element","error-info": {"bad-element": "extra"},"error-severity": "error"}}} ' new2 "restconf wrong method" -expecteq "$(curl -s -X POST -d '{"input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' new2 "restconf edit-config missing mandatory" -expecteq "$(curl -s -X POST -d '{"input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' +expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' diff --git a/test/test_startup.sh b/test/test_startup.sh index d9ec5b07..ab0acb0f 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -41,7 +41,7 @@ run(){ dbdir=$dir/db cat < $dbdir - + run ex:eth @@ -53,7 +53,7 @@ EOF cat < $dbdir - + startup ex:eth @@ -65,7 +65,7 @@ EOF cat < $dir/config - + extra ex:eth @@ -104,8 +104,8 @@ EOF } run init '' -run none 'runex:ethtrue' -run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' -run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' +run none 'runex:ethtrue' +run running 'extraex:ethtrueloex:loopbacktruerunex:ethtrue' +run startup 'extraex:ethtrueloex:loopbacktruestartupex:ethtrue' rm -rf $dir diff --git a/test/test_stream.sh b/test/test_stream.sh index 87932744..2673b832 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -65,7 +65,7 @@ EOF # using reportingEntity (rfc5277) not reporting-entity (rfc8040) cat < $fyang module example { - namespace "http://example.com/event/1.0"; + namespace "urn:example:clixon"; prefix ex; organization "Example, Inc."; contact "support at example.com"; @@ -130,35 +130,35 @@ sleep $RCWAIT new "1. Netconf RFC5277 stream testing" # 1.1 Stream discovery new "netconf event stream discovery RFC5277 Sec 3.2.5" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtrue]]>]]>' new "netconf event stream discovery RFC8040 Sec 6.2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'EXAMPLEExample event streamtruexmlhttps://localhost/streams/EXAMPLE]]>]]>' # # 1.2 Netconf stream subscription new "netconf EXAMPLE subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf subscription with empty startTime" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with simple filter" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLE]]>]]>' '^]]>]]>20' $NCWAIT new "netconf EXAMPLE subscription with filter classifier" -expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE]]>]]>" '^]]>]]>20' $NCWAIT new "netconf NONEXIST subscription" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'NONEXIST]]>]]>' '^applicationinvalid-valueerrorNo such stream]]>]]>$' $NCWAIT new "netconf EXAMPLE subscription with wrong date" -expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 +expectwait "$clixon_netconf -qf $cfg -y $fyang" 'EXAMPLEkallekaka]]>]]>' '^applicationbad-elementstartTimeerrorInvalid time: kallekaka]]>]]>$' 0 #new "netconf EXAMPLE subscription with replay" #NOW=$(date +"%Y-%m-%dT%H:%M:%S") #sleep 10 -#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 +#expectwait "$clixon_netconf -qf $cfg -y $fyang" "EXAMPLE$NOW]]>]]>" '^]]>]]>20' 10 sleep 2 # @@ -166,11 +166,11 @@ sleep 2 new "2. Restconf RFC8040 stream testing" # 2.1 Stream discovery new "restconf event stream discovery RFC8040 Sec 6.2" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams" 0 '{"ietf-restconf-monitoring:streams": {"stream": \[{"name": "EXAMPLE","description": "Example event stream","replay-support": true,"access": \[{"encoding": "xml","location": "https://localhost/streams/EXAMPLE"}\]}\]}' sleep 2 new "restconf subscribe RFC8040 Sec 6.3, get location" -expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"location": "https://localhost/streams/EXAMPLE"}' +expectfn "curl -s -X GET http://localhost/restconf/data/ietf-restconf-monitoring:restconf-state/streams/stream=EXAMPLE/access=xml/location" 0 '{"ietf-restconf-monitoring:location": "https://localhost/streams/EXAMPLE"}' sleep 2 # Restconf stream subscription RFC8040 Sec 6.3 @@ -246,13 +246,12 @@ if [ $nr -lt 9 -o $nr -gt 14 ]; then err 10 "$nr" fi - # Try parallell # start background job curl -s -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" "http://localhost/streams/EXAMPLE" > /dev/null & PID=$! -new "Start subscription in parallell" +new "Start subscriptions in parallell" ret=$($UTIL -u http://localhost/streams/EXAMPLE -t 8) expect="data: ${DATE}T[0-9:.]*faultEthernet0major" diff --git a/test/test_type.sh b/test/test_type.sh index 1261d2ba..20f6f7aa 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -214,7 +214,7 @@ new "cli set transitive string error" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle 9xx" 255 "^$" new "netconf set transitive string error" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "9xx]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '9xx]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" 'applicationbad-elementtalleerrorregexp match fail: "9xx" does not match \[a-z\]\[0-9\]\*]]>]]>$' @@ -241,7 +241,7 @@ new "cli set transitive union error int" expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 55" 255 "" new "netconf set transitive union error int" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "55]]>]]>" "^]]>]]>" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '55]]>]]>' "^]]>]]>" new "netconf validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationbad-elementulleerror'55' does not match enumeration]]>]]>$" @@ -276,7 +276,7 @@ new "netconf validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a.b& c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a.b& c.d]]>]]>' "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^" @@ -298,7 +298,7 @@ expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits create" 0 "^$" #expectfn "$clixon_cli -1f $cfg -l o -y $fyang set mbits \"create read\"" 0 "^$" new "netconf bits two values" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "create read]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'create read]]>]]>' "^]]>]]>$" new "cli bits validate" expectfn "$clixon_cli -1f $cfg -l o -y $fyang validate" 0 "^$" diff --git a/test/test_when_must.sh b/test/test_when_must.sh index 9f4c8d4b..82fc5647 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -106,16 +106,16 @@ if [ $BE -ne 0 ]; then fi new "when: add static route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "staticr1]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'staticr1]]>]]>' "^]]>]]>$" new "when: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "when: add direct route" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "directr2]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'directr2]]>]]>' "^]]>]]>$" new "when get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^directr2staticr1]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^directr2staticr1]]>]]>$' new "when: validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorwhen xpath validation failed]]>]]>$" @@ -124,19 +124,19 @@ new "when: discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet1500]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet1500]]>]]>' "^]]>]]>$" new "must: validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "must: add atm interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "atm32]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'atm32]]>]]>' "^]]>]]>$" new "must: atm validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn ATM MTU must be 64 .. 17966]]>]]>$" new "must: add eth interface" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ethernet989]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ethernet989]]>]]>' "^]]>]]>$" new "must: eth validate fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^applicationoperation-failederrorAn Ethernet MTU must be 1500]]>]]>" diff --git a/test/test_yang.sh b/test/test_yang.sh index 1d953dc5..46eb6234 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -160,13 +160,13 @@ new "cli defined extension" expectfn "$clixon_cli -1f $cfg -y $fyang show version" 0 "3." new "empty values in leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "a]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'a]]>]]>' "^]]>]]>$" new "empty values in leaf-list2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^a]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^a]]>]]>$' new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -182,7 +182,7 @@ new "netconf schema resource, RFC 7895" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' 'ietf-yang-types2013-07-15urn:ietf:params:xml:ns:yang:ietf-yang-typesimplement' new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "125one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '125one]]>]]>' "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -192,33 +192,34 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one]]>]]>$' new "netconf edit leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "hejhopp]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'hejhopp]]>]]>' "^]]>]]>$" new "netconf get leaf-list" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^hejhopp]]>]]>$' new "netconf get leaf-list path" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^125one" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^125one' new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" 0 "" new "cli show leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang show xpath /x/f/e" 0 "foo" + new "netconf set state data (not allowed)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "42]]>]]>" "^protocolinvalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '42]]>]]>' '^protocolinvalid-valueerrorState data not allowed]]>]]>$' new "netconf set presence and not present" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf get presence only" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' new "netconf get presence only" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" @@ -227,7 +228,7 @@ new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf anyxml" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" new "netconf validate anyxml" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -237,28 +238,28 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111one]]>]]>' "^]]>]]>$" new "netconf check add one 3-key" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one]]>]]>' new "netconf add another (with same 1st key)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "121two]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '121two]]>]]>' "^]]>]]>$" new "netconf check add another" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111one121two]]>]]>' new "netconf replace first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "111replace]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111replace]]>]]>' "^]]>]]>$" new "netconf check replace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '111replace121two]]>]]>' new "netconf delete first" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '111]]>]]>' "^]]>]]>$" new "netconf check delete" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '121two]]>]]>' # clear db for next test new "netconf delete candidate" @@ -268,10 +269,10 @@ new "netconf commit empty candidate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconfig config submodule" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "afoo]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'afoo]]>]]>' "^]]>]]>$" new "netconf submodule get config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^afoo]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^afoo]]>]]>$' new "netconf submodule validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index 90fc10e4..4f168fca 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -20,7 +20,7 @@ cat < $fyang1 module $APPNAME{ prefix ex; revision 2018-12-02; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf newex{ type string; } @@ -32,7 +32,7 @@ cat < $fyang2 module $APPNAME{ prefix ex; revision 2018-01-01; - namespace "urn:example:example"; + namespace "urn:example:clixon"; leaf oldex{ type string; } @@ -44,7 +44,7 @@ cat < $fyang3 module other{ prefix oth; revision 2018-01-01; - namespace "urn:example:example2"; + namespace "urn:example:clixon2"; leaf other{ type string; } @@ -85,13 +85,13 @@ if [ $BE -ne 0 ]; then fi new "1. Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' if [ $BE -eq 0 ]; then exit # BE @@ -137,13 +137,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederrorValidation failed]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -181,13 +181,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -226,13 +226,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill @@ -270,13 +270,13 @@ if [ $? -ne 0 ]; then fi new "Set newex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementoldexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -315,13 +315,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -362,13 +362,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Kill backend" # Check if premature kill @@ -408,13 +408,13 @@ if [ $? -ne 0 ]; then fi new "Set oldex" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' new "Set newex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementnewexerrorUnassigned yang spec]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationoperation-failederror' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^applicationunknown-elementothererrorUnassigned yang spec]]>]]>$' new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh index 19447f80..43fb2893 100755 --- a/test/test_yang_namespace.sh +++ b/test/test_yang_namespace.sh @@ -1,11 +1,15 @@ #!/bin/bash -# Yang specifics: multi-keys and empty type + APPNAME=example -# include err() and new() functions and creates $dir +# test two modules example1 and example2 with overlapping statements x. +# x is leaf in example1 and list on example2. +# Test netconf and restconf +# BTW, this is not supported in generated CLI + . ./lib.sh cfg=$dir/conf_yang.xml -fyang=$dir/example.yang +fyang1=$dir/example1.yang fyang2=$dir/example2.yang # /usr/local/share/$APPNAME/yang @@ -18,17 +22,35 @@ cat < $cfg /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/cli $APPNAME + false /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile 1 /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so + true true EOF + +cat < $fyang1 +module example1{ + yang-version 1.1; + prefix ex1; + namespace "urn:example:clixon1"; + import ietf-routing { + description "defines fib-route"; + prefix rt; + } + leaf x{ + type int32; + } +} +EOF + # For testing namespaces - -# x.y is different type. Here it is string whereas in fyang it is list. +# x.y is different type. Here it is string whereas in fyang1 it is list. # cat < $fyang2 module example2{ @@ -43,37 +65,6 @@ module example2{ } EOF -cat < $fyang -module example{ - yang-version 1.1; - prefix ex; - namespace "urn:example:clixon"; - import ietf-routing { - description "defines fib-route"; - prefix rt; - } - leaf x{ - type int32; - } - rpc client-rpc { - description "Example local client-side RPC that is processed by the - the netconf/restconf and not sent to the backend. - This is a clixon implementation detail: some rpc:s - are better processed by the client for API or perf reasons"; - input { - leaf request { - type string; - } - } - output { - leaf result{ - type string; - } - } - } -} -EOF - new "test params: -f $cfg" if [ $BE -ne 0 ]; then @@ -91,36 +82,57 @@ if [ $BE -ne 0 ]; then fi fi +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg -D $DBG" -s /bin/sh www-data & -new "netconf xmlns module ex" -expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' +new "netconf set x in example1" +expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlfn in return" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" +new "netconf get config example1" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^42]]>]]>$' -new "netconf xmlns module ex2" +new "netconf set x in example2" expecteof "$clixon_netconf -qf $cfg" 0 '99]]>]]>' '^]]>]]>$' -new "netconf get config XXX xmlns" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4299]]>]]>$" +new "netconf get config example1 and example2" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" '^4299]]>]]>$' new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang1" 0 "]]>]]>" "^]]>]]>$" -new "netconf xmlns:ex" -expecteof "$clixon_netconf -qf $cfg" 0 '4422]]>]]>' '^]]>]]>$' +new "restconf set x in example1" +expecteq "$(curl -s -X POST -d '{"example1:x":42}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4422]]>]]>$" +new2 "restconf get config example1" +expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} + ' -new "netconf xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg" 0 '9999]]>]]>' '^]]>]]>$' +new "restconf set x in example2" +expecteq "$(curl -s -X POST -d '{"example2:x":{"y":99}}' http://localhost/restconf/data)" '' -new "netconf get config XXX xmlns:ex2" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^44229999]]>]]>$" +# XXX GET ../example1:x is translated to select=/x which gets both example1&2 +#new "restconf get config example1" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example1:x)" '{"example1:x": 42} +# ' -# rpc +# XXX GET ../example2:x is translated to select=/x which gets both example1&2 +#new "restconf get config example2" +#expecteq "$(curl -s -X GET http://localhost/restconf/data/example2:x)" '{"example2:x": {"y":42}} +# ' + +new "restconf get config example1 and example2" +ret=$(curl -s -X GET http://localhost/restconf/data) +expect='"example1:x": 42,"example2:x": {"y": 99}' +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" if [ $BE -eq 0 ]; then exit # BE diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 162c3c0b..99d5b596 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -353,14 +353,19 @@ module clixon-config { Only works for Yang specified XML. If not set, all lists accessed via linear search."; } - leaf CLICON_XML_NS_ITERATE { + leaf CLICON_XML_NS_STRICT { type boolean; default true; description - "If set, iterate through modules to find the matching datanode + "If not set, make non-strict laze namespace checks, by iterating + through modules to find the matching datanode or rpc if no xmlns attribute specifies namespace. - This is loose semantics of finding namespaces. - And it is wrong, but is the way Clixon originally was written."; + This is lazy semantics of finding namespaces, which means you + do not need to explicitly give the namespace if the symbol exists + in some loaded module. + Example: is enough, instead of + But it is wrong, but is the way Clixon originally was written. + Strict semantics is the default."; } leaf CLICON_USE_STARTUP_CONFIG { type int32; diff --git a/yang/ietf-netconf-notification@2008-07-01.yang b/yang/ietf-netconf-notification@2008-07-01.yang index 978cdd4b..1db3ee3e 100644 --- a/yang/ietf-netconf-notification@2008-07-01.yang +++ b/yang/ietf-netconf-notification@2008-07-01.yang @@ -1,5 +1,6 @@ module ietf-netconf-notification { - namespace "urn:ietf:params:xml:ns:netconf:notification:1:0"; + /* namespace "urn:ietf:params:xml:ns:netconf:notification:1.0";*/ + namespace "urn:ietf:params:xml:ns:netmod:notification"; prefix "rcmon"; import ietf-yang-types { prefix yang; } From 0103d589945a9f7e325e8d500db5dce621f3074f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 15:48:30 +0100 Subject: [PATCH 18/72] 2019 --- .gitignore | 3 +-- LICENSE.md | 2 +- Makefile.in | 2 +- apps/Makefile.in | 2 +- apps/backend/Makefile.in | 2 +- apps/backend/backend_client.c | 2 +- apps/backend/backend_client.h | 2 +- apps/backend/backend_commit.c | 2 +- apps/backend/backend_commit.h | 2 +- apps/backend/backend_handle.h | 2 +- apps/backend/backend_main.c | 2 +- apps/backend/backend_plugin.c | 2 +- apps/backend/backend_plugin.h | 2 +- apps/backend/backend_socket.c | 2 +- apps/backend/backend_socket.h | 2 +- apps/backend/clixon_backend.h | 2 +- apps/backend/clixon_backend_handle.c | 2 +- apps/backend/clixon_backend_handle.h | 2 +- apps/backend/clixon_backend_transaction.c | 2 +- apps/backend/clixon_backend_transaction.h | 2 +- apps/cli/Makefile.in | 2 +- apps/cli/cli_common.c | 2 +- apps/cli/cli_common.h | 2 +- apps/cli/cli_generate.c | 2 +- apps/cli/cli_generate.h | 2 +- apps/cli/cli_handle.c | 2 +- apps/cli/cli_handle.h | 2 +- apps/cli/cli_main.c | 2 +- apps/cli/cli_plugin.c | 2 +- apps/cli/cli_plugin.h | 2 +- apps/cli/cli_show.c | 2 +- apps/cli/clixon_cli.h | 2 +- apps/cli/clixon_cli_api.h | 2 +- apps/netconf/Makefile.in | 2 +- apps/netconf/clixon_netconf.h | 2 +- apps/netconf/netconf_filter.c | 2 +- apps/netconf/netconf_filter.h | 2 +- apps/netconf/netconf_hello.c | 2 +- apps/netconf/netconf_hello.h | 2 +- apps/netconf/netconf_lib.c | 2 +- apps/netconf/netconf_lib.h | 2 +- apps/netconf/netconf_main.c | 2 +- apps/netconf/netconf_rpc.c | 2 +- apps/netconf/netconf_rpc.h | 2 +- apps/restconf/Makefile.in | 2 +- apps/restconf/clixon_restconf.h | 2 +- apps/restconf/restconf_lib.c | 2 +- apps/restconf/restconf_lib.h | 2 +- apps/restconf/restconf_main.c | 2 +- apps/restconf/restconf_methods.c | 2 +- apps/restconf/restconf_methods.h | 2 +- apps/restconf/restconf_stream.c | 2 +- apps/restconf/restconf_stream.h | 2 +- configure.ac | 2 +- datastore/Makefile.in | 2 +- datastore/datastore_client.c | 2 +- datastore/text/Makefile.in | 2 +- datastore/text/clixon_xmldb_text.c | 2 +- datastore/text/clixon_xmldb_text.h | 2 +- doc/Makefile.in | 2 +- docker/Dockerfile | 2 +- docker/Makefile.in | 2 +- etc/Makefile.in | 2 +- etc/clixonrc.in | 2 +- example/Dockerfile | 2 +- example/Makefile.in | 2 +- example/example_backend.c | 2 +- example/example_backend_nacm.c | 2 +- example/example_cli.c | 2 +- example/example_netconf.c | 2 +- example/example_restconf.c | 2 +- include/Makefile.in | 2 +- include/clixon_custom.h | 2 +- lib/Makefile.in | 2 +- lib/clixon/Makefile.in | 2 +- lib/clixon/clixon.h.in | 2 +- lib/clixon/clixon_err.h | 2 +- lib/clixon/clixon_event.h | 2 +- lib/clixon/clixon_file.h | 2 +- lib/clixon/clixon_handle.h | 2 +- lib/clixon/clixon_hash.h | 2 +- lib/clixon/clixon_json.h | 2 +- lib/clixon/clixon_log.h | 2 +- lib/clixon/clixon_nacm.h | 2 +- lib/clixon/clixon_netconf_lib.h | 2 +- lib/clixon/clixon_options.h | 2 +- lib/clixon/clixon_plugin.h | 2 +- lib/clixon/clixon_proto.h | 2 +- lib/clixon/clixon_proto_client.h | 2 +- lib/clixon/clixon_queue.h | 2 +- lib/clixon/clixon_sha1.h | 2 +- lib/clixon/clixon_sig.h | 2 +- lib/clixon/clixon_stream.h | 2 +- lib/clixon/clixon_string.h | 2 +- lib/clixon/clixon_xml.h | 2 +- lib/clixon/clixon_xml_db.h | 2 +- lib/clixon/clixon_xml_map.h | 2 +- lib/clixon/clixon_xml_sort.h | 2 +- lib/clixon/clixon_xpath.h | 2 +- lib/clixon/clixon_xpath_ctx.h | 2 +- lib/clixon/clixon_yang.h | 2 +- lib/clixon/clixon_yang_module.h | 2 +- lib/clixon/clixon_yang_type.h | 2 +- lib/src/Makefile.in | 2 +- lib/src/clixon_err.c | 2 +- lib/src/clixon_event.c | 2 +- lib/src/clixon_file.c | 2 +- lib/src/clixon_handle.c | 2 +- lib/src/clixon_hash.c | 2 +- lib/src/clixon_json.c | 2 +- lib/src/clixon_json_parse.h | 2 +- lib/src/clixon_json_parse.l | 2 +- lib/src/clixon_json_parse.y | 2 +- lib/src/clixon_log.c | 2 +- lib/src/clixon_nacm.c | 2 +- lib/src/clixon_netconf_lib.c | 2 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_plugin.c | 2 +- lib/src/clixon_proto.c | 2 +- lib/src/clixon_proto_client.c | 2 +- lib/src/clixon_sig.c | 2 +- lib/src/clixon_stream.c | 2 +- lib/src/clixon_string.c | 2 +- lib/src/clixon_xml.c | 2 +- lib/src/clixon_xml_db.c | 2 +- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_xml_parse.h | 2 +- lib/src/clixon_xml_parse.l | 2 +- lib/src/clixon_xml_parse.y | 2 +- lib/src/clixon_xml_sort.c | 2 +- lib/src/clixon_xpath.c | 2 +- lib/src/clixon_xpath_ctx.c | 2 +- lib/src/clixon_xpath_parse.h | 2 +- lib/src/clixon_xpath_parse.l | 2 +- lib/src/clixon_xpath_parse.y | 2 +- lib/src/clixon_yang.c | 2 +- lib/src/clixon_yang_cardinality.c | 2 +- lib/src/clixon_yang_cardinality.h | 2 +- lib/src/clixon_yang_module.c | 2 +- lib/src/clixon_yang_parse.h | 2 +- lib/src/clixon_yang_parse.l | 2 +- lib/src/clixon_yang_parse.y | 2 +- lib/src/clixon_yang_type.c | 2 +- util/Makefile.in | 2 +- util/clixon_util_json.c | 2 +- util/clixon_util_stream.c | 2 +- util/clixon_util_xml.c | 2 +- util/clixon_util_xpath.c | 2 +- util/clixon_util_yang.c | 2 +- yang/Makefile.in | 2 +- yang/clixon-config@2018-10-21.yang | 2 +- 151 files changed, 151 insertions(+), 152 deletions(-) diff --git a/.gitignore b/.gitignore index cd4446bc..c04c1244 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,9 @@ lib/Makefile lib/*/Makefile autom4te.cache/ -clixon.conf.cpp -clixon.mk config.log config.status +TAGS apps/backend/clixon_backend apps/backend/test diff --git a/LICENSE.md b/LICENSE.md index 84997252..af683735 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2009-2018 Olof Hagsand and Benny Holmgren +Copyright 2009-2019 Olof Hagsand and Benny Holmgren CLIXON is dual license. diff --git a/Makefile.in b/Makefile.in index 3d34933a..a1a9f1f0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/Makefile.in b/apps/Makefile.in index bfcc31cf..d8125aa0 100644 --- a/apps/Makefile.in +++ b/apps/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 4d57dfc8..b3700e33 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 3e1fa1ad..c9ea04ad 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 81620d4a..c9a1da11 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 7c29131a..61e9a246 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 45b0f922..3af2461a 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 8eb3e073..f2acea6f 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ba297852..77d791cf 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 996a27cb..6218c0cd 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index a2736b19..eb038319 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c index ec32fb53..0d823ec8 100644 --- a/apps/backend/backend_socket.c +++ b/apps/backend/backend_socket.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/backend_socket.h b/apps/backend/backend_socket.h index df6ac149..8778be50 100644 --- a/apps/backend/backend_socket.h +++ b/apps/backend/backend_socket.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend.h b/apps/backend/clixon_backend.h index b3a75763..37e046b1 100644 --- a/apps/backend/clixon_backend.h +++ b/apps/backend/clixon_backend.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index f4707de4..3ab58e9c 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index bdfbb425..491778aa 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_transaction.c b/apps/backend/clixon_backend_transaction.c index 173ba01c..727a7332 100644 --- a/apps/backend/clixon_backend_transaction.c +++ b/apps/backend/clixon_backend_transaction.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index a290c5f1..447cd15f 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index a55918d9..2b75b479 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index f6796f58..ebab38a7 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h index 8e177e5e..4e29838f 100644 --- a/apps/cli/cli_common.h +++ b/apps/cli/cli_common.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 022e41b8..b25067c4 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_generate.h b/apps/cli/cli_generate.h index 371063ba..d049d600 100644 --- a/apps/cli/cli_generate.h +++ b/apps/cli/cli_generate.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c index 58e03948..34a92c4d 100644 --- a/apps/cli/cli_handle.c +++ b/apps/cli/cli_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h index bbf66742..c7fd728b 100644 --- a/apps/cli/cli_handle.h +++ b/apps/cli/cli_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index b22e1c9f..e6d7925a 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 633c46ae..4f17f948 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index 2df7fb47..75aa99c2 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index ec39ac4e..0fa9fa56 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/clixon_cli.h b/apps/cli/clixon_cli.h index c41d7185..aaebfb19 100644 --- a/apps/cli/clixon_cli.h +++ b/apps/cli/clixon_cli.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 3507e31f..d2926029 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 81d86577..ee2d0082 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index 9fed0a1a..470474f1 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_filter.c b/apps/netconf/netconf_filter.c index 89b698e9..8451c33b 100644 --- a/apps/netconf/netconf_filter.c +++ b/apps/netconf/netconf_filter.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_filter.h b/apps/netconf/netconf_filter.h index ed5e3d98..ce7d76e6 100644 --- a/apps/netconf/netconf_filter.h +++ b/apps/netconf/netconf_filter.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 1be1211c..9c4b07ba 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_hello.h b/apps/netconf/netconf_hello.h index 4f877b32..2ccec1b8 100644 --- a/apps/netconf/netconf_hello.h +++ b/apps/netconf/netconf_hello.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index 50723de4..0a6b869b 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_lib.h b/apps/netconf/netconf_lib.h index 6d2de111..d243ac6a 100644 --- a/apps/netconf/netconf_lib.h +++ b/apps/netconf/netconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 4cdef1a0..e60a5d08 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 7945110e..f2bbb605 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/netconf/netconf_rpc.h b/apps/netconf/netconf_rpc.h index 3ce6a615..4a20397f 100644 --- a/apps/netconf/netconf_rpc.h +++ b/apps/netconf/netconf_rpc.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 32366f89..768a261d 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index 2c79c3cc..a8625e9f 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 062dff65..500a07c3 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index e05eb054..8c478370 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index d5067c4f..e6234884 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b2910a1b..8f3cb4a5 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 11daaa26..42525946 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index ec20f373..667db0ad 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/apps/restconf/restconf_stream.h b/apps/restconf/restconf_stream.h index 480832f1..0cea952f 100644 --- a/apps/restconf/restconf_stream.h +++ b/apps/restconf/restconf_stream.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/configure.ac b/configure.ac index ebdccaf6..94710961 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 6943dec5..5adde268 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index b2a20029..0bbe5a3b 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index 23c2a7bf..9eadb121 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 645fef97..d4160253 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index 19e9351d..9bb46e42 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/doc/Makefile.in b/doc/Makefile.in index ad0f4203..f67f183d 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/docker/Dockerfile b/docker/Dockerfile index b1a71ce3..a15c874b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/docker/Makefile.in b/docker/Makefile.in index c18b9fad..375fa22d 100644 --- a/docker/Makefile.in +++ b/docker/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/etc/Makefile.in b/etc/Makefile.in index 263840df..09c671af 100644 --- a/etc/Makefile.in +++ b/etc/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/etc/clixonrc.in b/etc/clixonrc.in index e06018c6..c2a5cde3 100644 --- a/etc/clixonrc.in +++ b/etc/clixonrc.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/Dockerfile b/example/Dockerfile index 08314bf8..27cfb737 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/Makefile.in b/example/Makefile.in index bbbd9070..8e18d3ea 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/example/example_backend.c b/example/example_backend.c index ae1f51d4..7c09ee3d 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c index 3814a975..608916a8 100644 --- a/example/example_backend_nacm.c +++ b/example/example_backend_nacm.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_cli.c b/example/example_cli.c index 01a375a3..3aaf9259 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_netconf.c b/example/example_netconf.c index b70128de..742b1d98 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/example/example_restconf.c b/example/example_restconf.c index ec0e8d27..e49a48f4 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/include/Makefile.in b/include/Makefile.in index 75aa04a1..d55e6e97 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 8b7c5ae6..7a6c342a 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/Makefile.in b/lib/Makefile.in index d34e81ac..a0f09147 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/clixon/Makefile.in b/lib/clixon/Makefile.in index e455a860..eee5ce91 100644 --- a/lib/clixon/Makefile.in +++ b/lib/clixon/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 2fc6c50d..6615433c 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_err.h b/lib/clixon/clixon_err.h index b5961453..63fff1fe 100644 --- a/lib/clixon/clixon_err.h +++ b/lib/clixon/clixon_err.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_event.h b/lib/clixon/clixon_event.h index 99b598e2..c2ef6edc 100644 --- a/lib/clixon/clixon_event.h +++ b/lib/clixon/clixon_event.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_file.h b/lib/clixon/clixon_file.h index 3e34b6ce..44915abd 100644 --- a/lib/clixon/clixon_file.h +++ b/lib/clixon/clixon_file.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_handle.h b/lib/clixon/clixon_handle.h index 711e6cb8..944f5911 100644 --- a/lib/clixon/clixon_handle.h +++ b/lib/clixon/clixon_handle.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_hash.h b/lib/clixon/clixon_hash.h index 0adbb047..0cbb8129 100644 --- a/lib/clixon/clixon_hash.h +++ b/lib/clixon/clixon_hash.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_json.h b/lib/clixon/clixon_json.h index 0a644680..0a4af828 100644 --- a/lib/clixon/clixon_json.h +++ b/lib/clixon/clixon_json.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index 7db79142..a9835488 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h index 978b5486..cdae13bb 100644 --- a/lib/clixon/clixon_nacm.h +++ b/lib/clixon/clixon_nacm.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index a615c663..2bb4e8f2 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index a849c6cd..af92305c 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index cd6038aa..09703d90 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index e3290c3f..3d9e6603 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index ac917d77..07d808b5 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_queue.h b/lib/clixon/clixon_queue.h index 70127756..ee277134 100644 --- a/lib/clixon/clixon_queue.h +++ b/lib/clixon/clixon_queue.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_sha1.h b/lib/clixon/clixon_sha1.h index 6f788e14..fbf9bfc5 100644 --- a/lib/clixon/clixon_sha1.h +++ b/lib/clixon/clixon_sha1.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_sig.h b/lib/clixon/clixon_sig.h index ac03a908..51c29341 100644 --- a/lib/clixon/clixon_sig.h +++ b/lib/clixon/clixon_sig.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_stream.h b/lib/clixon/clixon_stream.h index a1870395..ae09a8ab 100644 --- a/lib/clixon/clixon_stream.h +++ b/lib/clixon/clixon_stream.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index dd2eb290..1b2b20fc 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 2117bfef..be3e2352 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 58607396..a79349d1 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 9eba5bfd..5148589a 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index c4ffeab3..2a58472f 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index ce62fc23..d0d6a360 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_xpath_ctx.h b/lib/clixon/clixon_xpath_ctx.h index 03854495..0a1bb122 100644 --- a/lib/clixon/clixon_xpath_ctx.h +++ b/lib/clixon/clixon_xpath_ctx.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index a9291583..fb727dfd 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang_module.h b/lib/clixon/clixon_yang_module.h index 0ca4760c..2a26b7ba 100644 --- a/lib/clixon/clixon_yang_module.h +++ b/lib/clixon/clixon_yang_module.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 61b581af..8fecdd5f 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index ce0c0587..fc8fa3e4 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index 6ec992c6..b4f07fbf 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index d15e12c5..3085f89c 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index 22998553..73a2b894 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index dcb6da5c..a89d81fd 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index aa397d4d..4769abfa 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 1cd99af1..f6f8ff23 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.h b/lib/src/clixon_json_parse.h index 8196e942..0f088a32 100644 --- a/lib/src/clixon_json_parse.h +++ b/lib/src/clixon_json_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.l b/lib/src/clixon_json_parse.l index 3763d7f1..6d720140 100644 --- a/lib/src/clixon_json_parse.l +++ b/lib/src/clixon_json_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index 1cf6a583..a6533a06 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 87e0c8de..4c8641c3 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index bf60b865..8efa4421 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index b8981bbc..cfa93882 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index e6410ad8..449b33ae 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 03ed2b29..f0a28a6f 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 17b09614..30da4e59 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 396a0665..0dbb76d1 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_sig.c b/lib/src/clixon_sig.c index c1374876..d7c27335 100644 --- a/lib/src/clixon_sig.c +++ b/lib/src/clixon_sig.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_stream.c b/lib/src/clixon_stream.c index ae454256..6bad4671 100644 --- a/lib/src/clixon_stream.c +++ b/lib/src/clixon_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index a7c05f39..b3e5e588 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 6849e94d..d4f15a13 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 32aab817..8fb5da70 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5d210644..5b4f513d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.h b/lib/src/clixon_xml_parse.h index 3122e8f0..35ee2dee 100644 --- a/lib/src/clixon_xml_parse.h +++ b/lib/src/clixon_xml_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index 39173634..812b246c 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index d6a1200f..9e2eafaa 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 265d82a5..d227a961 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 2fec113a..b814ffc4 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_ctx.c b/lib/src/clixon_xpath_ctx.c index 721df45c..a50ff5c2 100644 --- a/lib/src/clixon_xpath_ctx.c +++ b/lib/src/clixon_xpath_ctx.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.h b/lib/src/clixon_xpath_parse.h index 1affce3c..6ba57672 100644 --- a/lib/src/clixon_xpath_parse.h +++ b/lib/src/clixon_xpath_parse.h @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l index 5ac54644..a886dd70 100644 --- a/lib/src/clixon_xpath_parse.l +++ b/lib/src/clixon_xpath_parse.l @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index 4e055ca7..b121cc2c 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2298235a..36404682 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_cardinality.c b/lib/src/clixon_yang_cardinality.c index 9c26fe92..638a077b 100644 --- a/lib/src/clixon_yang_cardinality.c +++ b/lib/src/clixon_yang_cardinality.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_cardinality.h b/lib/src/clixon_yang_cardinality.h index eb71e8ea..815d8d56 100644 --- a/lib/src/clixon_yang_cardinality.h +++ b/lib/src/clixon_yang_cardinality.h @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index c0b1e408..500aaf89 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.h b/lib/src/clixon_yang_parse.h index baa136df..d85e8f6b 100644 --- a/lib/src/clixon_yang_parse.h +++ b/lib/src/clixon_yang_parse.h @@ -5,7 +5,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index 58715c0c..905eb388 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -1,7 +1,7 @@ /* ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 7bd42cc1..9ffac45e 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 788af4f7..d043abff 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/Makefile.in b/util/Makefile.in index 5095b612..657cd3db 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/util/clixon_util_json.c b/util/clixon_util_json.c index dd16e0e0..b60e2299 100644 --- a/util/clixon_util_json.c +++ b/util/clixon_util_json.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_stream.c b/util/clixon_util_stream.c index 6f45de57..3530514f 100644 --- a/util/clixon_util_stream.c +++ b/util/clixon_util_stream.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index 88d2ab53..8b3e3fa6 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index 81b33361..b711b072 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/util/clixon_util_yang.c b/util/clixon_util_yang.c index 3615fd27..9f3b0e15 100644 --- a/util/clixon_util_yang.c +++ b/util/clixon_util_yang.c @@ -2,7 +2,7 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON. diff --git a/yang/Makefile.in b/yang/Makefile.in index b595b5ea..fb32b74d 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -1,7 +1,7 @@ # # ***** BEGIN LICENSE BLOCK ***** # -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren +# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren # # This file is part of CLIXON # diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 99d5b596..d6b8c2c1 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -12,7 +12,7 @@ module clixon-config { description "Clixon configuration file ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren This file is part of CLIXON From 32ea957a524c36828b879d3498741baae00f20c0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 17:43:58 +0100 Subject: [PATCH 19/72] CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. --- CHANGELOG.md | 1 + apps/backend/backend_client.c | 2 +- apps/backend/backend_main.c | 2 - datastore/text/clixon_xmldb_text.c | 29 ++---------- example/example_cli.c | 2 +- lib/clixon/clixon_options.h | 3 -- lib/clixon/clixon_xml_map.h | 1 - lib/clixon/clixon_xml_sort.h | 9 +--- lib/src/clixon_options.c | 7 +-- lib/src/clixon_xml_map.c | 74 ++---------------------------- lib/src/clixon_xml_sort.c | 24 ++-------- test/test_cli.sh | 8 +++- test/test_nacm_ext.sh | 2 +- yang/clixon-config@2018-10-21.yang | 9 ---- 14 files changed, 28 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2ebd7a..cbc70587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) +* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Removed `delete-config` support for candidate db since it is not supported in RFC6241. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index c9ea04ad..6a8b5943 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -482,7 +482,7 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * the upper part of the tree, until we get the "config" tree. */ - if (clicon_xml_sort(h) && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) + if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 77d791cf..8e05c175 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -832,8 +832,6 @@ main(int argc, goto done; if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0) goto done; - if (xmldb_setopt(h, "sort", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XML_SORT")) < 0) - goto done; /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index d4160253..42210170 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -88,8 +88,6 @@ struct text_handle { Assumes single backend*/ char *th_format; /* Datastroe format: xml / json */ int th_pretty; /* Store xml/json pretty-printed. */ - int th_sort; /* Sort XML lists and leaf-lists alphabetically - (unless ordered-by user) */ }; /* Struct per database in hash */ @@ -241,8 +239,6 @@ text_getopt(xmldb_handle xh, *value = th->th_format; else if (strcmp(optname, "pretty") == 0) *value = &th->th_pretty; - else if (strcmp(optname, "sort") == 0) - *value = &th->th_sort; else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -292,9 +288,6 @@ text_setopt(xmldb_handle xh, else if (strcmp(optname, "pretty") == 0){ th->th_pretty = (intptr_t)value; } - else if (strcmp(optname, "sort") == 0){ - th->th_sort = (intptr_t)value; - } else{ clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); goto done; @@ -551,14 +544,8 @@ text_get(xmldb_handle xh, /* Add default values (if not set) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; - /* Order XML children according to YANG. - * XXX: should this be !th->th_sort? - */ - if (!th->th_sort) - if (xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) - goto done; #if 1 /* debug */ - if (th->th_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__); #endif if (debug>1) @@ -741,7 +728,7 @@ text_modify(struct text_handle *th, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; x0vec[i++] = x0c; } @@ -865,7 +852,7 @@ text_modify_top(struct text_handle *th, goto ok; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, th->th_sort, yc) < 0) + if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; @@ -996,14 +983,8 @@ text_put(xmldb_handle xh, xml_name(x0)); goto done; } -#if 0 - /* Add yang specification backpointer to all XML nodes - * This is already done in from_client_edit_config() */ - if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) - goto done; -#endif #if 0 /* debug */ - if (th->th_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); #endif /* @@ -1029,7 +1010,7 @@ text_put(xmldb_handle xh, if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) goto done; #if 0 /* debug */ - if (th->th_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) + if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); #endif /* Write back to datastore cache if first time */ diff --git a/example/example_cli.c b/example/example_cli.c index 3aaf9259..bf24064d 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h, goto done; } /* Print result */ - xml2txt(stdout, xml_child_i(xret, 0), 1); + xml2txt(stdout, xml_child_i(xret, 0), 0); retval = 0; done: if (xret) diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index af92305c..04aae2e6 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -153,9 +153,6 @@ static inline char *clicon_xmldb_dir(clicon_handle h){ static inline char *clicon_xmldb_plugin(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_PLUGIN"); } -static inline int clicon_xml_sort(clicon_handle h){ - return clicon_option_bool(h, "CLICON_XML_SORT"); -} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 5148589a..2fa2afeb 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -62,7 +62,6 @@ int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath); int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); int xml_tree_prune_flagged(cxobj *xt, int flag, int test); int xml_default(cxobj *x, void *arg); -int xml_order(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 2a58472f..83f94f44 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -36,13 +36,6 @@ #ifndef _CLIXON_XML_SORT_H #define _CLIXON_XML_SORT_H -/* Sort and binary search of XML children - * XXX This variable is a kludge since low-level functions xml_merge/xml_diff calls - * match_base_child without handle - * @see clicon_xml_sort - */ -extern int xml_child_sort; - /* * Prototypes */ @@ -55,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, int xml_sort, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 449b33ae..4fdb1dce 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -207,6 +207,8 @@ parse_configfile(clicon_handle h, *xconfig = xt; xt = NULL; done: + if (cbret) + cbuf_free(cbret); if (xt) xml_free(xt); if (f) @@ -275,11 +277,6 @@ clicon_options_main(clicon_handle h, goto done; /* Set clixon_conf pointer to handle */ clicon_conf_xml_set(h, xconfig); - /* Specific option handling */ - if (clicon_option_bool(h, "CLICON_XML_SORT") == 1) - xml_child_sort = 1; - else - xml_child_sort = 0; /* XXX Kludge to low-level functions to search for xml in all yang modules */ _CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT"); retval = 0; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5b4f513d..d3585ee4 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -988,8 +988,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; } - /* XXX xml_child_sort is global */ - if (match_base_child(x2, x1c, &x2c, xml_child_sort, yc) < 0) + if (match_base_child(x2, x1c, &x2c, yc) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) @@ -1025,8 +1024,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; } - /* XXX xml_child_sort is global */ - if (match_base_child(x1, x2c, &x1c, xml_child_sort, yc) < 0) + if (match_base_child(x1, x2c, &x1c, yc) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1550,70 +1548,6 @@ xml_default(cxobj *xt, return retval; } -/*! Order XML children according to YANG - * @param[in] xt XML top of tree - * @param[in] arg Dummy (so it can be called from xml_apply) - * @see xml_sort XXX: how do they relate? - * Called from text_get *only* - */ -int -xml_order(cxobj *xt, - void *arg) -{ - int retval = -1; - yang_stmt *y; - yang_stmt *yc; - int i; - int j0; - int j; - cxobj *xc; - cxobj *xj; - char *yname; /* yang child name */ - char *xname; /* xml child name */ - - if ((y = (yang_stmt*)xml_spec(xt)) == NULL){ - retval = 0; - goto done; - } - j0 = 0; - /* Go through yang node's children and ensure that the - * xml children follow this order. - * Do not order the list or leaf-list children (have same name). - */ - for (i=0; iys_len; i++){ - yc = y->ys_stmt[i]; - if (!yang_datanode(yc)) - continue; - yname = yc->ys_argument; - /* First go thru xml children with same name in rest of list */ - for (; j0ys_keyword, keynr, keyvec, keyval); + if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ + yorder = yang_order(yc); + *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); + } else{ - if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ - yorder = yang_order(yc); - *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); - } - else{ - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); - } + *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } ok: retval = 0; diff --git a/test/test_cli.sh b/test/test_cli.sh index b4005433..23fee546 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -115,7 +115,13 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" 0 "^$" expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" -expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" +expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 'rpc-reply { + route { + address-family ipv4; + next-hop { + next-hop-list 2.3.4.5; + } + source-protocol static;' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index b706b96f..b9cef0e1 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -209,7 +209,7 @@ new "cli show conf as guest" expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied" new "cli rpc as admin" -expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "next-hop-list 2.3.4.5;" new "cli rpc as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny" diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index d6b8c2c1..efa777e3 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -344,15 +344,6 @@ module clixon-config { If set, insert spaces and line-feeds making the XML/JSON human readable. If not set, make the XML/JSON more compact."; } - leaf CLICON_XML_SORT { - type boolean; - default true; - description - "If set, sort XML lists and leaf-lists alphabetically and uses binary - search. Unless ordered-by user is used. - Only works for Yang specified XML. - If not set, all lists accessed via linear search."; - } leaf CLICON_XML_NS_STRICT { type boolean; default true; From 6c48165340540c865de5d173990f7c1b14599e6d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 18:20:50 +0100 Subject: [PATCH 20/72] three-valued return values for datastore text_modify --- apps/backend/backend_commit.c | 2 +- datastore/text/clixon_xmldb_text.c | 84 ++++++++++++++++++------------ example/example_backend.c | 12 ++++- lib/src/clixon_xml_db.c | 2 +- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 61e9a246..d7c2d6c3 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -285,7 +285,7 @@ candidate_commit(clicon_handle h, /* Optionally write (potentially modified) tree back to candidate */ if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ - if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL)) < 0) + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, cbret)) < 0) goto done; if (ret == 0) goto fail; diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 42210170..c4d2d1b4 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -572,7 +572,10 @@ text_get(xmldb_handle xh, * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[out] cbret Initialized cligen buffer. Contains return XML or "". + * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0. + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK * Assume x0 and x1 are same on entry and that y is the spec * @see text_modify_top */ @@ -598,6 +601,7 @@ text_modify(struct text_handle *th, yang_stmt *yc; /* yang child */ cxobj **x0vec = NULL; int i; + int ret; assert(x1 && xml_type(x1) == CX_ELMNT); assert(y0); @@ -613,7 +617,7 @@ text_modify(struct text_handle *th, if (x0){ if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) goto done; - goto ok; + goto fail; } case OP_NONE: /* fall thru */ case OP_MERGE: @@ -658,7 +662,7 @@ text_modify(struct text_handle *th, if (x0==NULL){ if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; } case OP_REMOVE: /* fall thru */ if (x0){ @@ -675,7 +679,7 @@ text_modify(struct text_handle *th, if (x0){ if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) goto done; - goto ok; + goto fail; } case OP_REPLACE: /* fall thru */ if (x0){ @@ -738,18 +742,18 @@ text_modify(struct text_handle *th, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) + if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) - goto ok; + if (ret == 0) + goto fail; } break; case OP_DELETE: if (x0==NULL){ if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; } case OP_REMOVE: /* fall thru */ if (x0) @@ -760,21 +764,26 @@ text_modify(struct text_handle *th, } /* CONTAINER switch op */ } /* else Y_CONTAINER */ xml_sort(x0p, NULL); - ok: - retval = 0; + retval = 1; done: if (x0vec) free(x0vec); return retval; + fail: /* cbret set */ + retval = 0; + goto done; } /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 - * @param[in] th text handle - * @param[in] x0 Base xml tree (can be NULL in add scenarios) - * @param[in] x1 xml tree which modifies base + * @param[in] th text handle + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[out] cbret Initialized cligen buffer. Contains return XML or "". + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0. + * @retval -1 Error + * @retval 0 Failed (cbret set) + * @retval 1 OK * @see text_modify */ static int @@ -792,6 +801,7 @@ text_modify_top(struct text_handle *th, yang_stmt *yc; /* yang child */ yang_stmt *ymod;/* yang module */ char *opstr; + int ret; /* Assure top-levels are 'config' */ assert(x0 && strcmp(xml_name(x0),"config")==0); @@ -820,7 +830,7 @@ text_modify_top(struct text_handle *th, case OP_DELETE: if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) goto done; - goto ok; + goto fail; break; default: break; @@ -849,21 +859,24 @@ text_modify_top(struct text_handle *th, if (yc == NULL){ if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0) goto done; - goto ok; + goto fail; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) + if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) - goto ok; + if (ret == 0) + goto fail; } - ok: - retval = 0; + // ok: + retval = 1; done: return retval; + fail: /* cbret set */ + retval = 0; + goto done; } /* text_modify_top */ /*! For containers without presence and no children(except attrs), remove @@ -923,14 +936,11 @@ text_put(xmldb_handle xh, yang_spec *yspec; cxobj *x0 = NULL; struct db_element *de = NULL; - int cbretlocal = 0; /* Set if cbret is NULL on entry */ + int ret; if (cbret == NULL){ - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cbretlocal++; + clicon_err(OE_XML, EINVAL, "cbret is NULL"); + goto done; } if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); @@ -991,10 +1001,10 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0) + if ((ret = text_modify_top(th, x0, x1, yspec, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ - if (cbuf_len(cbret)) + if (ret == 0) goto fail; /* Remove NONE nodes if all subs recursively are also NONE */ @@ -1047,8 +1057,6 @@ text_put(xmldb_handle xh, goto done; retval = 1; done: - if (cbretlocal && cbret) - cbuf_free(cbret); if (f != NULL) fclose(f); if (dbfile) @@ -1408,6 +1416,8 @@ main(int argc, char *yangmod; /* yang file */ yang_spec *yspec = NULL; clicon_handle h; + cbuf *cbret = NULL; + int ret; if ((h = clicon_handle_init()) == NULL) goto done; @@ -1456,13 +1466,21 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn, NULL) < 1) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; + } + if ((ret = xmldb_put(h, db, op, NULL, xn, cbret)) < 0) + goto done; + if (ret == 0) + fprintf(stderr, "%s\n", cbuf_get(cbret)); } else usage(argv[0]); printf("\n"); done: + if (cbret) + cbuf_free(cbret); return 0; } diff --git a/example/example_backend.c b/example/example_backend.c index 7c09ee3d..a322ad1e 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -260,6 +260,7 @@ example_reset(clicon_handle h, int retval = -1; cxobj *xt = NULL; int ret; + cbuf *cbret = NULL; if (xml_parse_string("" "loex:loopback" @@ -268,15 +269,22 @@ example_reset(clicon_handle h, /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) goto done; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } /* Merge user reset state */ - if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, NULL)) < 0) + if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret)) < 0) goto done; if (ret == 0){ - clicon_err(OE_XML, 0, "Error when writing to XML database"); + clicon_err(OE_XML, 0, "Error when writing to XML database: %s", + cbuf_get(cbret)); goto done; } retval = 0; done: + if (cbret) + cbuf_free(cbret); if (xt != NULL) xml_free(xt); return retval; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 8fb5da70..73959bf7 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -379,7 +379,7 @@ xmldb_get(clicon_handle h, * @param[in] db running or candidate * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy - * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". + * @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0 * @retval 1 OK * @retval 0 Failed, cbret contains error xml message * @retval -1 Error From b443471e7b3426c2e3068aa5c89a2f0c1ddd7280 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 18:44:27 +0100 Subject: [PATCH 21/72] Removed CLI generation for yang notifications (and other non-data yang nodes --- CHANGELOG.md | 1 + apps/cli/cli_generate.c | 14 +++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc70587..82ba6372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ * ys_keyword){ - case Y_GROUPING: - case Y_RPC: - case Y_AUGMENT: - return 0; - break; case Y_CONTAINER: if (yang2cli_container(h, ys, cbuf, gt, level) < 0) goto done; @@ -773,19 +768,20 @@ yang2cli_stmt(clicon_handle h, if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) goto done; break; - default: + case Y_SUBMODULE: + case Y_MODULE: for (i=0; iys_len; i++) if ((yc = ys->ys_stmt[i]) != NULL) if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0) goto done; break; + default: /* skip */ + break; } } - retval = 0; done: return retval; - } /*! Generate CLI code for Yang specification @@ -814,7 +810,7 @@ yang2cli(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - /* Traverse YANG specification: loop through statements */ + /* Traverse YANG, loop through all modules and generate CLI */ for (i=0; iyp_len; i++) if ((ymod = yspec->yp_stmt[i]) != NULL){ if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0) From 11479f7ec3840c1611b1192d84736f205739b004 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 20:59:57 +0100 Subject: [PATCH 22/72] add yang-spec as arg to decode clicon msg --- apps/backend/backend_client.c | 12 +++++++----- apps/cli/cli_common.c | 2 +- apps/netconf/netconf_rpc.c | 7 +++++-- apps/restconf/restconf_stream.c | 2 +- lib/clixon/clixon_proto.h | 2 +- lib/src/clixon_proto.c | 4 +++- lib/src/clixon_xml_map.c | 14 +++++--------- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 6a8b5943..a141f09f 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -965,6 +965,7 @@ from_client_msg(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; + yspec = clicon_dbspec_yang(h); /* Return netconf message. Should be filled in by the dispatch(sub) functions * as wither rpc-error or by positive response. */ @@ -972,21 +973,22 @@ from_client_msg(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); goto done; } - if (clicon_msg_decode(msg, &xt) < 0){ + if (clicon_msg_decode(msg, yspec, &xt) < 0){ if (netconf_malformed_message(cbret, "XML parse error")< 0) goto done; goto reply; } - /* Get yang spec */ - yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */ + if ((x = xpath_first(xt, "/rpc")) == NULL){ if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } - /* Populate incoming XML tree with yang */ + /* Populate incoming XML tree with yang - + * should really have been dealt with by decode above + * maybe not necessary since it should be */ if (xml_spec_populate_rpc(h, x, yspec) < 0) - goto done; + goto done; if ((ret = xml_yang_validate_rpc(x, cbret)) < 0) goto done; if (ret == 0) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ebab38a7..8a52207e 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -959,7 +959,7 @@ cli_notification_cb(int s, event_unreg_fd(s, cli_notification_cb); goto done; } - if (clicon_msg_decode(reply, &xt) < 0) + if (clicon_msg_decode(reply, NULL, &xt) < 0) /* XXX pass yang_spec */ goto done; if ((xe = xpath_first(xt, "//event")) != NULL){ x = NULL; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index f2bbb605..0770a0b9 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -754,6 +754,8 @@ netconf_notification_cb(int s, cbuf *cb; cxobj *xn = NULL; /* event xml */ cxobj *xt = NULL; /* top xml */ + clicon_handle h = (clicon_handle)arg; + yang_spec *yspec = NULL; clicon_debug(1, "%s", __FUNCTION__); /* get msg (this is the reason this function is called) */ @@ -767,7 +769,8 @@ netconf_notification_cb(int s, event_unreg_fd(s, netconf_notification_cb); goto done; } - if (clicon_msg_decode(reply, &xt) < 0) + yspec = clicon_dbspec_yang(h); + if (clicon_msg_decode(reply, yspec, &xt) < 0) goto done; if ((xn = xpath_first(xt, "notification")) == NULL) goto ok; @@ -839,7 +842,7 @@ netconf_create_subscription(clicon_handle h, goto ok; if (event_reg_fd(s, netconf_notification_cb, - NULL, + h, "notification socket") < 0) goto done; ok: diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 667db0ad..3d34c798 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -180,7 +180,7 @@ restconf_stream_cb(int s, clicon_exit_set(); goto done; } - if (clicon_msg_decode(reply, &xtop) < 0) + if (clicon_msg_decode(reply, NULL, &xtop) < 0) /* XXX pass yang_spec */ goto done; /* create event */ if ((cb = cbuf_new()) == NULL){ diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 3d9e6603..e5457685 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -66,7 +66,7 @@ struct clicon_msg *clicon_msg_encode(char *format, ...) __attribute__ ((format ( #else struct clicon_msg *clicon_msg_encode(char *format, ...); #endif -int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml); +int clicon_msg_decode(struct clicon_msg *msg, yang_spec *yspec, cxobj **xml); int clicon_connect_unix(char *sockpath); diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 30da4e59..76c7816e 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -160,10 +160,12 @@ clicon_msg_encode(char *format, ...) /*! Decode a clicon netconf message * @param[in] msg CLICON msg + * @param[in] yspec Yang specification, (can be NULL) * @param[out] xml XML parse tree */ int clicon_msg_decode(struct clicon_msg *msg, + yang_spec *yspec, cxobj **xml) { int retval = -1; @@ -172,7 +174,7 @@ clicon_msg_decode(struct clicon_msg *msg, /* body */ xmlstr = msg->op_body; clicon_debug(1, "%s %s", __FUNCTION__, xmlstr); - if (xml_parse_string(xmlstr, NULL, xml) < 0) + if (xml_parse_string(xmlstr, yspec, xml) < 0) goto done; retval = 0; done: diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index d3585ee4..660c4849 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1623,7 +1623,7 @@ xml_spec_populate_rpc(clicon_handle h, yang_stmt *ymod=NULL; /* yang module */ yang_stmt *yi = NULL; /* input */ cxobj *x; - int i; + // int i; if ((strcmp(xml_name(xrpc), "rpc"))!=0){ clicon_err(OE_UNIX, EINVAL, "RPC expected"); @@ -1635,15 +1635,12 @@ xml_spec_populate_rpc(clicon_handle h, goto done; if (ymod != NULL) y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x)); - /* Loose semantics: loop through all modules to find the node + /* Non-strict semantics: loop through all modules to find the node */ if (y == NULL && !clicon_option_bool(h, "CLICON_XML_NS_STRICT")){ - for (i=0; iyp_len; i++){ - ymod = yspec->yp_stmt[i]; - if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) - break; - } + if (xml_yang_find_non_strict(x, yspec, &y) < 0) /* find rpc */ + goto done; } if (y){ xml_spec_set(x, y); @@ -1696,9 +1693,8 @@ xml_spec_populate(cxobj *x, if (ys_module_by_xml(yspec, x, &ymod) < 0) goto done; if (ymod != NULL) - y = yang_find_datanode((yang_node*)ymod, name); + y = yang_find_schemanode((yang_node*)ymod, name); /* Non-strict semantics: loop through all modules to find the node - * XXX clicon_option_bool(h, "CLICON_XML_NS_STRICT") */ if (y == NULL && !_CLICON_XML_NS_STRICT){ if (xml_yang_find_non_strict(x, yspec, &y) < 0) From 7a8f242a09f3403ae1473a70132bd27b42da70a4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 2 Jan 2019 22:50:03 +0100 Subject: [PATCH 23/72] Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. --- CHANGELOG.md | 1 + apps/backend/backend_main.c | 3 + apps/cli/cli_main.c | 3 + apps/netconf/netconf_main.c | 4 +- apps/restconf/restconf_main.c | 4 +- apps/restconf/restconf_methods.c | 12 +- example/example.yang | 9 - lib/src/clixon_proto_client.c | 2 +- test/test_install.sh | 6 +- test/test_restconf.sh | 4 +- yang/Makefile.in | 2 + yang/clixon-config@2018-10-21.yang | 10 +- yang/clixon-lib@2019-01-02.yang | 55 ++++++ yang/ietf-restconf@2017-01-26.yang | 279 +++++++++++++++++++++++++++++ 14 files changed, 362 insertions(+), 32 deletions(-) create mode 100644 yang/clixon-lib@2019-01-02.yang create mode 100644 yang/ietf-restconf@2017-01-26.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ba6372..588c5e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` * Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`. diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 8e05c175..6fbb8750 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -805,6 +805,9 @@ main(int argc, if ((str = clicon_yang_main_dir(h)) != NULL) if (yang_spec_load_dir(h, str, yspec) < 0) goto done; + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index e6d7925a..ce994fee 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -446,6 +446,9 @@ main(int argc, char **argv) if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index e60a5d08..eadbcc1b 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -468,7 +468,9 @@ main(int argc, if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index e6234884..0819c7cb 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -645,7 +645,9 @@ main(int argc, if (yang_spec_load_dir(h, str, yspec) < 0) goto done; } - + /* Load clixon lib yang module */ + if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0) + goto done; /* Load yang module library, RFC7895 */ if (yang_modules_init(h) < 0) goto done; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 8f3cb4a5..58980589 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1132,27 +1132,23 @@ api_operations_get(clicon_handle h, if (use_xml) cprintf(cbx, ""); else - cprintf(cbx, "{\"operations\": "); + cprintf(cbx, "{\"operations\": {"); ymod = NULL; + i = 0; while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { namespace = yang_find_mynamespace(ymod); - yc = NULL; i=0; + yc = NULL; while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; if (use_xml) cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); else{ - if (i==0) - cprintf(cbx, "{"); - if (i) + if (i++) cprintf(cbx, ","); cprintf(cbx, "\"%s:%s\": null", ymod->ys_argument, yc->ys_argument); } - i++; } - if (!use_xml && i) - cprintf(cbx, "}"); } if (use_xml) cprintf(cbx, ""); diff --git a/example/example.yang b/example/example.yang index 749b10ff..f5e82063 100644 --- a/example/example.yang +++ b/example/example.yang @@ -128,13 +128,4 @@ module example { } } } - rpc debug { - description "Set debug level of backend. XXX should be in clixon-config"; - input { - leaf level { - type uint32; - } - } - } - } diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 0dbb76d1..b454b608 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -827,7 +827,7 @@ clicon_rpc_debug(clicon_handle h, username = clicon_username_get(h); /* XXX: hardcoded example yang, should be clixon-config!!! */ - if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/test/test_install.sh b/test/test_install.sh index 032bba4a..09867c42 100755 --- a/test/test_install.sh +++ b/test/test_install.sh @@ -12,19 +12,23 @@ if [ $? -ne 0 ]; then err fi -new "Check installed files" +new "Check installed files /usr" if [ ! -d $dir/usr ]; then err $dir/usr fi +new "Check installed files /www-data" if [ ! -d $dir/www-data ]; then err $dir/www-data fi +new "Check installed files clixon-config" if [ ! -f $dir/usr/local/share/clixon/clixon-config* ]; then err $dir/usr/local/share/clixon/clixon-config* fi +new "Check installed files libclixon.so" if [ ! -h $dir/usr/local/lib/libclixon.so ]; then err $dir/usr/local/lib/libclixon.so fi +new "Check installed files libclixon_backend.so" if [ ! -h $dir/usr/local/lib/libclixon_backend.so ]; then err $dir/usr/local/lib/libclixon_backend.so fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index e7451d0a..2a1fd961 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -118,12 +118,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r # Should be alphabetically ordered new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null}{"ietf-routing:fib-route": null,"ietf-routing:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"example:empty": null,"example:client-rpc": null,"ietf-routing:fib-route": null,"ietf-routing:route-count": null,"clixon-lib:debug": null} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect='' +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" diff --git a/yang/Makefile.in b/yang/Makefile.in index fb32b74d..a1d0be57 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -41,10 +41,12 @@ datarootdir = @datarootdir@ CLIXON_DATADIR = @CLIXON_DATADIR@ YANGSPECS = clixon-config@2018-10-21.yang +YANGSPECS += clixon-lib@2019-01-02.yang YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-yang-types@2013-07-15.yang +YANGSPECS += ietf-restconf@2017-01-26.yang YANGSPECS += ietf-restconf-monitoring@2017-01-26.yang YANGSPECS += ietf-netconf-notification@2008-07-01.yang YANGSPECS += ietf-yang-library@2016-06-21.yang diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index efa777e3..a9ad7414 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -1,6 +1,6 @@ module clixon-config { yang-version 1.1; - namespace "http://clicon.org"; + namespace "http://clicon.org/config"; prefix cc; organization @@ -455,12 +455,4 @@ module clixon-config { } } - rpc debug { - description "Set debug level of backend."; - input { - leaf level { - type uint32; - } - } - } } diff --git a/yang/clixon-lib@2019-01-02.yang b/yang/clixon-lib@2019-01-02.yang new file mode 100644 index 00000000..1e26187b --- /dev/null +++ b/yang/clixon-lib@2019-01-02.yang @@ -0,0 +1,55 @@ +module clixon-lib { + yang-version 1.1; + namespace "http://clicon.org/lib"; + prefix cl; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon Netconf extensions for communication between clients and backend. + + ***** BEGIN LICENSE BLOCK ***** + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON + + Licensed under the Apache License, Version 2.0 (the \"License\"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an \"AS IS\" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the \"GPL\"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK *****"; + + revision 2019-01-02 { + description + "Released in Clixon 3.9"; + } + rpc debug { + description "Set debug level of backend."; + input { + leaf level { + type uint32; + } + } + } +} diff --git a/yang/ietf-restconf@2017-01-26.yang b/yang/ietf-restconf@2017-01-26.yang new file mode 100644 index 00000000..249006af --- /dev/null +++ b/yang/ietf-restconf@2017-01-26.yang @@ -0,0 +1,279 @@ +module ietf-restconf { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-restconf"; + prefix "rc"; + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for basic RESTCONF media type definitions used in + RESTCONF protocol messages. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The 'restconf-media-type' YANG extension statement + provides a normative syntax for XML and JSON + message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8040; see + the RFC itself for full legal notices."; + + revision 2017-01-26 { + description + "Initial revision."; + reference + "RFC 8040: RESTCONF Protocol."; + } + + extension yang-data { + argument name { + yin-element true; + } + description + "This extension is used to specify a YANG data template that + represents conceptual data defined in YANG. It is + intended to describe hierarchical data independent of + protocol context or specific message-encoding format. + Data definition statements within a yang-data extension + specify the generic syntax for the specific YANG data + template, whose name is the argument of the 'yang-data' + extension statement. + + Note that this extension does not define a media type. + A specification using this extension MUST specify the + message-encoding rules, including the content media type. + + The mandatory 'name' parameter value identifies the YANG + data template that is being defined. It contains the + template name. + + This extension is ignored unless it appears as a top-level + statement. It MUST contain data definition statements + that result in exactly one container data node definition. + An instance of a YANG data template can thus be translated + into an XML instance document, whose top-level element + corresponds to the top-level container. + + The module name and namespace values for the YANG module using + the extension statement are assigned to instance document data + conforming to the data definition statements within + this extension. + + The substatements of this extension MUST follow the + 'data-def-stmt' rule in the YANG ABNF. + + The XPath document root is the extension statement itself, + such that the child nodes of the document root are + represented by the data-def-stmt substatements within + this extension. This conceptual document is the context + for the following YANG statements: + + - must-stmt + - when-stmt + - path-stmt + - min-elements-stmt + - max-elements-stmt + - mandatory-stmt + - unique-stmt + - ordered-by + - instance-identifier data type + + The following data-def-stmt substatements are constrained + when used within a 'yang-data' extension statement. + + - The list-stmt is not required to have a key-stmt defined. + - The if-feature-stmt is ignored if present. + - The config-stmt is ignored if present. + - The available identity values for any 'identityref' + leaf or leaf-list nodes are limited to the module + containing this extension statement and the modules + imported into that module. + "; + } + + rc:yang-data yang-errors { + uses errors; + } + + rc:yang-data yang-api { + uses restconf; + } + + grouping errors { + description + "A grouping that contains a YANG container + representing the syntax and semantics of a + YANG Patch error report within a response message."; + + container errors { + description + "Represents an error report returned by the server if + a request results in an error."; + + list error { + description + "An entry containing information about one + specific error that occurred while processing + a RESTCONF request."; + reference + "RFC 6241, Section 4.3."; + + leaf error-type { + type enumeration { + enum transport { + description + "The transport layer."; + } + enum rpc { + description + "The rpc or notification layer."; + } + enum protocol { + description + "The protocol operation layer."; + } + enum application { + description + "The server application layer."; + } + } + mandatory true; + description + "The protocol layer where the error occurred."; + } + + leaf error-tag { + type string; + mandatory true; + description + "The enumerated error-tag."; + } + + leaf error-app-tag { + type string; + description + "The application-specific error-tag."; + } + + leaf error-path { + type instance-identifier; + description + "The YANG instance identifier associated + with the error node."; + } + + leaf error-message { + type string; + description + "A message describing the error."; + } + + anydata error-info { + description + "This anydata value MUST represent a container with + zero or more data nodes representing additional + error information."; + } + } + } + } + + grouping restconf { + description + "Conceptual grouping representing the RESTCONF + root resource."; + + container restconf { + description + "Conceptual container representing the RESTCONF + root resource."; + container data { + description + "Container representing the datastore resource. + Represents the conceptual root of all state data + and configuration data supported by the server. + The child nodes of this container can be any data + resources that are defined as top-level data nodes + from the YANG modules advertised by the server in + the 'ietf-yang-library' module."; + } + + container operations { + description + "Container for all operation resources. + + Each resource is represented as an empty leaf with the + name of the RPC operation from the YANG 'rpc' statement. + + For example, the 'system-restart' RPC operation defined + in the 'ietf-system' module would be represented as + an empty leaf in the 'ietf-system' namespace. This is + a conceptual leaf and will not actually be found in + the module: + + module ietf-system { + leaf system-reset { + type empty; + } + } + + To invoke the 'system-restart' RPC operation: + + POST /restconf/operations/ietf-system:system-restart + + To discover the RPC operations supported by the server: + + GET /restconf/operations + + In XML, the YANG module namespace identifies the module: + + + + In JSON, the YANG module name identifies the module: + + { 'ietf-system:system-restart' : [null] } + "; + } + + leaf yang-library-version { + type string { + pattern '\d{4}-\d{2}-\d{2}'; + } + config false; + mandatory true; + description + "Identifies the revision date of the 'ietf-yang-library' + module that is implemented by this RESTCONF server. + Indicates the year, month, and day in YYYY-MM-DD + numeric format."; + } + } + } + +} From 058a14579f4969258de84c4f82d76fd3c1b6e1d7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 4 Jan 2019 11:37:26 +0100 Subject: [PATCH 24/72] Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. --- CHANGELOG.md | 2 + apps/cli/cli_generate.c | 1 + apps/netconf/netconf_main.c | 17 ++- apps/netconf/netconf_rpc.c | 15 +- datastore/text/clixon_xmldb_text.c | 35 ++++- lib/clixon/clixon_xml_sort.h | 2 +- lib/clixon/clixon_yang.h | 1 + lib/src/clixon_json_parse.y | 2 +- lib/src/clixon_xml_map.c | 106 ++++++++++----- lib/src/clixon_xml_sort.c | 56 +++++--- lib/src/clixon_xpath_parse.y | 2 +- lib/src/clixon_yang.c | 24 +++- lib/src/clixon_yang_parse.y | 2 +- test/test_choice.sh | 212 +++++++++++++++++++++++++++++ test/test_leafref.sh | 2 +- test/test_rpc.sh | 19 ++- 16 files changed, 412 insertions(+), 86 deletions(-) create mode 100755 test/test_choice.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 588c5e10..58ca127d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. ### API changes on existing features (you may need to change your code) +* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. * CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. @@ -94,6 +95,7 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. * Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 8c87a0d7..b91a5242 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -768,6 +768,7 @@ yang2cli_stmt(clicon_handle h, if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) goto done; break; + case Y_CASE: case Y_SUBMODULE: case Y_MODULE: for (i=0; iys_len; i++) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index eadbcc1b..7e1163dc 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -80,7 +80,7 @@ * @param[in] cb Packet buffer */ static int -process_incoming_packet(clicon_handle h, +netconf_input_packet(clicon_handle h, cbuf *cb) { int retval = -1; @@ -94,6 +94,8 @@ process_incoming_packet(clicon_handle h, cxobj *xc; yang_spec *yspec; int ret; + cxobj *xa; + cxobj *xa2; clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); @@ -110,7 +112,7 @@ process_incoming_packet(clicon_handle h, /* Parse incoming XML message */ if (xml_parse_string(str, yspec, &xreq) < 0){ free(str0); - if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0) goto done; netconf_output_encap(1, cbret, "rpc-error"); goto done; @@ -143,8 +145,13 @@ process_incoming_packet(clicon_handle h, goto done; } else{ /* there is a return message in xret */ - cxobj *xa, *xa2; - assert(xret); + + if (xret == NULL){ + if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0) + goto done; + netconf_output_encap(1, cbret, "rpc-error"); + goto done; + } if ((xc = xml_child_i(xret,0))!=NULL){ xa=NULL; /* Copy message-id attribute from incoming to reply. @@ -228,7 +235,7 @@ netconf_input_cb(int s, /* OK, we have an xml string from a client */ /* Remove trailer */ *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; - if (process_incoming_packet(h, cb) < 0) + if (netconf_input_packet(h, cb) < 0) ; //goto done; // ignore errors if (cc_closed) break; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 0770a0b9..21a02193 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -314,7 +314,6 @@ netconf_edit_config(clicon_handle h, enum operation_type operation = OP_MERGE; enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */ - cxobj *xc; /* config */ cxobj *x; cxobj *xfilter; char *ftype = NULL; @@ -366,18 +365,8 @@ netconf_edit_config(clicon_handle h, ""); goto ok; } - - /* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */ - if ((xc = xpath_first(xn, "config")) != NULL){ -#if 0 - /* application-specific code registers 'config' */ - if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){ - goto ok; - } -#endif - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - } + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; ok: retval = 0; done: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index c4d2d1b4..689e9a4b 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -715,8 +715,9 @@ text_modify(struct text_handle *th, if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ } - /* First pass: mark existing children in base */ - /* Loop through children of the modification tree */ + /* First pass: Loop through children of the x1 modification tree + * collect matching nodes from x0 in x0vec (no changes to x0 children) + */ if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; @@ -732,17 +733,29 @@ text_modify(struct text_handle *th, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; - x0vec[i++] = x0c; +#if 1 + if (x0c && (yc != xml_spec(x0c))){ + /* There is a match but is should be replaced (choice)*/ + if (xml_purge(x0c) < 0) + goto done; + x0c = NULL; + } +#endif + x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */ } - /* Second pass: modify tree */ + /* Second pass: Loop through children of the x1 modification tree again + * Now potentially modify x0:s children + * Here x0vec contains one-to-one matching nodes of x1:s children. + */ x1c = NULL; i = 0; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); + x0c = x0vec[i++]; yc = yang_find_datanode(y0, x1cname); - if ((ret = text_modify(th, x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret)) < 0) + if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (ret == 0) @@ -862,8 +875,16 @@ text_modify_top(struct text_handle *th, goto fail; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; +#if 1 + if (x0c && (yc != xml_spec(x0c))){ + /* There is a match but is should be replaced (choice)*/ + if (xml_purge(x0c) < 0) + goto done; + x0c = NULL; + } +#endif if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 83f94f44..8b1e6f82 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -48,6 +48,6 @@ int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword, int upper); cxobj *xml_match(cxobj *x0, char *name, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval); int xml_sort_verify(cxobj *x, void *arg); -int match_base_child(cxobj *x0, cxobj *x1c, cxobj **x0cp, yang_stmt *yc); +int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index fb727dfd..b0635385 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -264,6 +264,7 @@ yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); char *yang_find_myprefix(yang_stmt *ys); char *yang_find_mynamespace(yang_stmt *ys); +yang_node *yang_choice(yang_stmt *y); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index a6533a06..07da89a4 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -103,7 +103,7 @@ object. #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_json_parsetext, _JY->jy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 660c4849..4b51e30b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -189,13 +189,14 @@ xml2cli(FILE *f, if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); - body = xml_body(x); if (gt == GT_ALL || gt == GT_VARS) fprintf(f, "%s ", xml_name(x)); - if (index(body, ' ')) - fprintf(f, "\"%s\"", body); - else - fprintf(f, "%s", body); + if ((body = xml_body(x)) != NULL){ + if (index(body, ' ')) + fprintf(f, "\"%s\"", body); + else + fprintf(f, "%s", body); + } fprintf(f, "\n"); goto ok; } @@ -508,32 +509,56 @@ xml_yang_validate_add(cxobj *xt, int retval = -1; cg_var *cv = NULL; char *reason = NULL; - yang_stmt *yc; + yang_stmt *yt; /* yang spec of xt going in */ + yang_stmt *yc; /* yang spec of yt child */ + yang_stmt *yx; /* yang spec of xt children */ + yang_node *yp; /* yang spec of parent of yang spec of xt children */ int i; - yang_stmt *ys; char *body; int ret; cxobj *x; /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ - if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ - switch (ys->ys_keyword){ + if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + switch (yt->ys_keyword){ case Y_RPC: case Y_INPUT: case Y_LIST: /* fall thru */ case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = ys->ys_stmt[i]; - if (yc->ys_keyword != Y_LEAF) - continue; - if (yang_config(yc)==0) - continue; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; + for (i=0; iys_len; i++){ + yc = yt->ys_stmt[i]; + switch (yc->ys_keyword){ + case Y_LEAF: + if (yang_config(yc)==0) + break; + if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; + } + break; + case Y_CHOICE: + if (yang_mandatory(yc)){ + /* If there is one child with this choice as parent */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((yx = xml_spec(x)) != NULL && + (yp = yang_choice(yx)) != NULL && + yp == (yang_node*)yc){ + break; /* leave loop with x set */ + } + } + if (x == NULL){ /* No hit */ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0) + goto done; + goto fail; + } + } + break; + default: + break; } } break; @@ -541,7 +566,7 @@ xml_yang_validate_add(cxobj *xt, /* fall thru */ case Y_LEAF_LIST: /* validate value against ranges, etc */ - if ((cv = cv_dup(ys->ys_cv)) == NULL){ + if ((cv = cv_dup(yt->ys_cv)) == NULL){ clicon_err(OE_UNIX, errno, "cv_dup"); goto done; } @@ -550,12 +575,12 @@ xml_yang_validate_add(cxobj *xt, */ if ((body = xml_body(xt)) != NULL){ if (cv_parse1(body, cv, &reason) != 1){ - if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0) goto done; goto fail; } - if ((ys_cv_validate(cv, ys, &reason)) != 1){ - if (netconf_bad_element(cbret, "application", ys->ys_argument, reason) < 0) + if ((ys_cv_validate(cv, yt, &reason)) != 1){ + if (netconf_bad_element(cbret, "application", yt->ys_argument, reason) < 0) goto done; goto fail; } @@ -948,12 +973,12 @@ cvec2xml_1(cvec *cvv, } /*! Recursive help function to compute differences between two xml trees - * @param[in] x1 First XML tree - * @param[in] x2 Second XML tree + * @param[in] x1 First XML tree + * @param[in] x2 Second XML tree * @param[out] x1vec Pointervector to XML nodes existing in only first tree * @param[out] x1veclen Length of first vector - * @param[out] x2vec Pointervector to XML nodes existing in only second tree - * @param[out] x2veclen Length of x2vec vector + * @param[out] x2vec Pointervector to XML nodes existing in only second tree + * @param[out] x2veclen Length of x2vec vector * @param[out] changed_x1 Pointervector to XML nodes changed orig value * @param[out] changed_x2 Pointervector to XML nodes changed wanted value * @param[out] changedlen Length of changed vector @@ -988,13 +1013,21 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x1c)); goto done; } - if (match_base_child(x2, x1c, &x2c, yc) < 0) + if (match_base_child(x2, x1c, yc, &x2c) < 0) goto done; if (x2c == NULL){ if (cxvec_append(x1c, x1vec, x1veclen) < 0) goto done; } - else{ + else if (yang_choice(yc)){ + /* if x1c and x2c are choice/case, then they are changed */ + if (cxvec_append(x1c, changed_x1, changedlen) < 0) + goto done; + (*changedlen)--; /* append two vectors */ + if (cxvec_append(x2c, changed_x2, changedlen) < 0) + goto done; + } + else{ /* if x1c and x2c are leafs w bodies, then they are changed */ if (yc->ys_keyword == Y_LEAF){ if ((b1 = xml_body(x1c)) == NULL) /* empty type */ break; @@ -1024,7 +1057,7 @@ xml_diff1(yang_stmt *ys, clicon_err(OE_UNIX, errno, "Unknown element: %s", xml_name(x2c)); goto done; } - if (match_base_child(x1, x2c, &x1c, yc) < 0) + if (match_base_child(x1, x2c, yc, &x1c) < 0) goto done; if (x1c == NULL) if (cxvec_append(x2c, x2vec, x2veclen) < 0) @@ -1118,7 +1151,8 @@ yang2api_path_fmt_1(yang_stmt *ys, yp->yn_keyword != Y_SUBMODULE){ if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */ goto done; - cprintf(cb, "/"); + if (yp->yn_keyword != Y_CHOICE && yp->yn_keyword != Y_CASE) + cprintf(cb, "/"); } else /* top symbol - mark with name prefix */ cprintf(cb, "/%s:", yp->yn_argument); @@ -1128,14 +1162,12 @@ yang2api_path_fmt_1(yang_stmt *ys, cprintf(cb, "%s", ys->ys_argument); } else{ -#if 1 if (ys->ys_keyword == Y_LEAF && yp && yp->yn_keyword == Y_LIST){ if (yang_key_match(yp, ys->ys_argument) == 0) cprintf(cb, "%s", ys->ys_argument); /* Not if leaf and key */ } else -#endif if (ys->ys_keyword != Y_CHOICE && ys->ys_keyword != Y_CASE) cprintf(cb, "%s", ys->ys_argument); } @@ -2202,7 +2234,7 @@ xml_merge1(cxobj *x0, } /* See if there is a corresponding node in the base tree */ x0c = NULL; - if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) + if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) goto done; if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; @@ -2284,8 +2316,12 @@ xml_merge(cxobj *x0, break; } /* See if there is a corresponding node in the base tree */ - if (match_base_child(x0, x1c, &x0c, yc) < 0) + if (match_base_child(x0, x1c, yc, &x0c) < 0) goto done; + /* There is a case where x0c and x1c are choice nodes, if so, + * it is treated as a match, and x0c will remain + * If it is overwritten, then x0c should be removed here. + */ if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; if (*reason != NULL) diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 7f08b35e..fb5e412b 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -75,6 +75,8 @@ * @param[in] xp XML parent, can be NULL. * @param[in] yspec Yang specification (top level) * @param[out] yresult Pointer to yang stmt of result, or NULL, if not found + * @retval 0 OK + * @retval -1 Error * @note special rule for rpc, ie ,look for top "foo" node. * @note works for import prefix, but not work for generic XML parsing where * xmlns and xmlns:ns are used. @@ -555,22 +557,18 @@ xml_sort_verify(cxobj *x0, } /*! Given child tree x1c, find matching child in base tree x0 and return as x0cp - * param[in] x0 Base tree node - * param[in] x1c Modification tree child - * param[in] yc Yang spec of tree child - * param[out] x0cp Matching base tree child (if any) - * @note XXX: room for optimization? on 1K calls we have 1M body calls and - 500K xml_child_each/cvec_each calls. - The outer loop is large for large lists - The inner loop is small - Major time in xml_find_body() - Can one do a binary search in the x0 list? -*/ + * @param[in] x0 Base tree node + * @param[in] x1c Modification tree child + * @param[in] yc Yang spec of tree child + * @param[out] x0cp Matching base tree child (if any) + * @retval 0 OK + * @retval -1 Error + */ int -match_base_child(cxobj *x0, - cxobj *x1c, - cxobj **x0cp, - yang_stmt *yc) +match_base_child(cxobj *x0, + cxobj *x1c, + yang_stmt *yc, + cxobj **x0cp) { int retval = -1; cvec *cvk = NULL; /* vector of index keys */ @@ -582,8 +580,29 @@ match_base_child(cxobj *x0, char **keyvec = NULL; int i; int yorder; + cxobj *x0c = NULL; + yang_stmt *y0c; + yang_node *y0p; + yang_node *yp; /* yang parent */ - *x0cp = NULL; /* return value */ + *x0cp = NULL; /* init return value */ +#if 1 + /* Special case is if yc parent (yp) is choice/case + * then find x0 child with same yc even though it does not match lexically + * However this will give another y0c != yc + */ + if ((yp = yang_choice(yc)) != NULL){ + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { + if ((y0c = xml_spec(x0c)) != NULL && + (y0p = yang_choice(y0c)) != NULL && + y0p == yp) + break; /* x0c will have a value */ + } + *x0cp = x0c; + goto ok; /* What to do if not found? */ + } +#endif switch (yc->ys_keyword){ case Y_CONTAINER: /* Equal regardless */ case Y_LEAF: /* Equal regardless */ @@ -629,11 +648,12 @@ match_base_child(cxobj *x0, /* Get match. Sorting mode(optimized) or not?*/ if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){ yorder = yang_order(yc); - *x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); + x0c = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval); } else{ - *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); + x0c = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); } + *x0cp = x0c; ok: retval = 0; done: diff --git a/lib/src/clixon_xpath_parse.y b/lib/src/clixon_xpath_parse.y index b121cc2c..db672129 100644 --- a/lib/src/clixon_xpath_parse.y +++ b/lib/src/clixon_xpath_parse.y @@ -95,7 +95,7 @@ #define _YYERROR(msg) {clicon_err(OE_XML, 0, "YYERROR %s '%s' %d", (msg), clixon_xpath_parsetext, _XY->xy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 36404682..b618f037 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -624,6 +624,28 @@ yang_find_mynamespace(yang_stmt *ys) return namespace; } +/*! If a given yang stmt has a choice/case as parent, return the choice statement + */ +yang_node * +yang_choice(yang_stmt *y) +{ + yang_node *yp; + + if ((yp = y->ys_parent) != NULL){ + switch (yp->yn_keyword){ + case Y_CHOICE: + return yp; + break; + case Y_CASE: + return yp->yn_parent; + break; + default: + break; + } + } + return NULL; +} + /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -2685,7 +2707,7 @@ yang_mandatory(yang_stmt *ys) { yang_stmt *ym; - if (ys->ys_keyword != Y_LEAF) + if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE) return 0; if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ if (ym->ys_cv == NULL) /* shouldnt happen */ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 9ffac45e..aef0cc07 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -165,7 +165,7 @@ #define _YYERROR(msg) {clicon_debug(2, "YYERROR %s '%s' %d", (msg), clixon_yang_parsetext, _YY->yy_linenum); YYERROR;} -/* add _yy to error paramaters */ +/* add _yy to error parameters */ #define YY_(msgid) msgid #include "clixon_config.h" diff --git a/test/test_choice.sh b/test/test_choice.sh new file mode 100755 index 00000000..c2b2f91f --- /dev/null +++ b/test/test_choice.sh @@ -0,0 +1,212 @@ +#!/bin/bash +# Choice type and mandatory +# Example from RFC7950 Sec 7.9.6 +# Also test mandatory behaviour as in 7.6.5 +# (XXX Would need default test in 7.6.4) +# Use-case: The ietf-netconf edit-config has a shorthand version of choice w mandatory: +# container { choice target { mandatory; leaf candidate; leaf running; }} + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/choice.xml +fyang=$dir/type.yang + +cat < $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + system + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + false + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +cat < $fyang +module system{ + yang-version 1.1; + namespace "urn:example:config"; + prefix ex; + container system{ + /* From RFC 7950 7.9.6 */ + container protocol { + presence true; + choice name { + case a { + leaf udp { + type empty; + } + } + case b { + leaf tcp { + type empty; + } + } + } + } + /* Same but shorthand */ + container shorthand { + presence true; + choice name { + leaf udp { + type empty; + } + leaf tcp { + type empty; + } + } + } + /* Same with mandatory true */ + container mandatory { + presence true; + choice name { + mandatory true; + case a { + leaf udp { + type empty; + } + } + case b { + leaf tcp { + type empty; + } + } + } + } + } +} +EOF + +new "test params: -f $cfg -y $fyang" + +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 -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG" -s /bin/sh www-data & + +# First vanilla (protocol) case +new "netconf validate empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf set empty protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf validate protocol" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf set protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf commit protocol udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "restconf set protocol tcp" +expecteq "$(curl -s -X PUT http://localhost/restconf/data/system:system/protocol -d {\"system:protocol\":{\"tcp\":null}})" "" + +new2 "restconf get protocol tcp" +expecteq "$(curl -s -X GET http://localhost/restconf/data/system:system)" '{"system:system": {"protocol": {"tcp": null}}} + ' + +new "cli set protocol udp" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set system protocol udp" 0 "^$" + +new "cli get protocol udp" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o show configuration cli " 0 "^system protocol udp$" + +new "cli delete all" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o delete all" 0 "^$" + +new "commit" +expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o commit" 0 "^$" + +# Second shorthand (no case) +new "netconf set shorthand tcp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf changing from TCP to UDP (RFC7950 7.9.6)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' '^]]>]]>$' + +new "netconf get shorthand udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate shorthand" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +# Third check mandatory +new "netconf set empty mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory empty" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory choice]]>]]>$' + +new "netconf set mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" + +new "netconf get mandatory udp" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' + +new "netconf validate mandatory" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +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 +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 63c6a270..4bb83b5c 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,7 +88,7 @@ if [ $BE -ne 0 ]; then fi new "leafref base config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.1
192.0.2.2
loex:lo
127.0.0.1
]]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'eth0ex:eth
192.0.2.124
192.0.2.224
loex:lo
127.0.0.132
]]>]]>' '^]]>]]>$' new "leafref get config" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^eth0' diff --git a/test/test_rpc.sh b/test/test_rpc.sh index f033dfb0..45b357f1 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -115,13 +115,28 @@ expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0","extra":"0"}}' http:// new2 "restconf wrong method" expecteq "$(curl -s -X POST -d '{"example:input":{"x":"0"}}' http://localhost/restconf/operations/example:wrong)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "missing-element","error-info": {"bad-element": "wrong"},"error-severity": "error","error-message": "RPC not defined"}}} ' -new2 "restconf edit-config missing mandatory" +new2 "restconf example missing input" expecteq "$(curl -s -X POST -d '{"example:input":null}' http://localhost/restconf/operations/ietf-netconf:edit-config)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "operation-failed","error-severity": "error","error-message": "yang module not found"}}} ' + new "netconf kill-session missing session-id mandatory" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementsession-iderrorMandatory variable]]>]]>$' -# edit-config? +new "netconf edit-config ok" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^]]>]]>$" + +new "netconf edit-config extra arg should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerror]]>]]>$" + +new "netconf edit-config empty target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementconfig-targeterrorMandatory choice]]>]]> +$' + +new "netconf edit-config missing target should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^missing-elementprotocolerrortarget]]>]]>$" + +new "netconf edit-config missing config should fail" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'applicationmissing-elementedit-contenterrorMandatory choice]]>]]>$' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" From 5b6af82e2989107559d020437c8f36d0880f1c81 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 5 Jan 2019 11:08:16 +0100 Subject: [PATCH 25/72] Validation of mandatory choice and recursive mandatory containers. --- CHANGELOG.md | 17 +++-- apps/backend/backend_client.c | 5 +- apps/backend/backend_commit.c | 4 +- apps/netconf/netconf_main.c | 4 +- apps/restconf/README.md | 10 +-- example/example_cli.c | 2 +- lib/src/clixon_xml_map.c | 132 ++++++++++++++++++++++------------ lib/src/clixon_yang.c | 52 +++++++++++--- test/test_choice.sh | 2 +- test/test_nacm_ext.sh | 4 +- test/test_nacm_protocol.sh | 5 +- test/test_openconfig.sh | 34 ++++++--- test/test_restconf.sh | 2 +- test/test_rpc.sh | 7 +- 14 files changed, 186 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ca127d..36269387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 3.9.0 (Preliminary Target: Mid-January 2019) ### Planned new features -* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) +* [Roadmap](ROADMAP.md) ### Major New features * Correct XML namespace handling @@ -56,11 +56,6 @@ ``` * To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false. * See https://github.com/clicon/clixon/issues/49 -* NACM extension (RFC8341) - * NACM module support (RFC8341 A1+A2) - * Recovery user "_nacm_recovery" added. - * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. - * Example user changed adm1 to andy to comply with RFC8341 example * Yang code upgrade (RFC7950) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 @@ -70,13 +65,18 @@ * Openconfig yang specs parsed: https://github.com/openconfig/public * Improved "unknown" handling * More precise Yang validation and better error messages - * Example: adding bad-, missing-, or unknown-element error messages, etc instead of operation-failed, bad-element instead of "yang node not found", etc. - * + * Example: adding bad-, missing-, or unknown-element error messages, instead of operation-failed. + * Validation of mandatory choice and recursive mandatory containers * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. +* NACM extension (RFC8341) + * NACM module support (RFC8341 A1+A2) + * Recovery user "_nacm_recovery" added. + * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. + * Example user changed adm1 to andy to comply with RFC8341 example ### API changes on existing features (you may need to change your code) * Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. @@ -95,7 +95,6 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes -* Yang choice functionality improved and stricter validation for CLI generation, mandatory flags, etc. * Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC. * Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK. * This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath` diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index a141f09f..2bbfbf85 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -430,8 +430,9 @@ from_client_edit_config(clicon_handle h, goto done; } if ((target = netconf_db_find(xn, "target")) == NULL){ - clicon_err(OE_XML, 0, "db not found"); - goto done; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index d7c2d6c3..8c406d69 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -117,8 +117,8 @@ generic_validate(yang_spec *yspec, for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); - if (ys && yang_mandatory(ys)){ - if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Removed mandatory variable") < 0) + if (ys && yang_mandatory(ys) && yang_config(ys)==0){ + if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Missing mandatory variable") < 0) goto done; goto fail; } diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 7e1163dc..19ec4ee6 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -97,8 +97,8 @@ netconf_input_packet(clicon_handle h, cxobj *xa; cxobj *xa2; - clicon_debug(1, "RECV"); - clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); + clicon_debug(1, "%s", __FUNCTION__); + clicon_debug(2, "%s: \"%s\"", __FUNCTION__, cbuf_get(cb)); if ((cbret = cbuf_new()) == NULL){ clicon_err(LOG_ERR, errno, "cbuf_new"); goto done; diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 2295e3fa..e951e18a 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -2,10 +2,10 @@ * [Installation](#installation) * [Streams](#streams) - * [Nchan Streams](#nchan-streams) + * [Nchan Streams](#nchan) * [Debugging](#debugging) -## 1. Installation +## Installation The examples are based on Nginx. Other reverse proxies should work but are not verified. @@ -76,7 +76,7 @@ Example of writing a new interfaces specification: curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}' ``` -## 2. Streams +## Streams Clixon have two experimental restconf event stream implementations following RFC8040 Section 6 using SSE. One native and one using Nginx @@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing See (stream tests)[../test/test_streams.sh] for more examples. -## 3. Nchan +## Nchan As an alternative streams implementation, Nginx/Nchan can be used. Nginx uses pub/sub channels and can be configured in a variety of @@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. -## 4. Debugging +## Debugging Start the restconf fastcgi program with debug flag: ``` diff --git a/example/example_cli.c b/example/example_cli.c index bf24064d..ebd2305a 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h, /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", + if (xml_parse_va(&xtop, NULL, "%sipv4", clicon_username_get(h), cv_string_get(instance)) < 0) goto done; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 4b51e30b..faac0666 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -482,6 +482,89 @@ xml_yang_validate_rpc(cxobj *xrpc, goto done; } +/*! Check if an xml node lacks mandatory children + * @param[in] xt XML node to be validated + * @param[in] yt xt:s yang statement + * @param[out] cbret Error buffer (set w netconf error if retval == 0) + * @retval 1 Validation OK + * @retval 0 Validation failed (cbret set) + * @retval -1 Error + */ +static int +check_mandatory(cxobj *xt, + yang_stmt *yt, + cbuf *cbret) +{ + int retval = -1; + int i; + cxobj *x; + yang_stmt *y; + yang_stmt *yc; + yang_node *yp; + + for (i=0; iys_len; i++){ + yc = yt->ys_stmt[i]; + if (!yang_mandatory(yc)) + continue; + switch (yc->ys_keyword){ + case Y_CONTAINER: + case Y_ANYDATA: + case Y_ANYXML: + case Y_LEAF: + if (yang_config(yc)==0) + break; + /* Find a child with the mandatory yang */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL + && y==yc) + break; /* got it */ + } + if (x == NULL){ + if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) + goto done; + goto fail; + } + break; + case Y_CHOICE: /* More complex because of choice/case structure */ + x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if ((y = xml_spec(x)) != NULL && + (yp = yang_choice(y)) != NULL && + yp == (yang_node*)yc){ + break; /* leave loop with x set */ + } + } + if (x == NULL){ + /* @see RFC7950: 15.6 Error Message for Data That Violates + * a Mandatory "choice" Statement */ + if (cprintf(cbret, "" + "application" + "data-missing" + "missing-choice" +#ifdef NYI + // "" +#endif + "%s" + "error" + "", + yc->ys_argument) <0) + goto done; + goto fail; + } + break; + default: + break; + } /* switch */ + } + 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. @@ -510,10 +593,6 @@ xml_yang_validate_add(cxobj *xt, cg_var *cv = NULL; char *reason = NULL; yang_stmt *yt; /* yang spec of xt going in */ - yang_stmt *yc; /* yang spec of yt child */ - yang_stmt *yx; /* yang spec of xt children */ - yang_node *yp; /* yang spec of parent of yang spec of xt children */ - int i; char *body; int ret; cxobj *x; @@ -521,47 +600,12 @@ xml_yang_validate_add(cxobj *xt, /* if not given by argument (overide) use default link and !Node has a config sub-statement and it is false */ if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){ + if ((ret = check_mandatory(xt, yt, cbret)) < 0) + goto done; + if (ret == 0) + goto fail; + /* Check leaf values */ switch (yt->ys_keyword){ - case Y_RPC: - case Y_INPUT: - case Y_LIST: - /* fall thru */ - case Y_CONTAINER: - for (i=0; iys_len; i++){ - yc = yt->ys_stmt[i]; - switch (yc->ys_keyword){ - case Y_LEAF: - if (yang_config(yc)==0) - break; - if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory variable") < 0) - goto done; - goto fail; - } - break; - case Y_CHOICE: - if (yang_mandatory(yc)){ - /* If there is one child with this choice as parent */ - x = NULL; - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { - if ((yx = xml_spec(x)) != NULL && - (yp = yang_choice(yx)) != NULL && - yp == (yang_node*)yc){ - break; /* leave loop with x set */ - } - } - if (x == NULL){ /* No hit */ - if (netconf_missing_element(cbret, "application", yc->ys_argument, "Mandatory choice") < 0) - goto done; - goto fail; - } - } - break; - default: - break; - } - } - break; case Y_LEAF: /* fall thru */ case Y_LEAF_LIST: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index b618f037..7a81bc6b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2701,26 +2701,62 @@ ys_parse_sub(yang_stmt *ys, * Note: one can cache this value in ys_cvec instead of functionally evaluating it. * @retval 1 yang statement is leaf and it has a mandatory sub-stmt with value true * @retval 0 The negation of conditions for return value 1. + * @see RFC7950 Sec 3: + * o mandatory node: A mandatory node is one of: + * 1) A leaf, choice, anydata, or anyxml node with a "mandatory" + * statement with the value "true". + * 2) A list or leaf-list node with a "min-elements" statement with a + * value greater than zero. + * 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. */ int yang_mandatory(yang_stmt *ys) { yang_stmt *ym; + char *reason = NULL; + uint8_t min_elements; /* XXX change to 32 (need new cligen version) */ - if (ys->ys_keyword != Y_LEAF && ys->ys_keyword != Y_CHOICE) - return 0; - if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ - if (ym->ys_cv == NULL) /* shouldnt happen */ - return 0; - return cv_bool_get(ym->ys_cv); + /* 1) A leaf, choice, anydata, or anyxml node with a "mandatory" + * statement with the value "true". */ + if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_CHOICE || + ys->ys_keyword == Y_ANYDATA || ys->ys_keyword == Y_ANYXML){ + if ((ym = yang_find((yang_node*)ys, Y_MANDATORY, NULL)) != NULL){ + if (ym->ys_cv != NULL) /* shouldnt happen */ + return cv_bool_get(ym->ys_cv); + } + } + /* 2) A list or leaf-list node with a "min-elements" statement with a + * value greater than zero. */ + else if (ys->ys_keyword == Y_LIST || ys->ys_keyword == Y_LEAF_LIST){ + if ((ym = yang_find((yang_node*)ys, Y_MIN_ELEMENTS, NULL)) != NULL){ + /* XXX change to 32 (need new cligen version) */ + if (parse_uint8(ym->ys_argument, &min_elements, &reason) != 1){ + clicon_err(OE_YANG, EINVAL, "%s", reason?reason:"parse_uint8"); + return 0; /* XXX ignore error */ + } + return min_elements > 0; + } + } + /* 3) A container node without a "presence" statement and that has at + * least one mandatory node as a child. */ + else if (ys->ys_keyword == Y_CONTAINER && + yang_find((yang_node*)ys, Y_PRESENCE, NULL) == NULL){ + yang_stmt *yc; + int i; + for (i=0; iys_len; i++){ + yc = ys->ys_stmt[i]; + if (yang_mandatory(yc)) + return 1; + } } return 0; } /*! Return config state of this node * config statement is default true. - * Note that a node with config=false may not have a sub - * statement where config=true. And this function does not check the sttaus of a parent. + * @note a node with config=false may not have a sub statement where + * config=true. And this function does not check the status of a parent. * @retval 0 if node has a config sub-statement and it is false * @retval 1 node has not config sub-statement or it is true */ diff --git a/test/test_choice.sh b/test/test_choice.sh index c2b2f91f..1289a0f5 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -179,7 +179,7 @@ new "netconf get mandatory empty" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^]]>]]>$' new "netconf validate mandatory" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationmissing-elementnameerrorMandatory choice]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationdata-missingmissing-choicenameerror]]>]]>$' new "netconf set mandatory udp" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 ']]>]]>' "^]]>]]>$" diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index b9cef0e1..edbef77f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -137,8 +137,8 @@ EOF new "test params: -f $cfg -y $fyang" if [ $BE -ne 0 ]; then - new "kill old backend -zf $cfg -y $fyang" - sudo clixon_backend -zf $cfg -y $fyang + new "kill old backend -zf $cfg " + sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index b4e5bc2f..8a3c298e 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -21,7 +21,7 @@ # automatically committed to running immediately after each successful # edit. # Which means that restconf -X DELETE /data translates to edit-config + commit -# WHICH IS allowed. +# which is allowed. APPNAME=example # include err() and new() functions and creates $dir @@ -65,7 +65,6 @@ module $APPNAME{ } } EOF - # The groups are slightly modified from RFC8341 A.1 # The rule-list is from A.2 RULES=$(cat < $cfg EOF files=$(find $OPENCONFIG -name "*.yang") -# Just cound nr of modules (exclude submodule) -let m=0; # Nr of modules +# Count nr of modules (exclude submodule) Assume "module" or "submodule" +# first word on first line +let ms=0; # Nr of modules +let ss=0; # Nr of smodules for f in $files; do - if [ -n "$(head -1 $f|grep '^module')" ]; then + let m=0; # Nr of modules + let s=0; # Nr of modules + if [ -n "$(head -15 $f|grep '^[ ]*module')" ]; then let m++; + let ms++; + elif [ -n "$(head -15 $f|grep '^[ ]*submodule')" ]; then + let s++; + let ss++; + else + echo "No module or submodule found $f" + exit + fi + if [ $m -eq 1 -a $s -eq 1 ]; then + echo "Double match $f" + exit fi done +echo "m:$ms s:$ss" new "Openconfig test: $clixon_cli -1f $cfg -y $f show version ($m modules)" for f in $files; do if [ -n "$(head -1 $f|grep '^module')" ]; then - new "cli $f" + new "$clixon_cli -1f $cfg -y $f show version" expectfn "$clixon_cli -1f $cfg -y $f show version" 0 "3." fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 2a1fd961..66710e91 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -293,7 +293,7 @@ new2 "restconf rpc missing input" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ietf-routing:fib-route)" '{"ietf-restconf:errors" : {"error": {"error-type": "rpc","error-tag": "malformed-message","error-severity": "error","error-message": "restconf RPC does not have input statement"}}} ' new "restconf rpc using POST xml" -ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/ietf-routing:fib-route) +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"ietf-routing:input":{"routing-instance-name":"ipv4","destination-address":{"address-family":"ipv4"}}}' http://localhost/restconf/operations/ietf-routing:fib-route) expect='ipv42.3.4.5static' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then diff --git a/test/test_rpc.sh b/test/test_rpc.sh index 45b357f1..27fb74c8 100755 --- a/test/test_rpc.sh +++ b/test/test_rpc.sh @@ -129,14 +129,13 @@ new "netconf edit-config extra arg should fail" expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^applicationunknown-elementextraerror]]>]]>$" new "netconf edit-config empty target should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementconfig-targeterrorMandatory choice]]>]]> -$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationdata-missingmissing-choiceconfig-targeterror]]>]]>$' new "netconf edit-config missing target should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' "^missing-elementprotocolerrortarget]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationmissing-elementtargeterrorMandatory variable]]>]]>$' new "netconf edit-config missing config should fail" -expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' 'applicationmissing-elementedit-contenterrorMandatory choice]]>]]>$' +expecteof "$clixon_netconf -qf $cfg" 0 ']]>]]>' '^applicationdata-missingmissing-choiceedit-contenterror]]>]]>$' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" From 0502ca4218a3fcd188d3512f7a34e066d2163304 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 5 Jan 2019 14:16:56 +0100 Subject: [PATCH 26/72] Hand-crafted validation messages removed and replaced with generic validations. --- CHANGELOG.md | 4 +- apps/netconf/netconf_rpc.c | 390 +++---------------------------------- 2 files changed, 30 insertions(+), 364 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36269387..937eeac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,9 @@ ### API changes on existing features (you may need to change your code) * Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced. -* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently since setting it to false is obsolete. +* Many hand-crafted validation messages have been removed and replaced with generic validations, which may lead to changed rpc-error messages. +* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently. Unsorted XML lists leads to slower performance and old obsolete code can be removed. * Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file. - * For backward compatibility, load of startup and running set CLICON_XML_NS_STRICT to false temporarily. * Removed `delete-config` support for candidate db since it is not supported in RFC6241. * Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order. * Yang parser is stricter (see above) which may break parsing of existing yang specs. diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 21a02193..ba41648a 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -135,20 +135,10 @@ netconf_get_config(clicon_handle h, { cxobj *xfilter; /* filter */ int retval = -1; - char *source; char *ftype = NULL; cxobj *xfilterconf; cxobj *xconf; - if ((source = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "source" - ""); - goto ok; - } /* ie ... */ if ((xfilter = xpath_first(xn, "filter")) != NULL) ftype = xml_find_value(xfilter, "type"); @@ -187,7 +177,6 @@ netconf_get_config(clicon_handle h, "type" "
"); } - ok: /* netconf error is not fatal */ retval = 0; done: return retval; @@ -223,11 +212,9 @@ get_edit_opts(cxobj *xn, if ((optstr = xml_body(x)) != NULL){ if (strcmp(optstr, "test-then-set") == 0) *testopt = TEST_THEN_SET; - else - if (strcmp(optstr, "set") == 0) + else if (strcmp(optstr, "set") == 0) *testopt = SET; - else - if (strcmp(optstr, "test-only") == 0) + else if (strcmp(optstr, "test-only") == 0) *testopt = TEST_ONLY; else goto parerr; @@ -311,52 +298,18 @@ netconf_edit_config(clicon_handle h, { int retval = -1; int optret; - enum operation_type operation = OP_MERGE; enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */ - cxobj *x; - cxobj *xfilter; - char *ftype = NULL; - char *target; /* db */ - /* must have target, and it should be candidate */ - if ((target = netconf_get_target(xn, "target")) == NULL || - strcmp(target, "candidate")){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - /* CLICON addition, eg /> */ - if ((xfilter = xpath_first(xn, "filter")) != NULL) { - if ((ftype = xml_find_value(xfilter, "type")) != NULL) - if (strcmp(ftype,"restconf")){ - xml_parse_va(xret, NULL, "" - "invalid-value" - "protocol" - "error" - ""); - goto ok; - } - } - if ((x = xpath_first(xn, "default-operation")) != NULL){ - if (xml_operation(xml_body(x), &operation) < 0){ - xml_parse_va(xret, NULL, "" - "invalid-value" - "protocol" - "error" - ""); - goto ok; - } - } if ((optret = get_edit_opts(xn, &testopt, &erropt, xret)) < 0) goto done; if (optret == 0) /* error in opt parameters */ goto ok; - /* not supported opts */ + /* These constraints are clixon-specific since :validate should + * support all testopts, and erropts should be supported + * And therefore extends the validation + * (implement the features before removing these checks) + */ if (testopt!=TEST_THEN_SET || erropt!=STOP_ON_ERROR){ xml_parse_va(xret, NULL, "" "operation-not-supported" @@ -364,158 +317,15 @@ netconf_edit_config(clicon_handle h, "error" ""); goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Netconf copy configuration - - - - - - - - - - - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_copy_config(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *source; - char *target; /* filenames */ - - if ((source = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "source" - ""); - goto ok; - } - if ((target = netconf_get_target(xn, "target")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Delete configuration - - - - - - Delete a configuration datastore. The - configuration datastore cannot be deleted. - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_delete_config(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - char *target; /* filenames */ - int retval = -1; - - if ((target = netconf_get_target(xn, "target")) == NULL || - strcmp(target, "running")==0){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - - -/*! Lock a database - - - - - - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_lock(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *target; - - if ((target = netconf_get_target(xn, "target")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; } if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) goto done; ok: retval = 0; - done: + done: return retval; } -/*! Unlock a database - - - - - - XXX - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_unlock(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - return netconf_lock(h, xn, xret); -} - /*! Get running configuration and device state information * * @@ -579,138 +389,11 @@ netconf_get(clicon_handle h, "type" "
"); } - // ok: /* netconf error is not fatal */ retval = 0; done: return retval; } - -/*! Close a (user) session - - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK -*/ -static int -netconf_close_session(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - cc_closed++; - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - -/*! Kill other user sessions - - PID - - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_kill_session(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval=-1; - cxobj *xs; - - if ((xs = xpath_first(xn, "//session-id")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "session-id" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} -/*! Check the semantic consistency of candidate - - :validate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_validate(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - char *target; - - if ((target = netconf_get_target(xn, "source")) == NULL){ - xml_parse_va(xret, NULL, "" - "missing-element" - "protocol" - "error" - "target" - ""); - goto ok; - } - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - ok: - retval = 0; - done: - return retval; -} - -/*! Commit candidate -> running - - :candidate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_commit(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - -/*! Discard all changes in candidate / revert to running - - :candidate - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at ... level. - * @param[out] xret Return XML, error or OK - */ -static int -netconf_discard_changes(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) - goto done; - retval = 0; - done: - return retval; -} - /*! Called when a notification has happened on backend * and this session has registered for that event. * Filter it and forward it. @@ -993,9 +676,25 @@ netconf_rpc_dispatch(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } + /* Many of these calls are now calling generic clicon_rpc_netconf_xml + * directly, since the validation is generic and done before this place + * in the call. Some call however need extra validation, such as the + * filter parameter to get/get-config and tes- err-opts of edit-config. + */ xe = NULL; while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { - if (strcmp(xml_name(xe), "get-config") == 0){ + if (strcmp(xml_name(xe), "copy-config") == 0 || + strcmp(xml_name(xe), "delete-config") == 0 || + strcmp(xml_name(xe), "lock") == 0 || + strcmp(xml_name(xe), "unlock") == 0 || + strcmp(xml_name(xe), "kill-session") == 0 || + strcmp(xml_name(xe), "validate") == 0 || /* :validate */ + strcmp(xml_name(xe), "commit") == 0 || /* :candidate */ + strcmp(xml_name(xe), "discard-changes") == 0){ + if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0) + goto done; + } + else if (strcmp(xml_name(xe), "get-config") == 0){ if (netconf_get_config(h, xe, xret) < 0) goto done; } @@ -1003,47 +702,14 @@ netconf_rpc_dispatch(clicon_handle h, if (netconf_edit_config(h, xe, xret) < 0) goto done; } - else if (strcmp(xml_name(xe), "copy-config") == 0){ - if (netconf_copy_config(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "delete-config") == 0){ - if (netconf_delete_config(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "lock") == 0) { - if (netconf_lock(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "unlock") == 0){ - if (netconf_unlock(h, xe, xret) < 0) - goto done; - } else if (strcmp(xml_name(xe), "get") == 0){ if (netconf_get(h, xe, xret) < 0) goto done; } else if (strcmp(xml_name(xe), "close-session") == 0){ - if (netconf_close_session(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "kill-session") == 0) { - if (netconf_kill_session(h, xe, xret) < 0) - goto done; - } - /* Validate capability :validate */ - else if (strcmp(xml_name(xe), "validate") == 0){ - if (netconf_validate(h, xe, xret) < 0) - goto done; - } - /* Candidate configuration capability :candidate */ - else if (strcmp(xml_name(xe), "commit") == 0){ - if (netconf_commit(h, xe, xret) < 0) - goto done; - } - else if (strcmp(xml_name(xe), "discard-changes") == 0){ - if (netconf_discard_changes(h, xe, xret) < 0) - goto done; + cc_closed++; + if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0) + goto done; } /* RFC 5277 :notification */ else if (strcmp(xml_name(xe), "create-subscription") == 0){ From bd67a2a5f264e106086533c071a8022ae6ad8f5b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 6 Jan 2019 16:31:36 +0100 Subject: [PATCH 27/72] Support for empty yang string added, eg `default "";` if-feature added in yang parser at several places. --- CHANGELOG.md | 1 + lib/src/clixon_yang_parse.l | 2 +- lib/src/clixon_yang_parse.y | 23 +++++++++++++++++------ test/lib.sh | 8 ++++++++ test/test_openconfig.sh | 23 +++++++++-------------- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937eeac7..d0728483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ * \{ { BEGIN(KEYWORD); return *yytext; } . { return *yytext; } -[0-9][0-9]* { clixon_yang_parselval.string = strdup(yytext); +\-?[0-9][0-9]* { clixon_yang_parselval.string = strdup(yytext); return INT; } ; { BEGIN(KEYWORD); return *yytext; } \{ { BEGIN(KEYWORD); return *yytext; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index aef0cc07..31fee4c8 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -641,7 +641,8 @@ identity_substmts : identity_substmts identity_substmt { clicon_debug(2,"identity-substmts -> identity-substmt"); } ; -identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } +identity_substmt : if_feature_stmt { clicon_debug(2,"identity-substmt -> if-feature-stmt"); } + | base_stmt { clicon_debug(2,"identity-substmt -> base-stmt"); } | status_stmt { clicon_debug(2,"identity-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"identity-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"identity-substmt -> reference-stmt"); } @@ -876,7 +877,8 @@ enum_substmts : enum_substmts enum_substmt { clicon_debug(2,"enum-substmts -> enum-substmt"); } ; -enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stmt"); } +enum_substmt : if_feature_stmt { clicon_debug(2,"enum-substmt -> if-feature-stmt"); } + | value_stmt { clicon_debug(2,"enum-substmt -> value-stmt"); } | status_stmt { clicon_debug(2,"enum-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"enum-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"enum-substmt -> reference-stmt"); } @@ -911,7 +913,8 @@ bit_substmts : bit_substmts bit_substmt { clicon_debug(2,"bit-substmts -> bit-substmt"); } ; -bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition-stmt"); } +bit_substmt : if_feature_stmt { clicon_debug(2,"bit-substmt -> if-feature-stmt"); } + | position_stmt { clicon_debug(2,"bit-substmt -> positition-stmt"); } | status_stmt { clicon_debug(2,"bit-substmt -> status-stmt"); } | description_stmt { clicon_debug(2,"bit-substmt -> description-stmt"); } | reference_stmt { clicon_debug(2,"bit-substmt -> reference-stmt"); } @@ -1318,9 +1321,16 @@ refine_substmts : refine_substmts refine_substmt { clicon_debug(2,"refine-substmts -> refine-substmt"); } ; -refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } - | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } +refine_substmt : if_feature_stmt { clicon_debug(2,"refine-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); } + | presence_stmt { clicon_debug(2,"refine-substmt -> presence-stmt"); } | default_stmt { clicon_debug(2,"refine-substmt -> default-stmt"); } + | config_stmt { clicon_debug(2,"refine-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"refine-substmt -> mandatory-stmt"); } + | min_elements_stmt { clicon_debug(2,"refine-substmt -> min-elements-stmt"); } + | max_elements_stmt { clicon_debug(2,"refine-substmt -> max-elements-stmt"); } + | description_stmt { clicon_debug(2,"refine-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"refine-substmt -> reference-stmt"); } | unknown_stmt { clicon_debug(2,"refine-substmt -> unknown-stmt");} | { clicon_debug(2,"refine-substmt -> "); } ; @@ -1561,7 +1571,8 @@ qstrings : qstrings '+' qstring { $$=$1; clicon_debug(2,"qstrings-> qstring"); } ; -qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"");} +qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"");} + | DQ DQ { $$=strdup(""); clicon_debug(2,"string-> \" \"");} | SQ ustring SQ { $$=$2; clicon_debug(2,"string-> ' ustring '"); } ; diff --git a/test/lib.sh b/test/lib.sh index 3d82e32d..be102a62 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -13,6 +13,14 @@ testname= # If set, enable debugging (of backend) : ${DBG:=0} +# Parse yangmodels from https://github.com/YangModels/yang +# Recommended: checkout yangmodels elsewhere in the tree and set the env +# to that +: ${YANGMODELS=$(pwd)/yang} + +# Parse yang openconfig models from https://github.com/openconfig/public +: ${OPENCONFIG=$(pwd)/public} + # For memcheck #clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" clixon_cli=clixon_cli diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index f59333e2..47574fa2 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -6,29 +6,24 @@ # - release/models/wifi/types/openconfig-wifi-types.yang # issue: https://github.com/clicon/clixon/issues/59 # -OPENCONFIG=public -OCDIR=$OPENCONFIG/release/models - -# Clone openconfig dir if not there -if [ ! -d public ]; then - git clone https://github.com/openconfig/public -#else -# (cd public; -# #git pull -# ) -fi - -# include err() and new() functions and creates $dir -. ./lib.sh # Yang specifics: multi-keys and empty type APPNAME=example + # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang +new "openconfig" +if [ ! -d "$OPENCONFIG" ]; then + err "Hmm Openconfig dir does not seem to exist, try git clone https://github.com/openconfig/public?" +fi + +OCDIR=$OPENCONFIG/release/models + + cat < $cfg $cfg From c7e847cd247f1324b6f566f8d546872209cd2fc9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 6 Jan 2019 22:11:59 +0100 Subject: [PATCH 28/72] Keyword "min" (not only "max") can be used in built-in types "range" and "length" statements. --- .gitignore | 2 +- CHANGELOG.md | 1 + README.md | 3 +- apps/cli/cli_generate.c | 95 +++------ lib/clixon/clixon_string.h | 1 + lib/clixon/clixon_yang.h | 3 +- lib/clixon/clixon_yang_type.h | 19 +- lib/src/clixon_string.c | 42 +++- lib/src/clixon_xml_map.c | 2 +- lib/src/clixon_yang.c | 210 +++++++++++++------- lib/src/clixon_yang_type.c | 353 +++++++++++++++------------------- test/README.md | 4 +- test/test_openconfig.sh | 3 - test/test_type.sh | 136 +++++++++---- 14 files changed, 469 insertions(+), 405 deletions(-) diff --git a/.gitignore b/.gitignore index c04c1244..1d8e315b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,5 @@ build-root/*.tar.xz build-root/*.rpm build-root/rpmbuild -test/public +test/site.sh doc/html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d0728483..b64cdd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ * Fix the original error in cvtype_max2str - * by adding a fraction_digits argument. - */ -static char * -cvtype_max2str_dup2(enum cv_type type, - int fraction_digits) -{ - int len; - char *str; - - if (type!=CGV_DEC64 || fraction_digits==0) - return cvtype_max2str_dup(type); - if ((len = cvtype_max2str(type, NULL, 0)) < 0) - return NULL; - if ((str = (char *)malloc(len+1)) == NULL) - return NULL; - memset(str, '\0', len+1); - len = snprintf(str, len+1, "%" PRId64 ".0", (INT64_MAX/((int)pow(10,fraction_digits)))); - return str; -} - /*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type * Check for completion (of already existent values), ranges (eg range[min:max]) and * patterns, (eg regexp:"[0.9]*"). @@ -208,18 +186,17 @@ yang2cli_var_sub(clicon_handle h, char *helptext, enum cv_type cvtype, int options, - cg_var *mincv, - cg_var *maxcv, + cvec *cvv, char *pattern, uint8_t fraction_digits ) { int retval = -1; char *type; - char *r; yang_stmt *yi = NULL; int i = 0; char *cvtypestr; + cg_var *cv; if (cvtype == CGV_VOID){ retval = 0; @@ -276,44 +253,30 @@ yang2cli_var_sub(clicon_handle h, if (options & YANG_OPTIONS_FRACTION_DIGITS) cprintf(cb, " fraction-digits:%u", fraction_digits); + if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){ - assert(mincv || maxcv); - cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); - if (mincv){ - if ((r = cv2str_dup(mincv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv2str_dup"); - goto done; - } - cprintf(cb, "%s:", r); - free(r); - r = NULL; - } - if (maxcv != NULL){ - if ((r = cv2str_dup(maxcv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv2str_dup"); - goto done; - } - } - else{ /* Cligen does not have 'max' keyword in range so need to find actual - max value of type if yang range expression is 0..max - */ - if (cvtype==CGV_STRING){ - if ((r = malloc(512)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - snprintf(r, 512, "%d", MAXPATHLEN); - } - else { - if ((r = cvtype_max2str_dup2(cvtype, fraction_digits)) == NULL){ - clicon_err(OE_UNIX, errno, "cvtype_max2str"); - goto done; + /* Loop through range_min and range_min..rang_max */ + i = 0; + while (iys_argument); + cv = cvec_i(cvv, i++); + if (strcmp(cv_name_get(cv),"range_min") == 0){ + cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); + cv2cbuf(cv, cb); + cprintf(cb,":"); + /* probe next */ + if (iys_argument:NULL; @@ -370,7 +332,7 @@ yang2cli_var_union_one(clicon_handle h, if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; if ((retval = yang2cli_var_sub(h, ys, ytype, cb, helptext, cvtype, - options, mincv, maxcv, pattern, fraction_digits)) < 0) + options, cvv, pattern, fraction_digits)) < 0) goto done; } retval = 0; @@ -438,8 +400,7 @@ yang2cli_var(clicon_handle h, char *origtype; yang_stmt *yrestype; /* resolved type */ char *restype; /* resolved type */ - cg_var *mincv = NULL; - cg_var *maxcv = NULL; + cvec *cvv = NULL; char *pattern = NULL; uint8_t fraction_digits = 0; enum cv_type cvtype; @@ -448,7 +409,7 @@ yang2cli_var(clicon_handle h, char *type; if (yang_type_get(ys, &origtype, &yrestype, - &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) + &options, &cvv, &pattern, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; @@ -485,7 +446,7 @@ yang2cli_var(clicon_handle h, if (completionp) cprintf(cb, "("); if ((retval = yang2cli_var_sub(h, ys, yrestype, cb, helptext, cvtype, - options, mincv, maxcv, pattern, fraction_digits)) < 0) + options, cvv, pattern, fraction_digits)) < 0) goto done; if (completionp){ if (cli_expand_var_generate(h, ys, cvtype, cb, diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 1b2b20fc..5895401d 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -87,6 +87,7 @@ int uri_percent_decode(char *enc, char **str); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); int nodeid_split(char *nodeid, char **prefix, char **id); +char *clixon_trim(char *str); #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index b0635385..3842b225 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -176,8 +176,7 @@ typedef struct yang_stmt yang_stmt; /* forward */ */ struct yang_type_cache{ int yc_options; - cg_var *yc_mincv; - cg_var *yc_maxcv; + cvec *yc_cvv; /* range and length restriction */ char *yc_pattern; uint8_t yc_fraction; yang_stmt *yc_resolved; /* Resolved type object, can be NULL - note direct ptr */ diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 8fecdd5f..bbdfe79e 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -55,11 +55,11 @@ * Prototypes */ int yang_type_cache_set(yang_type_cache **ycache, - yang_stmt *resolved, int options, cg_var *mincv, - cg_var *maxcv, char *pattern, uint8_t fraction); -int yang_type_cache_get(yang_type_cache *ycache, - yang_stmt **resolved, int *options, cg_var **mincv, - cg_var **maxcv, char **pattern, uint8_t *fraction); + yang_stmt *resolved, int options, + cvec *cvv, char *pattern, uint8_t fraction); +int yang_type_cache_get(yang_type_cache *ycache, yang_stmt **resolved, + int *options, cvec **cvv, char **pattern, + uint8_t *fraction); int yang_type_cache_cp(yang_type_cache **ycnew, yang_type_cache *ycold); int yang_type_cache_free(yang_type_cache *ycache); int ys_resolve_type(yang_stmt *ys, void *arg); @@ -69,12 +69,11 @@ yang_stmt *yang_find_identity(yang_stmt *ys, char *identity); int ys_cv_validate(cg_var *cv, yang_stmt *ys, char **reason); int clicon_type2cv(char *type, char *rtype, enum cv_type *cvtype); int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype, - int *options, cg_var **mincv, cg_var **maxcv, char **pattern, + int *options, cvec **cvv, char **pattern, uint8_t *fraction_digits); -int yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, - yang_stmt **restype, int *options, - cg_var **mincv, cg_var **maxcv, - char **pattern, uint8_t *fraction); +int yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, + yang_stmt **restype, int *options, + cvec **cvv, char **pattern, uint8_t *fraction); #endif /* _CLIXON_YANG_TYPE_H_ */ diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index b3e5e588..733a1ca4 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -57,14 +57,15 @@ /*! Split string into a vector based on character delimiters. Using malloc * * The given string is split into a vector where the delimiter can be - * any of the characters in the specified delimiter string. + * _any_ of the characters in the specified delimiter string. * * The vector returned is one single memory block that must be freed * by the caller * * @code - * char **vec = NULL; - * int nvec; + * char **vec = NULL; + * char *v; + * int nvec; * if ((vec = clicon_strsep("/home/user/src/clixon", "/", &nvec)) == NULL) * err; * for (i=0; i [[a,"b"][c="d"] * kalle&c=d -> [[c="d"]] # Discard elements with no delim2 * XXX differentiate between error and null cvec. @@ -610,6 +614,26 @@ nodeid_split(char *nodeid, return retval; } +/*! Trim blanks from front and end of a string, return new string + * @param[in] str + * @retval s Pointer into existing str after trimming blanks + */ +char * +clixon_trim(char *str) +{ + char *s = str; + int i; + + while (strlen(s) && isblank(s[0])) + s++; + for (i=0; iys_argument, "enumeration")) goto done; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 7a81bc6b..66590a56 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1068,7 +1068,7 @@ ys_populate_leaf(yang_stmt *ys, yparent = ys->ys_parent; /* Find parent: list/container */ /* 1. Find type specification and set cv type accordingly */ - if (yang_type_get(ys, &type, &yrestype, &options, NULL, NULL, NULL, &fraction_digits) < 0) + if (yang_type_get(ys, &type, &yrestype, &options, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) /* This handles non-resolved also */ @@ -1133,12 +1133,62 @@ ys_populate_list(yang_stmt *ys, return 0; } -/*! Populate range and length statements +/*! Set range or length boundary for built-in yang types + * Help functions to range and length statements + */ +static int +bound_add(yang_stmt *ys, + enum cv_type cvtype, + char *name, + char *val, + int options, + uint8_t fraction_digits + ) +{ + int retval = -1; + cg_var *cv; + char *reason = NULL; + int ret = 1; + + if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ + clicon_err(OE_YANG, errno, "cvec_add"); + goto done; + } + if (cv_name_set(cv, name) == NULL){ + clicon_err(OE_YANG, errno, "cv_name_set(%s)", name); + goto done; + } + if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) + cv_dec64_n_set(cv, fraction_digits); + if (strcmp(val, "min") == 0) + cv_min_set(cv); + else if (strcmp(val, "max") == 0) + cv_max_set(cv); + else if ((ret = cv_parse1(val, cv, &reason)) < 0){ + clicon_err(OE_YANG, errno, "cv_parse1"); + goto done; + } + if (ret == 0){ /* parsing failed */ + clicon_err(OE_YANG, errno, "range statement %s: %s", val, reason); + free(reason); + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Populate string built-in range statement * * Create cvec variables "range_min" and "range_max". Assume parent is type. - * Actually: min..max [| min..max]* - * where min,max is integer or keywords 'min' or 'max. - * We only allow one range, ie not 1..2|4..5 + * Actually: bound[..bound] (| bound[..bound])* + * where bound is integer, decimal or keywords 'min' or 'max. + * RFC 7950 9.2.4: + * A range consists of an explicit value, or a lower-inclusive bound, + * two consecutive dots "..", and an upper-inclusive bound. Multiple + * values or ranges can be given, separated by "|". If multiple values + * or ranges are given, they all MUST be disjoint and MUST be in + * ascending order */ static int ys_populate_range(yang_stmt *ys, @@ -1152,11 +1202,11 @@ ys_populate_range(yang_stmt *ys, int options = 0x0; uint8_t fraction_digits; enum cv_type cvtype = CGV_ERR; - char *minstr = NULL; - char *maxstr; - cg_var *cv; - char *reason = NULL; - int cvret; + char **vec = NULL; + char *v; + char *v2; + int nvec; + int i; yparent = ys->ys_parent; /* Find parent: type */ if (yparent->yn_keyword != Y_TYPE){ @@ -1164,81 +1214,90 @@ ys_populate_range(yang_stmt *ys, goto done; } if (yang_type_resolve(ys, (yang_stmt*)yparent, &yrestype, - &options, NULL, NULL, NULL, &fraction_digits) < 0) + &options, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; origtype = yarg_id((yang_stmt*)yparent); /* This handles non-resolved also */ if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; - /* special case for strings, where limit is length, not a string */ - if (cvtype == CGV_STRING) - cvtype = CGV_UINT64; - if ((minstr = strdup(ys->ys_argument)) == NULL){ - clicon_err(OE_YANG, errno, "strdup"); + if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL) goto done; - } - if ((maxstr = strstr(minstr, "..")) != NULL){ - if (strlen(maxstr) < 2){ - clicon_err(OE_YANG, 0, "range statement: %s not on the form: ..", - ys->ys_argument); - goto done; - } - minstr[maxstr-minstr] = '\0'; - maxstr += 2; - /* minstr and maxstr need trimming */ - if (isblank(minstr[strlen(minstr)-1])) - minstr[strlen(minstr)-1] = '\0'; - if (isblank(maxstr[0])) - maxstr++; - if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_add"); - goto done; + for (i=0; iys_cvec, cvtype)) == NULL){ - clicon_err(OE_YANG, errno, "cvec_add"); - goto done; - } - if (cv_name_set(cv, "range_max") == NULL){ - clicon_err(OE_YANG, errno, "cv_name_set"); - goto done; - } - if (options & YANG_OPTIONS_FRACTION_DIGITS && cvtype == CGV_DEC64) - cv_dec64_n_set(cv, fraction_digits); - if ((cvret = cv_parse1(maxstr, cv, &reason)) < 0){ - clicon_err(OE_YANG, errno, "cv_parse1"); - goto done; - } - if (cvret == 0){ /* parsing failed */ - clicon_err(OE_YANG, errno, "range statement, max: %s", reason); - free(reason); - goto done; - } + if (v2) + if (bound_add(ys, cvtype, "range_max",v2, + options, fraction_digits) < 0) + goto done; } retval = 0; done: - if (minstr) - free(minstr); + if (vec) + free(vec); + return retval; +} + +/*! Populate integer built-in length statement + * + * Create cvec variables "range_min" and "range_max". Assume parent is type. + * Actually: len[..len] (| len[..len])* + * len is unsigned integer or keywords 'min' or 'max. + * RFC 7950 9.4.4 + * A length range consists of an explicit value, or a lower bound, two + * consecutive dots "..", and an upper bound. Multiple values or ranges + * can be given, separated by "|". Length-restricting values MUST NOT + * be negative. If multiple values or ranges are given, they all MUST + * be disjoint and MUST be in ascending order. + */ +static int +ys_populate_length(yang_stmt *ys, + void *arg) +{ + int retval = -1; + yang_node *yparent; /* type */ + enum cv_type cvtype = CGV_ERR; + char **vec = NULL; + char *v; + int nvec; + int i; + char *v2; + + yparent = ys->ys_parent; /* Find parent: type */ + if (yparent->yn_keyword != Y_TYPE){ + clicon_err(OE_YANG, 0, "parent should be type"); + goto done; + } + cvtype = CGV_UINT64; + if ((vec = clicon_strsep(ys->ys_argument, "|", &nvec)) == NULL) + goto done; + for (i=0; iyc_resolved = resolved; ycache->yc_options = options; - if (mincv && (ycache->yc_mincv = cv_dup(mincv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; - } - if (maxcv && (ycache->yc_maxcv = cv_dup(maxcv)) == NULL){ - clicon_err(OE_UNIX, errno, "cv_dup"); - goto done; + if (cvv){ + if ((ycache->yc_cvv = cvec_dup(cvv)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } } if (pattern && (ycache->yc_pattern = strdup(pattern)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -155,8 +152,7 @@ int yang_type_cache_get(yang_type_cache *ycache, yang_stmt **resolved, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { @@ -164,10 +160,8 @@ yang_type_cache_get(yang_type_cache *ycache, *resolved = ycache->yc_resolved; if (options) *options = ycache->yc_options; - if (mincv) - *mincv = ycache->yc_mincv; - if (maxcv) - *maxcv = ycache->yc_maxcv; + if (cvv) + *cvv = ycache->yc_cvv; if (pattern) *pattern = ycache->yc_pattern; if (fraction) @@ -181,14 +175,13 @@ yang_type_cache_cp(yang_type_cache **ycnew, { int retval = -1; int options; - cg_var *mincv; - cg_var *maxcv; + cvec *cvv; char *pattern; uint8_t fraction; yang_stmt *resolved; - yang_type_cache_get(ycold, &resolved, &options, &mincv, &maxcv, &pattern, &fraction); - if (yang_type_cache_set(ycnew, resolved, options, mincv, maxcv, pattern, fraction) < 0) + yang_type_cache_get(ycold, &resolved, &options, &cvv, &pattern, &fraction); + if (yang_type_cache_set(ycnew, resolved, options, cvv, pattern, fraction) < 0) goto done; retval = 0; done: @@ -198,10 +191,8 @@ yang_type_cache_cp(yang_type_cache **ycnew, int yang_type_cache_free(yang_type_cache *ycache) { - if (ycache->yc_mincv) - cv_free(ycache->yc_mincv); - if (ycache->yc_maxcv) - cv_free(ycache->yc_maxcv); + if (ycache->yc_cvv) + cvec_free(ycache->yc_cvv); if (ycache->yc_pattern) free(ycache->yc_pattern); free(ycache); @@ -220,8 +211,7 @@ ys_resolve_type(yang_stmt *ys, { int retval = -1; int options = 0x0; - cg_var *mincv = NULL; - cg_var *maxcv = NULL; + cvec *cvv = NULL; char *pattern = NULL; uint8_t fraction = 0; yang_stmt *resolved = NULL; @@ -231,12 +221,12 @@ ys_resolve_type(yang_stmt *ys, * Note that the resolved type could be ys itself. */ if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, - &options, &mincv, &maxcv, &pattern, &fraction) < 0) + &options, &cvv, &pattern, &fraction) < 0) goto done; /* Cache the resolve locally */ if (yang_type_cache_set(&ys->ys_typecache, - resolved, options, mincv, maxcv, pattern, fraction) < 0) + resolved, options, cvv, pattern, fraction) < 0) goto done; retval = 0; done: @@ -388,132 +378,121 @@ static int cv_validate1(cg_var *cv, enum cv_type cvtype, int options, - cg_var *range_min, - cg_var *range_max, + cvec *cvv, char *pattern, yang_stmt *yrestype, char *restype, char **reason) { int retval = 1; /* OK */ + cg_var *cv1; + cg_var *cv2; int retval2; yang_stmt *yi = NULL; - unsigned int u = 0; - int i = 0; char *str; int found; char **vec = NULL; int nvec; char *v; + uint64_t uu = 0; + int64_t ii = 0; + int i; + int ret; if (reason && *reason){ free(*reason); *reason = NULL; } + /* check options first for length and range */ + if ((options & YANG_OPTIONS_RANGE) != 0 || + (options & YANG_OPTIONS_LENGTH) != 0){ + i = 0; + while (iys_argument:NULL; @@ -664,7 +623,7 @@ ys_cv_validate_union_one(yang_stmt *ys, } if (retval == 0) goto done; - if ((retval = cv_validate1(cvt, cvtype, options, range_min, range_max, + if ((retval = cv_validate1(cvt, cvtype, options, cvv, pattern, yrt, restype, reason)) < 0) goto done; } @@ -736,8 +695,7 @@ ys_cv_validate(cg_var *cv, int retval = -1; cg_var *ycv; /* cv of yang-statement */ int options = 0; - cg_var *range_min = NULL; - cg_var *range_max = NULL; + cvec *cvv = NULL; char *pattern = NULL; enum cv_type cvtype; char *type; /* orig type */ @@ -756,8 +714,7 @@ ys_cv_validate(cg_var *cv, } ycv = ys->ys_cv; if (yang_type_get(ys, &type, &yrestype, - &options, &range_min, &range_max, &pattern, - &fraction) < 0) + &options, &cvv, &pattern, &fraction) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; if (clicon_type2cv(type, restype, &cvtype) < 0) @@ -782,7 +739,7 @@ ys_cv_validate(cg_var *cv, retval = retval2; /* invalid (0) with latest reason or valid 1 */ } else - if ((retval = cv_validate1(cv, cvtype, options, range_min, range_max, pattern, + if ((retval = cv_validate1(cv, cvtype, options, cvv, pattern, yrestype, restype, reason)) < 0) goto done; done: @@ -889,7 +846,17 @@ yang_find_identity(yang_stmt *ys, return yid; } -/* +/*! Resolve type restrictions, return contraining parameters + * @param[in] yrange Yang type range restriction if any + * @param[in] ylength Yang type length restriction if any + * @param[in] ypattern Yang type pattern restriction if any + * @param[in] yfraction Yang type fraction restriction if any + * @param[out] options Pointer to flags field of optional values. optional + * @param[out] cvv Pointer to cvec with min range or length. + * If options&YANG_OPTIONS_RANGE or YANG_OPTIONS_LENGTH + * @param[out] pattern Pointer to static string of yang string pattern. optional + * @param[out] fraction For decimal64, how many digits after period + * @retval 0 OK. */ static int resolve_restrictions(yang_stmt *yrange, @@ -897,19 +864,16 @@ resolve_restrictions(yang_stmt *yrange, yang_stmt *ypattern, yang_stmt *yfraction, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { - if (options && mincv && maxcv && yrange != NULL){ - *mincv = cvec_find(yrange->ys_cvec, "range_min"); - *maxcv = cvec_find(yrange->ys_cvec, "range_max"); + if (options && cvv && yrange != NULL){ + *cvv = yrange->ys_cvec; *options |= YANG_OPTIONS_RANGE; } - if (options && mincv && maxcv && ylength != NULL){ - *mincv = cvec_find(ylength->ys_cvec, "range_min"); /* XXX fel typ */ - *maxcv = cvec_find(ylength->ys_cvec, "range_max"); + if (options && cvv && ylength != NULL){ + *cvv = ylength->ys_cvec; *options |= YANG_OPTIONS_LENGTH; } if (options && pattern && ypattern != NULL){ @@ -928,8 +892,8 @@ resolve_restrictions(yang_stmt *yrange, * @param[in] ytype yang-stmt object containing currently resolving type * @param[out] yrestype resolved type. return built-in type or NULL. mandatory * @param[out] options pointer to flags field of optional values. optional - * @param[out] mincv pointer to cv with min range or length. If options&YANG_OPTIONS_RANGE - * @param[out] maxcv pointer to cv with max range or length. If options&YANG_OPTIONS_RANGE + * @param[out] cvv pointer to cvec with min range or length. + * If options&YANG_OPTIONS_RANGE or YANG_OPTIONS_LENGTH * @param[out] pattern pointer to static string of yang string pattern. optional * @param[out] fraction for decimal64, how many digits after period * @retval 0 OK. Note yrestype may still be NULL. @@ -946,8 +910,7 @@ yang_type_resolve(yang_stmt *ys, yang_stmt *ytype, yang_stmt **yrestype, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction) { @@ -970,8 +933,8 @@ yang_type_resolve(yang_stmt *ys, prefix = yarg_prefix(ytype); /* And this its prefix */ /* Cache does not work for eg string length 32? */ if (!yang_builtin(type) && ytype->ys_typecache != NULL){ - if (yang_type_cache_get(ytype->ys_typecache, - yrestype, options, mincv, maxcv, pattern, fraction) < 0) + if (yang_type_cache_get(ytype->ys_typecache, yrestype, + options, cvv, pattern, fraction) < 0) goto done; goto ok; } @@ -984,7 +947,7 @@ yang_type_resolve(yang_stmt *ys, if (prefix == NULL && yang_builtin(type)){ *yrestype = ytype; resolve_restrictions(yrange, ylength, ypattern, yfraction, options, - mincv, maxcv, pattern, fraction); + cvv, pattern, fraction); goto ok; } @@ -1022,13 +985,12 @@ yang_type_resolve(yang_stmt *ys, } /* recursively resolve this new type */ if (yang_type_resolve(ys, rytype, yrestype, - options, mincv, maxcv, pattern, fraction) < 0) + options, cvv, pattern, fraction) < 0) goto done; /* overwrites the resolved if any */ - resolve_restrictions(yrange, ylength, ypattern, yfraction, - options, mincv, maxcv, pattern, fraction); + resolve_restrictions(yrange, ylength, ypattern, yfraction, options, + cvv, pattern, fraction); } - ok: retval = 0; done: @@ -1042,11 +1004,11 @@ yang_type_resolve(yang_stmt *ys, * @code * yang_stmt *yrestype; * int options; - * int64_t min, max; + * cvec *cvv = NULL; * char *pattern; * uint8_t fraction; * - * if (yang_type_get(ys, &type, &yrestype, &options, &min, &max, &pattern, &fraction) < 0) + * if (yang_type_get(ys, &type, &yrestype, &options, &cvv, &pattern, &fraction) < 0) * goto err; * if (yrestype == NULL) # unresolved * goto err; @@ -1078,8 +1040,7 @@ yang_type_get(yang_stmt *ys, char **origtype, yang_stmt **yrestype, int *options, - cg_var **mincv, - cg_var **maxcv, + cvec **cvv, char **pattern, uint8_t *fraction ) @@ -1100,7 +1061,7 @@ yang_type_get(yang_stmt *ys, if (origtype) *origtype = type; if (yang_type_resolve(ys, ytype, yrestype, - options, mincv, maxcv, pattern, fraction) < 0) + options, cvv, pattern, fraction) < 0) goto done; clicon_debug(3, "%s: %s %s->%s", __FUNCTION__, ys->ys_argument, type, *yrestype?(*yrestype)->ys_argument:"null"); diff --git a/test/README.md b/test/README.md index 59832663..1326365e 100644 --- a/test/README.md +++ b/test/README.md @@ -2,8 +2,9 @@ This directory contains testing code for clixon and the example application. Assumes setup of http daemon as describe under apps/restonf -- clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script +- Jenkinsfile Makefile for Jenkins tests. Build clixon and run tests. - all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. By default the script will exit on first error. Run as `all.sh summary` to continue and print a summary on all tests. +- site.sh Add your site-specific modifications here - test_nacm.sh Auth tests using internal NACM - test_nacm_ext.sh Auth tests using external NACM (separate file) - test_cli.sh CLI tests @@ -12,6 +13,7 @@ application. Assumes setup of http daemon as describe under apps/restonf - test_yang.sh Yang tests for constructs not in the example. - test_leafref.sh Yang leafref tests - test_datastore.sh Datastore tests +- and many more... Example runs: ``` diff --git a/test/test_openconfig.sh b/test/test_openconfig.sh index 47574fa2..f5f56bcf 100755 --- a/test/test_openconfig.sh +++ b/test/test_openconfig.sh @@ -3,9 +3,6 @@ # Note that the openconfig test suites are patched to counter CLixon issues as follows: # - release/models/mpls/openconfig-mpls-te.yang # issue: https://github.com/clicon/clixon/issues/60 -# - release/models/wifi/types/openconfig-wifi-types.yang -# issue: https://github.com/clicon/clixon/issues/59 -# # Yang specifics: multi-keys and empty type APPNAME=example diff --git a/test/test_type.sh b/test/test_type.sh index 20f6f7aa..188107a6 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,8 +1,10 @@ #!/bin/bash # Advanced union types and generated code # and enum w values +# XXX NO SUPPORT FOR lists of ranges and lengths !!! APPNAME=example # include err() and new() functions and creates $dir + . ./lib.sh cfg=$dir/conf_yang.xml @@ -123,57 +125,47 @@ module example{ enum down; } } - leaf length1 { - type string { - length "1"; - } - } -/* leaf length2 { - type string { - length "max"; - } - } - leaf length3 { - type string { - length "min"; - } - }*/ - leaf length4 { - type string { - length "4..4000"; - } - } -/* leaf length5 { - type string { - length "min..max"; - } - }*/ leaf num1 { type int32 { range "1"; } } -/* leaf num2 { - type int32 { - range "min"; - } - } - leaf num3 { - type int32 { - range "max"; - } - } -*/ - leaf num4 { + leaf num2 { type int32 { range "4..4000"; } } -/* leaf num5 { - type int32 { + leaf num3 { + type uint8 { range "min..max"; } - }*/ + } + leaf num4 { /* XXX multiple ranges not supported yet - only first*/ + type uint8 { + range "1 .. 2 | 42"; + } + } + leaf len1 { + type string { + length "2"; + } + } + leaf len2 { + type string { + length "4..4000"; + } + } + leaf len3 { + type string { + length "min..max"; + } + } + leaf len4 { + type string { + range "1 .. 2 | 42"; + } + } + typedef mybits { description "Test adding several bits"; type bits { @@ -303,6 +295,70 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '-1]]>]]>' "^]]>]]>$" + +new "netconf validate num1 -1 wrong" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementnum1errorNumber out of range: -1]]>]]>$' + +new "cli range test num2 1000 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 1000" 0 "^$" + +new "cli range test num2 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 3" 255 "^$" + +new "cli range test num2 5000 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num2 5000" 255 "^$" + +new "cli range test num3 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num3 42" 0 "^$" + +new "cli range test num3 260 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set num3 260" 255 "^$" + +#----------------string ranges--------------------- + +new "cli length test len1 1 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 x" 255 "^$" + +new "cli length test len1 2 OK" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 xy" 0 "^$" + +new "cli length test len1 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len1 hej" 255 "^$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf length set len1 1" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'x]]>]]>' "^]]>]]>$" + +new "netconf validate len1 1 wrong" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" '^applicationbad-elementlen1errorstring length out of range: 1]]>]]>$' + +new "cli length test len2 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len2 hejhophdsakjhkjsadhkjsahdkjsad" 0 "^$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "cli length test len2 3 error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len2 ab" 255 "^$" + +new "cli range test len3 42 ok" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set len3 hsakjdhkjsahdkjsahdksahdksajdhsakjhd" 0 "^$" + if [ $BE -eq 0 ]; then exit # BE fi From 207858e20dd8b068dd13e7d720aa7c663d87884b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 10 Jan 2019 20:52:19 +0100 Subject: [PATCH 29/72] * Support of yangmodels supported, see test_yangmodels.sh * Added -o "