From 9fa5e216c4c37a1ca2d9460356f873016a426652 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 20 Feb 2020 14:00:01 +0100 Subject: [PATCH] * New "general-purpose" datastore upgrade callback added which i called once on startup, intended for lo w-level general upgrades and as a complement to module-specific upgrade. * Called on startup after initial XML parsing, but before module-specific upgrades * Enabled by definign the `.ca_datastore_upgrade` * [General-purpose upgrade documentation](https://clixon-docs.readthedocs.io/en/latest/backend.html#ge neral-purpose) * JSON parse error messages change from ` on line x: syntax error,..` to `json_parse: line x: syntax err or` * Unknown-element error message is more descriptive, eg from `namespace is: urn:example:clixon` to: `Fai led to find YANG spec of XML node: x with parent: xp in namespace urn:example:clixon`. * C-API parse and validation API more capable * `xml_spec_populate` family of functions extended with three-value return values * -1: error, 0: parse OK, 1: parse and YANG binding OK. * `xml_parse` and `json_parse` API changes * Three value returns: -1: error, 0: parse OK, 1: parse and YANG binding OK. * Extended `xml_parse_file2` and `xml_parse_string2` extended API functions with all options available. * New concept called `yang_bind` that defines how XML symbols are bound to YANG after parsing * Existing API same except `xml_parse_file` `endtag` argument moved to `xml_parse_file2` * C-API: Added instrumentation: `xml_size` and `xml_stats_get`. * Fixed: Enabling modstate (CLICON_XMLDB_MODSTATE), changing a revision on a yang, and restarting made the backend daemon exit at start (thanks Matt) * Also: ensure to load `ietf-yang-library.yang ` if CLICON_XMLDB_MODSTATE is set --- apps/backend/backend_client.c | 59 ++- apps/backend/backend_commit.c | 24 +- apps/backend/backend_main.c | 9 +- apps/backend/backend_plugin.c | 2 +- apps/backend/backend_startup.c | 2 +- apps/cli/cli_common.c | 2 +- apps/cli/cli_main.c | 2 +- apps/cli/cli_show.c | 27 +- apps/netconf/netconf_main.c | 3 +- apps/netconf/netconf_rpc.c | 26 +- apps/restconf/README.md | 2 +- apps/restconf/restconf_main.c | 19 +- apps/restconf/restconf_methods.c | 21 +- apps/restconf/restconf_methods_get.c | 3 +- apps/restconf/restconf_methods_post.c | 129 +++---- docker/main/startsystem.sh | 8 +- example/main/example_backend.c | 141 +++++++- include/clixon_custom.h | 25 +- lib/clixon/clixon_json.h | 2 + lib/clixon/clixon_path.h | 11 +- lib/clixon/clixon_plugin.h | 25 +- lib/clixon/clixon_string.h | 11 +- lib/clixon/clixon_xml.h | 20 +- lib/clixon/clixon_xml_map.h | 11 +- lib/clixon/clixon_xml_sort.h | 1 - lib/clixon/clixon_yang_module.h | 9 +- lib/src/clixon_data.c | 1 + lib/src/clixon_datastore.c | 3 +- lib/src/clixon_datastore_read.c | 13 +- lib/src/clixon_datastore_write.c | 9 +- lib/src/clixon_file.c | 12 +- lib/src/clixon_json.c | 135 ++++--- lib/src/clixon_json_parse.h | 26 +- lib/src/clixon_json_parse.l | 9 +- lib/src/clixon_json_parse.y | 29 +- lib/src/clixon_options.c | 5 +- lib/src/clixon_path.c | 13 +- lib/src/clixon_plugin.c | 51 ++- lib/src/clixon_proto_client.c | 39 +- lib/src/clixon_validate.c | 21 +- lib/src/clixon_xml.c | 351 ++++++++++++++---- lib/src/clixon_xml_changelog.c | 5 +- lib/src/clixon_xml_map.c | 496 ++++++++++++++++++++------ lib/src/clixon_xml_nsctx.c | 16 +- lib/src/clixon_xml_parse.h | 16 +- lib/src/clixon_xml_parse.l | 10 +- lib/src/clixon_xml_parse.y | 82 ++--- lib/src/clixon_xml_sort.c | 58 +-- lib/src/clixon_xpath.c | 9 +- lib/src/clixon_xpath_eval.c | 20 +- lib/src/clixon_yang.c | 2 +- lib/src/clixon_yang_module.c | 7 +- lib/src/clixon_yang_parse_lib.c | 2 +- lib/src/clixon_yang_type.c | 2 +- test/lib.sh | 3 + test/test_augment.sh | 5 +- test/test_copy_config.sh | 2 +- test/test_datastore_repair.sh | 135 +++++++ test/test_identity.sh | 23 +- test/test_json.sh | 2 +- test/test_leafref_state.sh | 13 +- test/test_netconf_whitespace.sh | 3 +- test/test_perf.sh | 9 +- test/test_restconf.sh | 41 +-- test/test_restconf2.sh | 42 ++- test/test_restconf_jukebox.sh | 21 +- test/test_restconf_listkey.sh | 2 +- test/test_rpc.sh | 8 +- test/test_search_index.sh | 85 +++++ test/test_upgrade.sh | 2 +- test/test_upgrade_simple.sh | 127 +++++++ test/test_yang.sh | 5 +- test/test_yang_bind.sh | 159 +++++++++ util/clixon_util_datastore.c | 6 +- util/clixon_util_grpc.c | 8 +- util/clixon_util_insert.c | 8 +- util/clixon_util_path.c | 48 ++- util/clixon_util_socket.c | 2 +- util/clixon_util_xml.c | 139 ++++++-- util/clixon_util_xpath.c | 25 +- 80 files changed, 2204 insertions(+), 755 deletions(-) create mode 100755 test/test_datastore_repair.sh create mode 100755 test/test_search_index.sh create mode 100755 test/test_upgrade_simple.sh create mode 100755 test/test_yang_bind.sh diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 12bf047a..c1aee865 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -3,7 +3,8 @@ ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2020 Olof Hagsand + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC This file is part of CLIXON. @@ -168,22 +169,28 @@ client_get_capabilities(clicon_handle h, char *xpath, cxobj **xret) { - int retval = -1; - cxobj *xrstate = NULL; /* xml restconf-state node */ - cxobj *xcap = NULL; /* xml capabilities node */ - + int retval = -1; + cxobj *xrstate = NULL; /* xml restconf-state node */ + cbuf *cb = NULL; + if ((xrstate = xpath_first(*xret, NULL, "restconf-state")) == NULL){ clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node"); goto done; } - if ((xcap = xml_new("capabilities", xrstate, yspec)) == NULL) + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; - if (xml_parse_va(&xcap, yspec, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit") < 0) - goto done; - if (xml_parse_va(&xcap, yspec, "urn:ietf:params:restconf:capability:depth:1.0") < 0) + } + cprintf(cb, ""); + cprintf(cb, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit"); + cprintf(cb, "urn:ietf:params:restconf:capability:depth:1.0"); + cprintf(cb, ""); + if (xml_parse_string2(cbuf_get(cb), YB_PARENT, NULL, &xrstate, NULL) < 0) goto done; retval = 0; done: + if (cb) + cbuf_free(cb); return retval; } @@ -217,10 +224,10 @@ client_get_streams(clicon_handle h, goto done; } if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, 0, "clicon buffer"); + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - cprintf(cb,"<%s xmlns=\"%s\">", top, yang_argument_get(yns)); + cprintf(cb, "<%s xmlns=\"%s\">", top, yang_argument_get(yns)); /* Second argument is a hack to have the same function for the * RFC5277 and 8040 stream cases */ @@ -271,11 +278,16 @@ client_statedata(clicon_handle h, yang_stmt *ymod; int ret; char *namespace; + cbuf *cb = NULL; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){ if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){ clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found"); @@ -285,7 +297,9 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found"); goto done; } - if (xml_parse_va(xret, yspec, "", namespace) < 0) + + cprintf(cb, "", namespace); + if (xml_parse_string2(cbuf_get(cb), YB_TOP, yspec, xret, NULL) < 0) goto done; if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0) goto done; @@ -301,7 +315,9 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found"); goto done; } - if (xml_parse_va(xret, yspec, "", namespace) < 0) + cbuf_reset(cb); + cprintf(cb, "", namespace); + if (xml_parse_string2(cbuf_get(cb), YB_TOP, yspec, xret, NULL) < 0) goto done; if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0) goto done; @@ -323,6 +339,8 @@ client_statedata(clicon_handle h, retval = 1; /* OK */ done: clicon_debug(1, "%s %d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); return retval; fail: retval = 0; @@ -574,7 +592,7 @@ from_client_edit_config(clicon_handle h, xml_spec_set(xc, NULL); /* Populate XML with Yang spec (why not do this in parser?) */ - if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) + if (xml_spec_populate(xc, yspec, NULL) < 0) goto done; /* Maybe validate xml here as in text_modify_top? */ if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0) @@ -980,15 +998,22 @@ from_client_get(clicon_handle h, } /* If not only-state, then read running config * Note xret can be pruned by nacm below and change name and - * metrged with state data, so zero-copy cant be used + * merged with state data, so zero-copy cant be used * Also, must use external namespace context here due to ", yspec, &xt) < 0) + if (xml_parse_file(fd, yspec, &xt) < 0) goto done; if (xt == NULL){ clicon_err(OE_XML, 0, "No xml tree in %s", filename); @@ -748,6 +749,10 @@ main(int argc, if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) goto done; + /* Load yang YANG module state */ + if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE") && + yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0) + goto done; /* Here all modules are loaded * Compute and set canonical namespace context */ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index edfe51c8..f2c28e66 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -133,7 +133,7 @@ clixon_plugin_statedata(clicon_handle h, cbuf_free(ccc); } #endif - if (xml_apply(x, CX_ELMNT, xml_spec_populate, yspec) < 0) + if (xml_spec_populate(x, yspec, NULL) < 0) goto done; if ((ret = netconf_trymerge(x, yspec, xret)) < 0) goto done; diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c index 55c0aa1c..5ef7e9cd 100644 --- a/apps/backend/backend_startup.c +++ b/apps/backend/backend_startup.c @@ -176,7 +176,7 @@ load_extraxml(clicon_handle h, goto done; } yspec = clicon_dbspec_yang(h); - if (xml_parse_file(fd, "", yspec, &xt) < 0) + if (xml_parse_file(fd, yspec, &xt) < 0) goto done; /* Replace parent w first child */ if (xml_rootchild(xt, 0, &xt) < 0) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index c55e7358..d3bf9d05 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -796,7 +796,7 @@ load_config_file(clicon_handle h, clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; } - if (xml_parse_file(fd, "", NULL, &xt) < 0) + if (xml_parse_file(fd, NULL, &xt) < 0) goto done; if (xt == NULL) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 66786725..4885ba0c 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -541,7 +541,7 @@ main(int argc, char **argv) cligen_tree_add(cli_cligen(h), treeref, pt); if (printgen) - cligen_print(stdout, pt, 1); /* pt_print */ + pt_print(stdout, pt, 1); /* pt_print */ } /* Initialize cli syntax */ diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index b9a3bd46..6eb7dbb6 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -2,7 +2,9 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC This file is part of CLIXON. @@ -431,6 +433,7 @@ cli_show_config1(clicon_handle h, cvec *argv) { int retval = -1; + int ret; char *db; char *formatstr; char *xpath; @@ -450,6 +453,10 @@ cli_show_config1(clicon_handle h, goto done; } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } /* First argv argument: Database */ db = cv_string_get(cvec_i(argv, 0)); /* Second argv argument: Format */ @@ -489,13 +496,13 @@ cli_show_config1(clicon_handle h, clicon_rpc_generate_error(xerr, "Get configuration", NULL); goto done; } - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_FATAL, 0, "No DB_SPEC"); + /* Some formats (eg cli) require yang */ + if ((ret = xml_spec_populate(xt, yspec, &xerr)) < 0) + goto done; + if (ret == 0){ + clicon_rpc_generate_error(xerr, "Get configuration", NULL); 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: @@ -681,6 +688,7 @@ cli_show_auto1(clicon_handle h, cvec *argv) { int retval = 1; + int ret; yang_stmt *yspec; char *api_path_fmt; /* xml key format */ // char *api_path = NULL; /* xml key */ @@ -740,6 +748,13 @@ cli_show_auto1(clicon_handle h, clicon_rpc_generate_error(xerr, "Get configuration", NULL); goto done; } + /* Some formats (eg cli) require yang */ + if ((ret = xml_spec_populate(xt, yspec, &xerr)) < 0) + goto done; + if (ret == 0){ + clicon_rpc_generate_error(xerr, "Get configuration", NULL); + goto done; + } if ((xp = xpath_first(xt, nsc, "%s", xpath)) != NULL) /* Print configuration according to format */ switch (format){ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index a540628e..c92ac114 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -103,7 +103,6 @@ netconf_hello_dispatch(cxobj *xn) return retval; } - /*! Process incoming packet * @param[in] h Clicon handle * @param[in] cb Packet buffer @@ -149,7 +148,7 @@ netconf_input_packet(clicon_handle h, free(str0); if ((xrpc=xpath_first(xreq, NULL, "//rpc")) != NULL){ isrpc++; - if (xml_spec_populate_rpc_input(h, xrpc, yspec) < 0) + if (xml_spec_populate_rpc(xrpc, yspec, NULL) < 0) goto done; if ((ret = xml_yang_validate_rpc(h, xrpc, &xret)) < 0) goto done; diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 8f3315f5..74d3f3ee 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -2,7 +2,9 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC This file is part of CLIXON. @@ -157,9 +159,11 @@ netconf_get_config(clicon_handle h, cxobj *xn, cxobj **xret) { - cxobj *xfilter; /* filter */ - int retval = -1; - char *ftype = NULL; + int retval = -1; + cxobj *xfilter; /* filter */ + char *ftype = NULL; + cxobj *xdata; + yang_stmt *yspec; /* ie ... */ if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL) @@ -354,9 +358,11 @@ netconf_get(clicon_handle h, cxobj *xn, cxobj **xret) { - cxobj *xfilter; /* filter */ - int retval = -1; - char *ftype = NULL; + int retval = -1; + cxobj *xfilter; /* filter */ + char *ftype = NULL; + cxobj *xdata; + yang_stmt *yspec; /* ie ... */ if ((xfilter = xpath_first(xn, NULL, "filter")) != NULL) @@ -471,7 +477,6 @@ netconf_notification_cb(int s, } /* - RESULT # If not present, events in the default NETCONF stream will be sent. @@ -586,7 +591,7 @@ netconf_application_rpc(clicon_handle h, /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find(yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) + if (xml_spec_populate(xn, yspec, NULL) < 0) goto done; if ((ret = xml_yang_validate_all_top(h, xn, &xerr)) < 0) goto done; @@ -618,9 +623,8 @@ netconf_application_rpc(clicon_handle h, if ((youtput = yang_find(yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, NULL, "/"); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) + if (xml_spec_populate(xoutput, yspec, NULL) < 0) goto done; - if ((ret = xml_yang_validate_all_top(h, xoutput, &xerr)) < 0) goto done; if (ret > 0 && (ret = xml_yang_validate_add(h, xoutput, &xerr)) < 0) diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 35afad43..78c7a67b 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -217,7 +217,7 @@ You can also run restconf in a debugger. sudo gdb /www-data/clixon_restconf (gdb) run -D 1 -f /usr/local/etc/example.xml ``` -but you need to ensure /www-data/fastcgi_restconf.sock has the following access: +but you need to ensure /www-data/fastcgi_restconf.sock has the following access (may need to be done after restconf has started) ``` rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock ``` diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index ad3fd10e..297a09fa 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -3,6 +3,7 @@ ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC This file is part of CLIXON. @@ -233,7 +234,7 @@ api_root(clicon_handle h, if (xml_parse_string("2016-06-21", NULL, &xt) < 0) goto done; - if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) + if (xml_spec_populate(xt, yspec, NULL) < 0) goto done; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -548,15 +549,15 @@ usage(clicon_handle h, { fprintf(stderr, "usage:%s [options]\n" "where options are\n" - "\t-h \t\tHelp\n" - "\t-D \tDebug level\n" - "\t-f \tConfiguration file (mandatory)\n" - "\t-l > \tLog on (s)yslog, (f)ile (syslog is default)\n" - "\t-p \tYang directory path (see CLICON_YANG_DIR)\n" - "\t-d \tSpecify restconf plugin directory dir (default: %s)\n" - "\t-y \tLoad yang spec file (override yang main module)\n" + "\t-h \t\t Help\n" + "\t-D \t Debug level\n" + "\t-f \t Configuration file (mandatory)\n" + "\t-l > \t Log on (s)yslog, (f)ile (syslog is default)\n" + "\t-p \t Yang directory path (see CLICON_YANG_DIR)\n" + "\t-d \t Specify restconf plugin directory dir (default: %s)\n" + "\t-y \t Load yang spec file (override yang main module)\n" "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" - "\t-u \tInternal socket domain path or IP addr (see -a)\n" + "\t-u \t Internal socket domain path or IP addr (see -a)\n" "\t-o \"