From a04630627030cbff85d07a69b3665c9930a51f2f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 30 Aug 2021 14:43:24 +0200 Subject: [PATCH] - New netconf-specific uint32 parse functions - Added failure handling to xpath traverse_canonical - Started pagination cli code --- apps/backend/backend_client.c | 15 +-- apps/backend/backend_get.c | 87 ++++++------- apps/cli/cli_common.c | 97 ++++++++------ apps/cli/cli_show.c | 3 - apps/restconf/restconf_methods_get.c | 51 ++++---- example/main/example_backend.c | 1 + lib/clixon/clixon_netconf_lib.h | 3 + lib/clixon/clixon_proto_client.h | 5 +- lib/clixon/clixon_xpath.h | 2 +- lib/src/clixon_netconf_lib.c | 108 +++++++++++++++ lib/src/clixon_proto_client.c | 36 +++-- lib/src/clixon_xpath.c | 96 ++++++++++---- test/test_pagination.sh | 123 +++++++++--------- test/test_xpath_canonical.sh | 4 +- util/clixon_util_xpath.c | 8 +- ...on-netconf-list-pagination@2021-08-27.yang | 12 +- 16 files changed, 410 insertions(+), 241 deletions(-) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 44b335a1..45dd0c3a 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -732,7 +732,7 @@ from_client_kill_session(clicon_handle h, { int retval = -1; uint32_t id; /* session id */ - char *str; + char *str = NULL; struct client_entry *ce; char *db = "running"; /* XXX */ cxobj *x; @@ -745,15 +745,10 @@ from_client_kill_session(clicon_handle h, goto done; goto ok; } - if ((ret = parse_uint32(str, &id, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_uint32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_element(cbret, "protocol", "session-id", reason) < 0) - goto done; - goto done; - } + if ((ret = netconf_parse_uint32("session-id", str, NULL, 0, cbret, &id)) < 0) + goto done; + if (ret == 0) + goto ok; /* may or may not be in active client list, probably not */ if ((ce = ce_find_byid(backend_client_list(h), id)) != NULL){ xmldb_unlock_all(h, id); /* Removes locks on all databases */ diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 2d8eefd6..34b2b959 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -273,17 +273,18 @@ client_statedata(clicon_handle h, } #ifdef LIST_PAGINATION + /*! Help function for parsing restconf query parameter and setting netconf attribute * * If not "unbounded", parse and set a numeric value - * @param[in] h Clixon handle - * @param[in] name Name of attribute - * @param[in] defaultstr Default string which is accepted and sets value to 0 - * @param[in,out] cbret Output buffer for internal RPC message - * @param[out] value Value - * @retval -1 Error - * @retval 0 Invalid, cbret set - * @retval 1 OK + * @param[in] h Clixon handle + * @param[in] name Name of attribute + * @param[in] defaultstr Default string which is accepted and sets value to 0 + * @param[in,out] cbret Output buffer for internal RPC message if invalid + * @param[out] value Value + * @retval -1 Error + * @retval 0 Invalid, cbret set + * @retval 1 OK */ static int element2value(clicon_handle h, @@ -293,35 +294,15 @@ element2value(clicon_handle h, cbuf *cbret, uint32_t *value) { - int retval = -1; char *valstr; - int ret; - char *reason = NULL; cxobj *x; *value = 0; if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL && - (valstr = xml_body(x)) != NULL && - strcmp(valstr, defaultstr) != 0){ - if ((ret = parse_uint32(valstr, value, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_uint32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_element(cbret, "application", - name, "Unrecognized value") < 0) - goto done; - goto fail; - } + (valstr = xml_body(x)) != NULL){ + return netconf_parse_uint32(name, valstr, defaultstr, 0, cbret, value); } - retval = 1; - done: - if (reason) - free(reason); - return retval; - fail: - retval = 0; - goto done; + return 1; } #endif @@ -368,8 +349,8 @@ get_common(clicon_handle h, char *reason = NULL; cbuf *cbmsg = NULL; /* For error msg */ #ifdef LIST_PAGINATION - uint32_t limit = 0; uint32_t offset = 0; + uint32_t limit = 0; uint32_t total = 0; uint32_t remaining = 0; char *direction = NULL; @@ -393,6 +374,8 @@ get_common(clicon_handle h, if ((xfilter = xml_find(xe, "filter")) != NULL){ char *xpath0; cvec *nsc1 = NULL; + cbuf *cbreason = NULL; + if ((xpath0 = xml_find_value(xfilter, "select"))==NULL) xpath0 = "/"; /* Create namespace context for xpath from @@ -402,8 +385,16 @@ get_common(clicon_handle h, else if (xml_nsctx_node(xfilter, &nsc) < 0) goto done; - if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0) + if ((ret = xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1, &cbreason)) < 0) goto done; + if (ret == 0){ + if (netconf_bad_attribute(cbret, "application", + "select", cbuf_get(cbreason)) < 0) + goto done; + goto ok; + } + if (cbreason) + cbuf_free(cbreason); if (nsc) xml_nsctx_free(nsc); nsc = nsc1; @@ -435,36 +426,42 @@ get_common(clicon_handle h, if (yang_path_arg(yspec, xpath, &ylist) < 0) goto done; if (ylist == NULL){ - if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not found") < 0) + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* error reason should be in clicon_err_reason */ + cprintf(cbmsg, "Netconf get list-pagination: \"%s\" not found", xpath); + if (netconf_invalid_value(cbret, "application", cbuf_get(cbmsg)) < 0) goto done; goto ok; } if (yang_keyword_get(ylist) != Y_LIST && yang_keyword_get(ylist) != Y_LEAF_LIST){ - if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0) + if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0) goto done; goto ok; } if ((list_config = yang_config_ancestor(ylist)) != 0){ /* config list */ if (content == CONTENT_NONCONFIG){ - if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a config list but content request is nonconfig") < 0) + if (netconf_invalid_value(cbret, "application", "list-pagination targets a config list but content request is nonconfig") < 0) goto done; goto ok; } } else { /* state list */ if (content == CONTENT_CONFIG){ - if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a state list but content request is config") < 0) + if (netconf_invalid_value(cbret, "application", "list-pagination targets a state list but content request is config") < 0) goto done; goto ok; } } - /* limit */ - if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) - goto done; /* offset */ if (ret && (ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) goto done; + /* limit */ + if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0) + goto done; /* direction */ if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){ direction = xml_body(x); @@ -517,7 +514,11 @@ get_common(clicon_handle h, } } else{ - /* XXX remaining of state list??*/ + /* Remaining of state list. Two strategies: + * 1. New api where state callback is registered, lock, iterative, unlock + * 2. Read all here and count (fallback) + */ + } /* Append predicate to original xpath and replace it */ xpath2 = cbuf_get(cbpath); @@ -525,9 +526,7 @@ get_common(clicon_handle h, else xpath2 = xpath; #endif /* LIST_PAGINATION */ - /* Read config - * XXX This seems unnecessary complex - */ + /* Read configuration */ switch (content){ case CONTENT_CONFIG: /* config data only */ /* specific xpath */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 471aafb1..ad5e3fc1 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -1317,28 +1317,38 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv) * XXX: This is hardcoded only for test_pagination.sh * @param[in] h Clicon handle * @param[in] cvv Vector of cli string and instantiated variables - * @param[in] argv Vector. Format: + * @param[in] argv Vector. Format: + * Also, if there is a cligen variable called "xpath" it will override argv xpath arg */ int -cli_pagination(clicon_handle h, cvec *vars, cvec *argv) +cli_pagination(clicon_handle h, + cvec *cvv, + cvec *argv) { - int retval = -1; - cbuf *cb = NULL; - char *xpath = NULL; - char *prefix = NULL; - char *namespace = NULL; - cxobj *xret = NULL; - cxobj *xerr; - cvec *nsc = NULL; - char *formatstr; + int retval = -1; + cbuf *cb = NULL; + char *xpath = NULL; + char *prefix = NULL; + char *namespace = NULL; + cxobj *xret = NULL; + cxobj *xerr; + cvec *nsc = NULL; + char *formatstr; enum format_enum format; - cxobj *xc; + cxobj *xc; + cg_var *cv; + int i; + const int win = 20; if (cvec_len(argv) != 4){ clicon_err(OE_PLUGIN, 0, "Expected usage: "); goto done; } - xpath = cvec_i_str(argv, 0); + /* prefix:variable overrides argv */ + if ((cv = cvec_find(cvv, "xpath")) != NULL) + xpath = cv_string_get(cv); + else + xpath = cvec_i_str(argv, 0); prefix = cvec_i_str(argv, 1); namespace = cvec_i_str(argv, 2); formatstr = cv_string_get(cvec_i(argv, 3)); /* Fourthformat: output format */ @@ -1348,34 +1358,39 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv) } if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL) goto done; - if (clicon_rpc_get_pageable_list(h, "running", xpath, NULL, nsc, CONTENT_CONFIG, -1, - "2", "0", - NULL, NULL, NULL, - &xret) < 0){ - goto done; - } - if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){ - clixon_netconf_error(xerr, "Get configuration", NULL); - goto done; - } - xc = NULL; - while ((xc = xml_child_each(xret, xc, CX_ELMNT)) != NULL) - switch (format){ - case FORMAT_XML: - clicon_xml2file_cb(stdout, xc, 0, 1, cligen_output); - break; - case FORMAT_JSON: - xml2json_cb(stdout, xc, 1, cligen_output); - break; - case FORMAT_TEXT: - xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ - break; - case FORMAT_CLI: - xml2cli_cb(stdout, xc, NULL, GT_HIDE, cligen_output); /* cli syntax */ - break; - case FORMAT_NETCONF: - break; + for (i = 0; i < 10; i++){ + if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc, + CONTENT_NONCONFIG, + -1, /* depth */ + win*i, /* offset */ + win*(i+1), /* limit */ + NULL, NULL, NULL, /* nyi */ + &xret) < 0){ + goto done; } + if ((xerr = xpath_first(xret, NULL, "/rpc-error")) != NULL){ + clixon_netconf_error(xerr, "Get configuration", NULL); + goto done; + } + xc = NULL; + while ((xc = xml_child_each(xret, xc, CX_ELMNT)) != NULL) + switch (format){ + case FORMAT_XML: + clicon_xml2file_cb(stdout, xc, 0, 1, cligen_output); + break; + case FORMAT_JSON: + xml2json_cb(stdout, xc, 1, cligen_output); + break; + case FORMAT_TEXT: + xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ + break; + case FORMAT_CLI: + xml2cli_cb(stdout, xc, NULL, GT_HIDE, cligen_output); /* cli syntax */ + break; + case FORMAT_NETCONF: + break; + } + } retval = 0; done: if (xret) @@ -1386,7 +1401,7 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv) } #else int -cli_pagination(clicon_handle h, cvec *vars, cvec *argv) +cli_pagination(clicon_handle h, cvec *cvv, cvec *argv) { fprintf(stderr, "Not yet implemented\n"); return 0; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index c7e524bd..e7e72210 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -207,7 +207,6 @@ expand_dbvar(void *h, cxobj *xbot = NULL; /* xpath, NULL if datastore */ yang_stmt *y = NULL; /* yang spec of xpath */ yang_stmt *yp; - char *reason = NULL; cvec *nsc = NULL; int ret; int cvvi = 0; @@ -368,8 +367,6 @@ expand_dbvar(void *h, xml_free(xerr); if (nsc) xml_nsctx_free(nsc); - if (reason) - free(reason); if (api_path) free(api_path); if (xvec) diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index 818f62db..df37aca9 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -351,8 +351,8 @@ api_data_collection(clicon_handle h, yang_stmt *y = NULL; cbuf *cbrpc = NULL; int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */ - char *limit; - char *offset; + uint32_t limit = 0; + uint32_t offset = 0; char *direction; char *sort; char *where; @@ -446,13 +446,29 @@ api_data_collection(clicon_handle h, } } } - limit = cvec_find_str(qvec, "limit"); - offset = cvec_find_str(qvec, "offset"); + if ((attr = cvec_find_str(qvec, "limit")) != NULL){ /* 1-uint32 or "unbounded" */ + if ((ret = netconf_parse_uint32_xml("limit", attr, "unbounded", 0, &xerr, &limit)) < 0) + goto done; + if (ret == 0){ + if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) + goto done; + goto ok; + } + } + if ((attr = cvec_find_str(qvec, "offset")) != NULL){ /* 1-uint32 or "none" */ + if ((ret = netconf_parse_uint32_xml("offset", attr, "none", 0, &xerr, &offset)) < 0) + goto done; + if (ret == 0){ + if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) + goto done; + goto ok; + } + } direction = cvec_find_str(qvec, "direction"); sort = cvec_find_str(qvec, "sort"); where = cvec_find_str(qvec, "where"); - if (clicon_rpc_get_pageable_list(h, "running", xpath, y, nsc, content, - depth, limit, offset, direction, sort, where, + if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc, content, + depth, offset, limit, direction, sort, where, &xret) < 0){ if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) goto done; @@ -483,23 +499,12 @@ api_data_collection(clicon_handle h, goto done; if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath) < 0) goto done; -#if 0 - /* Check if not exists */ - if (xlen == 0){ - /* 4.3: If a retrieval request for a data resource represents an - instance that does not exist, then an error response containing - a "404 Not Found" status-line MUST be returned by the server. - The error-tag value "invalid-value" is used in this case. */ - if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0) - goto done; - /* override invalid-value default 400 with 404 */ - if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){ - if (api_return_err(h, req, xe, pretty, media_out, 404) < 0) - goto done; - } - goto ok; - } -#endif + /* Note, the netconf GET pageable list can not distinguish between: + * - not-exists, ie there are no entries + * - no matching entries, eg there are entries, just not that match + * Here we take the latter approach to return an empty list and do not + * handle the non-exist case differently. + */ for (i=0; itrue"); - if (limit) - cprintf(cb, "%s", limit); - if (offset) - cprintf(cb, "%s", offset); + if (offset != 0) + cprintf(cb, "%u", offset); + if (limit != 0) + cprintf(cb, "%u", limit); if (direction) cprintf(cb, "%s", direction); if (sort) @@ -980,8 +976,8 @@ clicon_rpc_get_pageable_list(clicon_handle h, if (clicon_rpc_msg(h, msg, &xret) < 0) goto done; /* Send xml error back: first check error, then ok */ - if ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) - xr = xml_parent(xr); /* point to rpc-reply */ + if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL) + xd = xml_parent(xd); /* point to rpc-reply */ else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){ if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL) goto done; @@ -1001,7 +997,7 @@ clicon_rpc_get_pageable_list(clicon_handle h, } } } - if (xt){ + if (xt && xd){ if (xml_rm(xd) < 0) goto done; *xt = xd; diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index 0ef44f48..baa655f0 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -930,37 +930,64 @@ xpath_vec_bool(cxobj *xcur, return retval; } -/*! +/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes + * + * @param[in] xs Parsed xpath - xpath_tree + * @param[in] yspec Yang spec containing all modules, associated with namespaces + * @param[in] nsc0 Input namespace context + * @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free + * @param[out] reason Error reason if result is 0 - failed + * @retval 1 OK with nsc1 containing the transformed nsc + * @retval 0 XPath failure with reason set to why + * @retval -1 Fatal Error */ static int traverse_canonical(xpath_tree *xs, yang_stmt *yspec, cvec *nsc0, - cvec *nsc1) + cvec *nsc1, + cbuf **reason) { int retval = -1; char *prefix0; char *prefix1; char *namespace; yang_stmt *ymod; + cbuf *cb = NULL; + int ret; switch (xs->xs_type){ case XP_NODE: /* s0 is namespace prefix, s1 is name */ prefix0 = xs->xs_s0; if ((namespace = xml_nsctx_get(nsc0, prefix0)) == NULL){ - clicon_err(OE_XML, ENOENT, "No namespace found for prefix: %s", - prefix0); - goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "No namespace found for prefix: %s", prefix0); + if (reason) + *reason = cb; + goto failed; } if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){ - clicon_err(OE_XML, ENOENT, "No modules found for namespace: %s", - namespace); - goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "No modules found for namespace: %s", namespace); + if (reason) + *reason = cb; + goto failed; } if ((prefix1 = yang_find_myprefix(ymod)) == NULL){ - clicon_err(OE_XML, ENOENT, "No prefix found in module: %s", - yang_argument_get(ymod)); - goto done; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "No prefix found in module: %s", yang_argument_get(ymod)); + if (reason) + *reason = cb; + goto failed; } if (xml_nsctx_get(nsc1, prefix1) == NULL) if (xml_nsctx_add(nsc1, prefix1, namespace) < 0) @@ -977,25 +1004,36 @@ traverse_canonical(xpath_tree *xs, default: break; } - if (xs->xs_c0) - if (traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1) < 0) + if (xs->xs_c0){ + if ((ret = traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0) goto done; - if (xs->xs_c1) - if (traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1) < 0) + if (ret == 0) + goto failed; + } + if (xs->xs_c1){ + if ((ret = traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0) goto done; - retval = 0; + if (ret == 0) + goto failed; + } + retval = 1; done: return retval; + failed: + retval = 0; + goto done; } /*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes + * * @param[in] xpath0 Input xpath * @param[in] nsc0 Input namespace context * @param[in] yspec Yang spec containing all modules, associated with namespaces * @param[out] xpath1 Output xpath. Free after use with free * @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free - * @retval 0 OK, xpath1 and nsc1 allocated - * @retval -1 Error + * @retval 1 OK, xpath1 and nsc1 allocated + * @retval 0 XPath failure with reason set to why + * @retval -1 Fatal Error * Example: * Module A has prefix a and namespace urn:example:a and symbols x * Module B with prefix b and namespace urn:example:b and symbols y @@ -1008,7 +1046,8 @@ traverse_canonical(xpath_tree *xs, * @code * char *xpath1 = NULL; * cvec *nsc1 = NULL; - * if (xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1) < 0) + * cbuf *reason = NULL; + * if ((ret = xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1, &reason)) < 0) * err; * ... * if (xpath1) free(xpath1); @@ -1017,15 +1056,17 @@ traverse_canonical(xpath_tree *xs, */ int xpath2canonical(const char *xpath0, - cvec *nsc0, - yang_stmt *yspec, - char **xpath1, - cvec **nsc1p) + cvec *nsc0, + yang_stmt *yspec, + char **xpath1, + cvec **nsc1p, + cbuf **cbreason) { int retval = -1; xpath_tree *xpt = NULL; cvec *nsc1 = NULL; cbuf *xcb = NULL; + int ret; /* Parse input xpath into an xpath-tree */ if (xpath_parse(xpath0, &xpt) < 0) @@ -1036,8 +1077,10 @@ xpath2canonical(const char *xpath0, /* Traverse tree to find prefixes, transform them to canonical form and * create a canonical network namespace */ - if (traverse_canonical(xpt, yspec, nsc0, nsc1) < 0) + if ((ret = traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0) goto done; + if (ret == 0) + goto failed; /* Print tree with new prefixes */ if ((xcb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -1055,7 +1098,7 @@ xpath2canonical(const char *xpath0, *nsc1p = nsc1; nsc1 = NULL; } - retval = 0; + retval = 1; done: if (xcb) cbuf_free(xcb); @@ -1064,6 +1107,9 @@ xpath2canonical(const char *xpath0, if (xpt) xpath_tree_free(xpt); return retval; + failed: + retval = 0; + goto done; } /*! Return a count(xpath) diff --git a/test/test_pagination.sh b/test/test_pagination.sh index b8f701d4..ea1454b4 100755 --- a/test/test_pagination.sh +++ b/test/test_pagination.sh @@ -3,6 +3,7 @@ # Backlog items: # 1. "remaining" annotation RFC 7952 # 2. pattern '.*[\n].*' { modifier invert-match; +# XXX: handling of empty return ? # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -22,6 +23,10 @@ RESTCONFIG=$(restconf_config none false) # Validate internal state xml : ${validatexml:=false} +# Number of list/leaf-list entries in file +#: ${perfnr:=20000} +: ${perfnr:=200} + cat < $cfg $cfg @@ -206,59 +211,27 @@ cat< $fstate - - - ": "2020-10-11T06:47:59Z", - alice - 192.168.0.92 - POST /groups/group/2043 - true - - - 2020-11-01T15:22:01Z - bob - 192.168.2.16 - POST /groups/group/123 - false - - - 2020-12-12T21:00:28Z - eric - 192.168.254.1 - POST /groups/group/10 - true - - - 2021-01-03T06:47:59Z - alice - 192.168.0.92 - POST /groups/group/333 - true - - - 2021-01-21T10:00:00Z - bob - 192.168.2.16 - POST /groups/group/42 - true - - - 2020-02-07T09:06:21Z - alice - 192.168.0.92 - POST /groups/group/1202 - true - - - 2020-02-28T02:48:11Z - bob - 192.168.2.16 - POST /groups/group/345 - true - - EOF +# Check this later with committed data +new "generate state with $perfnr list entries" +echo "" >> $fstate +for (( i=0; i<$perfnr; i++ )); do + echo " " >> $fstate + mon=$(( ( RANDOM % 10 ) )) + day=$(( ( RANDOM % 10 ) )) + hour=$(( ( RANDOM % 10 ) )) + echo " 2020-0$mon-0$dayT0$hour:48:11Z" >> $fstate + echo " bob" >> $fstate + ip1=$(( ( RANDOM % 255 ) )) + ip2=$(( ( RANDOM % 255 ) )) + echo " 192.168.$ip1.$ip2" >> $fstate + echo " POST" >> $fstate + echo " true" >> $fstate + echo " " >> $fstate +done +echo -n "" >> $fstate # No CR + # Run limit-only test with netconf, restconf+xml and restconf+json # Args: # 1. offset @@ -278,6 +251,7 @@ function testlimit() jsonlist="" # for restconf json jsonmeta="" let i=0 + for li in $list; do if [ $i = 0 ]; then if [ $limit == 0 ]; then @@ -315,19 +289,40 @@ function testlimit() jsonstr="${jsonstr}&offset=$offset" fi fi - new "limit=$limit NETCONF get-config" -# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limitxmlstr$offsetxmlstr]]>]]>" "alicepublic$xmllist]]>]]>$" - new "limit=$limit NETCONF get" -# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limitxmlstr$offsetxmlstr]]>]]>" "alicepublic$xmllist]]>]]>$" + if [ -z "$list" ]; then + reply="]]>]]>$" + else + reply="alicepublic$xmllist]]>]]>$" + fi + new "limit=$limit offset=$offset NETCONF get-config" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limitxmlstr$offsetxmlstr]]>]]>" "$reply" - new "limit=$limit Parameter RESTCONF xml" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "$xmllist2" + if [ -z "$list" ]; then + reply="]]>]]>$" + else + reply="alicepublic$xmllist]]>]]>$" + fi + new "limit=$limit offset=$offset NETCONF get" + expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOtrue$limitxmlstr$offsetxmlstr]]>]]>" "$reply" - new "limit=$limit Parameter RESTCONF json" - expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "{\"yang-collection\":{\"example-social:uint8-numbers\":\[$jsonlist\]$jsonmeta}" + if [ -z "$list" ]; then + reply="" + else + reply="$xmllist2" + fi + new "limit=$limit offset=$offset Parameter RESTCONF xml" + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "$reply" -} # testrunf + if [ -z "$list" ]; then + reply="{\"yang-collection\":{}}" + else + reply="{\"yang-collection\":{\"example-social:uint8-numbers\":\[$jsonlist\]$jsonmeta}" + fi + new "limit=$limit offset=$offset Parameter RESTCONF json" + expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "$reply" + +} # testlimit new "test params: -f $cfg -s startup -- -sS $fstate" @@ -357,6 +352,7 @@ fi new "wait restconf" wait_restconf + new "A.3.1.1. limit=1" testlimit 0 1 5 "17" @@ -381,12 +377,12 @@ testlimit 2 0 0 "11 7 5 3" new "A.3.2.3. offset=5" testlimit 5 0 0 "3" -#new "A.3.2.4. offset=6" -#testlimit 6 0 0 "" +new "A.3.2.4. offset=6" +testlimit 6 0 0 "" # This is incomplete wrt the draft new "A.3.7. limit=2 offset=2" -testlimit 2 2 2 "11 7" +testlimitn 2 2 2 "11 7" # CLI # XXX This relies on a very specific clispec command: need a more generic test @@ -415,6 +411,7 @@ fi unset RESTCONFIG unset validatexml +unset perfnr rm -rf $dir diff --git a/test/test_xpath_canonical.sh b/test/test_xpath_canonical.sh index d339d31c..d869fb9e 100755 --- a/test/test_xpath_canonical.sh +++ b/test/test_xpath_canonical.sh @@ -57,10 +57,10 @@ new "xpath canonical form descendants" expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"' new "xpath canonical form (no default should fail)" -expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255 +expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2>&1)" 0 "/x/j:y: No namespace found for prefix" new "xpath canonical form (wrong namespace should fail)" -expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>/dev/null)" 255 +expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>&1)" 0 "/i:x/j:y: No modules found for namespace" rm -rf $dir diff --git a/util/clixon_util_xpath.c b/util/clixon_util_xpath.c index a0ec6368..ec611f0a 100644 --- a/util/clixon_util_xpath.c +++ b/util/clixon_util_xpath.c @@ -281,8 +281,14 @@ main(int argc, if (canonical){ char *xpath1 = NULL; cvec *nsc1 = NULL; - if (xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1) < 0) + cbuf *cbreason = NULL; + + if ((ret = xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1, &cbreason)) < 0) goto done; + if (ret == 0){ + fprintf(stderr, "Error with %s: %s", xpath, cbuf_get(cbreason)); + goto ok; + } xpath = xpath1; if (xpath) fprintf(stdout, "%s\n", xpath); diff --git a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang index 1bdd138e..0acb3add 100644 --- a/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang +++ b/yang/clixon/clixon-netconf-list-pagination@2021-08-27.yang @@ -105,9 +105,9 @@ module clixon-netconf-list-pagination { default "unbounded"; description "The maximum number of list entries to return. The - value of the 'limit' parameter is either an integer - greater than or equal to 1, or the string 'unbounded'. - The string 'unbounded' is the default value."; + value of the 'limit' parameter is either an integer + greater than or equal to 1, or the string 'unbounded'. + The string 'unbounded' is the default value."; } leaf offset { type union { @@ -119,9 +119,9 @@ module clixon-netconf-list-pagination { default "none"; description "The first list item to return. - the 'offset' parameter is either an integer greater than - or equal to 1, or the string 'unbounded'. The string - 'unbounded' is the default value."; + the 'offset' parameter is either an integer greater than + or equal to 1, or the string 'unbounded'. The string + 'none' is the default value."; } leaf direction { type enumeration {