diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 34b2b959..8237fddd 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -304,51 +304,38 @@ element2value(clicon_handle h, } return 1; } -#endif -/*! Common get/get-config code for retrieving configuration and state information. +/*! Specialized get for list-pagination * + * It is specialized enough to have its own function. Specifically, extra attributes as well + * as the list-paginaiton API * @param[in] h Clicon handle * @param[in] xe Request: * @param[in] content Get config/state/both * @param[in] db Database name + * @param[in] xpath + * @param[in] nsc * @param[out] cbret Return xml tree, eg ..., - * The set of namespace declarations are those in scope on the - * element. - */ - else - if (xml_nsctx_node(xfilter, &nsc) < 0) - goto done; - 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; - } - /* Clixon extensions: depth */ - if ((attr = xml_find_value(xe, "depth")) != NULL){ - if ((ret = parse_int32(attr, &depth, &reason)) < 0){ - clicon_err(OE_XML, errno, "parse_int32"); - goto done; - } - if (ret == 0){ - if (netconf_bad_attribute(cbret, "application", - "depth", "Unrecognized value of depth attribute") < 0) - goto done; - goto ok; - } - } -#ifdef LIST_PAGINATION - /* Check if list pagination */ - if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL && - (valstr = xml_body(x)) != NULL && - strcmp(valstr,"true")==0) - list_pagination = 1; - /* Sanity check for list pagination: path must be a list/leaf-list, if it is, - * check config/state - */ - if (list_pagination){ - /* Check if list/leaf-list */ - if (yang_path_arg(yspec, xpath, &ylist) < 0) - goto done; - if (ylist == NULL){ - 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(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(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(cbret, "application", "list-pagination targets a state list but content request is config") < 0) - goto done; - goto ok; - } - } - /* 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); - if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ - if (netconf_bad_attribute(cbret, "application", - "direction", "Unrecognized value of direction attribute") < 0) - goto done; - goto ok; - } - } - /* sort */ - if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL) - sort = xml_body(x); - if (sort) ; /* XXX */ - /* where */ - if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) - where = xml_body(x); - /* Build a "predicate" cbuf - * This solution uses xpath predicates to translate "limit" and "offset" to - * relational operators <>. - */ - if ((cbpath = cbuf_new()) == NULL){ + /* Check if list/leaf-list */ + if (yang_path_arg(yspec, xpath, &ylist) < 0) + goto done; + if (ylist == NULL){ + if ((cbmsg = cbuf_new()) == NULL){ clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; } - /* This uses xpath. Maybe limit should use parameters */ - if (xpath) - cprintf(cbpath, "%s", xpath); - else - cprintf(cbpath, "/"); - if (where) - cprintf(cbpath, "[%s]", where); - if (offset){ - cprintf(cbpath, "[%u <= position()", offset); - if (limit) - cprintf(cbpath, " and position() < %u", limit+offset); - cprintf(cbpath, "]"); + /* 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(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(cbret, "application", "list-pagination targets a config list but content request is nonconfig") < 0) + goto done; + goto ok; } - else if (limit) - cprintf(cbpath, "[position() < %u]", limit); - /* Get total/remaining - * XXX: Maybe together with get config / state data - */ - if (list_config){ - if ((xcache = xmldb_cache_get(h, db)) != NULL){ - if (xpath_count(xcache, nsc, xpath, &total) < 0) - goto done; - if (total >= (offset + limit)) - remaining = total - (offset + limit); - } + } + else { /* state list */ + if (content == CONTENT_CONFIG){ + if (netconf_invalid_value(cbret, "application", "list-pagination targets a state list but content request is config") < 0) + goto done; + goto ok; } - else{ - /* Remaining of state list. Two strategies: - * 1. New api where state callback is registered, lock, iterative, unlock - * 2. Read all here and count (fallback) - */ - +#if 1 /* XXX For now state lists are not implemenetd */ + if (netconf_operation_not_supported(cbret, "protocol", "List pagination for state lists is not yet implemented") < 0) + goto done; + goto ok; +#endif + } + /* offset */ + if ((ret = element2value(h, xe, "offset", "none", cbret, &offset)) < 0) + goto done; + /* limit */ + if (ret && (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); + if (strcmp(direction, "forward") != 0 && strcmp(direction, "reverse") != 0){ + if (netconf_bad_attribute(cbret, "application", + "direction", "Unrecognized value of direction attribute") < 0) + goto done; + goto ok; } - /* Append predicate to original xpath and replace it */ - xpath2 = cbuf_get(cbpath); - } /* list_pagination */ + } + /* sort */ + if (ret && (x = xml_find_type(xe, NULL, "sort", CX_ELMNT)) != NULL) + sort = xml_body(x); + if (sort) ; /* XXX */ + /* where */ + if (ret && (x = xml_find_type(xe, NULL, "where", CX_ELMNT)) != NULL) + where = xml_body(x); + /* Build a "predicate" cbuf + * This solution uses xpath predicates to translate "limit" and "offset" to + * relational operators <>. + */ + if ((cbpath = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* This uses xpath. Maybe limit should use parameters */ + if (xpath) + cprintf(cbpath, "%s", xpath); else - xpath2 = xpath; -#endif /* LIST_PAGINATION */ - /* Read configuration */ + cprintf(cbpath, "/"); + if (where) + cprintf(cbpath, "[%s]", where); + if (offset){ + cprintf(cbpath, "[%u <= position()", offset); + if (limit) + cprintf(cbpath, " and position() < %u", limit+offset); + cprintf(cbpath, "]"); + } + else if (limit) + cprintf(cbpath, "[position() < %u]", limit); + /* Get total/remaining + * XXX: Maybe together with get config / state data + */ + if (list_config){ + if ((xcache = xmldb_cache_get(h, db)) != NULL){ + if (xpath_count(xcache, nsc, xpath, &total) < 0) + goto done; + if (total >= (offset + limit)) + remaining = total - (offset + limit); + } + } + else{ + /* 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); + switch (content){ case CONTENT_CONFIG: /* config data only */ /* specific xpath */ @@ -574,6 +512,289 @@ get_common(clicon_handle h, goto done; break; }/* switch content */ + if (content != CONTENT_CONFIG && + clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Check XML by validating it. return internal error with error cause + * Primarily intended for user-supplied state-data. + * The whole config tree must be present in case the state data references config data + */ + if ((ret = xml_yang_validate_all_top(h, xret, &xerr)) < 0) + goto done; + if (ret > 0 && + (ret = xml_yang_validate_add(h, xret, &xerr)) < 0) + goto done; + if (ret == 0){ + if (clicon_debug_get()) + clicon_log_xml(LOG_DEBUG, xret, "VALIDATE_STATE"); + if (clixon_netconf_internal_error(xerr, + ". Internal error, state callback returned invalid XML", + NULL) < 0) + goto done; + if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) + goto done; + goto ok; + } + } /* CLICON_VALIDATE_STATE_XML */ + + if (content == CONTENT_NONCONFIG){ /* state only, all config should be removed now */ + /* Keep state data only, remove everything that is config. Note that state data + * may be a sub-part in a config tree, we need to traverse to find all + */ + if (xml_non_config_data(xret, NULL) < 0) + goto done; + if (xml_tree_prune_flagged_sub(xret, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + if (xml_apply(xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + goto done; + } + /* Code complex to filter out anything that is outside of xpath + * Actually this is a safety catch, should really be done in plugins + * and modules_state functions. + * But it is problematic, because defaults, at least of config data, is in place + * and we need to re-add it. + * Note original xpath + */ + if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; i", NETCONF_BASE_NAMESPACE); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, NETCONF_OUTPUT_DATA) < 0) + goto done; + /* Top level is data, so add 1 to depth if significant */ + if (clicon_xml2cbuf(cbret, xret, 0, 0, depth>0?depth+1:depth) < 0) + goto done; + } + cprintf(cbret, ""); + ok: + retval = 0; + done: + if (cbmsg) + cbuf_free(cbmsg); + if (cbpath) + cbuf_free(cbpath); + if (xerr) + xml_free(xerr); + if (xvec) + free(xvec); + if (xvecnacm) + free(xvecnacm); + if (xret) + xml_free(xret); + return retval; +} +#endif /* LIST_PAGINATION */ + +/*! Common get/get-config code for retrieving configuration and state information. + * + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[in] content Get config/state/both + * @param[in] db Database name + * @param[out] cbret Return xml tree, eg ..., + * The set of namespace declarations are those in scope on the + * element. + */ + else + if (xml_nsctx_node(xfilter, &nsc) < 0) + goto done; + 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; + } + nsc = nsc1; + } + /* Clixon extensions: depth */ + if ((attr = xml_find_value(xe, "depth")) != NULL){ + if ((ret = parse_int32(attr, &depth, &reason)) < 0){ + clicon_err(OE_XML, errno, "parse_int32"); + goto done; + } + if (ret == 0){ + if (netconf_bad_attribute(cbret, "application", + "depth", "Unrecognized value of depth attribute") < 0) + goto done; + goto ok; + } + } +#ifdef LIST_PAGINATION + /* Check if list pagination */ + if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL && + (valstr = xml_body(x)) != NULL && + strcmp(valstr,"true")==0) + list_pagination = 1; + /* Sanity check for list pagination: path must be a list/leaf-list, if it is, + * check config/state + */ + if (list_pagination){ + if (get_list_pagination(h, xe, content, db, + depth, yspec, xpath, nsc, username, + cbret) < 0) + goto done; + goto ok; + } +#endif /* LIST_PAGINATION */ + /* Read configuration */ + switch (content){ + case CONTENT_CONFIG: /* config data only */ + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + break; + case CONTENT_ALL: /* both config and state */ + case CONTENT_NONCONFIG: /* state data only */ + if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){ + /* Whole config tree, for validate debug */ + if (xmldb_get0(h, "running", YB_MODULE, nsc, NULL, 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + else if (content == CONTENT_ALL){ + /* specific xpath */ + if (xmldb_get0(h, db, YB_MODULE, nsc, xpath?xpath:"/", 1, &xret, NULL, NULL) < 0) { + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "Get %s datastore: %s", db, clicon_err_reason); + if (netconf_operation_failed(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto ok; + } + } + /* CONTENT_NONCONFIG */ + else if ((xret = xml_new(DATASTORE_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)/* Only top tree */ + goto done; + break; + }/* switch content */ /* If not only config, * get state data from plugins as defined by plugin_statedata(), if any */ @@ -583,7 +804,7 @@ get_common(clicon_handle h, break; case CONTENT_ALL: /* both config and state */ case CONTENT_NONCONFIG: /* state data only */ - if ((ret = client_statedata(h, xpath2?xpath2:"/", nsc, &xret)) < 0) + if ((ret = client_statedata(h, xpath?xpath:"/", nsc, &xret)) < 0) goto done; if (ret == 0){ /* Error from callback (error in xret) */ if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) @@ -659,34 +880,6 @@ get_common(clicon_handle h, /* Add default recursive values */ if (xml_default_recurse(xret, 0) < 0) goto done; - -#ifdef LIST_PAGINATION - /* Add remaining attribute Sec 3.1.5: - Any list or leaf-list that is limited includes, on the first element in the result set, - a metadata value [RFC7952] called "remaining"*/ - if (list_pagination && limit && xlen){ - cxobj *xa; - cbuf *cba = NULL; - - /* Add remaining attribute */ - x = xvec[0]; - if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL) - goto done; - if ((cba = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cba, "%u", remaining); - if (xml_value_set(xa, cbuf_get(cba)) < 0) - goto done; - if (xml_prefix_set(xa, "cp") < 0) - goto done; - if (xmlns_set(x, "cp", "http://clicon.org/clixon-netconf-list-pagination") < 0) - goto done; - if (cba) - cbuf_free(cba); - } -#endif /* LIST_PAGINATION */ /* Pre-NACM access step */ xnacm = clicon_nacm_cache(h); if (xnacm != NULL){ /* Do NACM validation */ @@ -711,10 +904,10 @@ get_common(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); -#ifdef LIST_PAGINATION - if (cbpath) - cbuf_free(cbpath); -#endif + if (cbreason) + cbuf_free(cbreason); + if (nsc) + xml_nsctx_free(nsc); if (cbmsg) cbuf_free(cbmsg); if (reason) @@ -727,8 +920,6 @@ get_common(clicon_handle h, free(xvec); if (xvecnacm) free(xvecnacm); - if (nsc) - xml_nsctx_free(nsc); if (xret) xml_free(xret); return retval; diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index ad5e3fc1..fc75c9a1 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -1312,99 +1312,3 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv) return cligen_help(ch, stdout, pt); } -#ifdef LIST_PAGINATION -/*! Show pagination/collection - * 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: - * Also, if there is a cligen variable called "xpath" it will override argv xpath arg - */ -int -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; - enum format_enum format; - 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; - } - /* 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 */ - if ((int)(format = format_str2int(formatstr)) < 0){ - clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); - goto done; - } - if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL) - goto done; - 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) - xml_free(xret); - if (cb) - cbuf_free(cb); - return retval; -} -#else -int -cli_pagination(clicon_handle h, cvec *cvv, cvec *argv) -{ - fprintf(stderr, "Not yet implemented\n"); - return 0; -} -#endif /* LIST_PAGINATION */ - diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h index 28390c02..4c967997 100644 --- a/apps/cli/cli_common.h +++ b/apps/cli/cli_common.h @@ -42,7 +42,7 @@ void cli_signal_block(clicon_handle h); void cli_signal_unblock(clicon_handle h); -/* If you do not find a function here it may be in clicon_cli_api.h which is +/* If you do not find a function here it may be in clixon_cli_api.h which is the external API */ #endif /* _CLI_COMMON_H_ */ diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index e7e72210..c47bb1ee 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -891,3 +891,118 @@ cli_show_options(clicon_handle h, return retval; } +#ifdef LIST_PAGINATION + +/*! Show pagination + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @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 *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 *str; + enum format_enum format; + cxobj *xc; + cg_var *cv; + int i; + int j; + int window = 0; + cxobj **xvec = NULL; + size_t xlen; + + if (cvec_len(argv) != 5){ + clicon_err(OE_PLUGIN, 0, "Expected usage: "); + goto done; + } + /* 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); + str = cv_string_get(cvec_i(argv, 3)); /* Fourthformat: output format */ + if ((int)(format = format_str2int(str)) < 0){ + clicon_err(OE_PLUGIN, 0, "Not valid format: %s", str); + goto done; + } + if ((str = cv_string_get(cvec_i(argv, 4))) != NULL){ + if (parse_int32(str, &window, NULL) < 1){ + clicon_err(OE_UNIX, errno, "error parsing window integer:%s", str); + goto done; + } + } + if (window == 0){ + clicon_err(OE_UNIX, EINVAL, "window is 0"); + goto done; + } + if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL) + goto done; + for (i = 0;; i++){ + if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc, + CONTENT_ALL, + -1, /* depth */ + window*i, /* offset */ + window, /* 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; + } + if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath) < 0) + goto done; + for (j = 0; j("XPATH expression"){ + xml, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "xml", "10"); + cli, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "cli", "10"); + text, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "text", "10"); + json, cli_pagination("use xpath var", "es", "http://example.com/ns/example-social", "json", "10"); + } configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false); cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", true, false, "set "); diff --git a/lib/src/clixon_xpath_yang.c b/lib/src/clixon_xpath_yang.c index 3355c445..4aafb2c8 100644 --- a/lib/src/clixon_xpath_yang.c +++ b/lib/src/clixon_xpath_yang.c @@ -109,7 +109,7 @@ xy_dup(xp_yang_ctx *xy0) * Always evaluates to true since there are no instances */ static int -xp_yang_op_eq(xp_yang_ctx *xy1, +xp_yang_op_eq(xp_yang_ctx *xy1, xp_yang_ctx *xy2, xp_yang_ctx **xyr) { @@ -118,8 +118,8 @@ xp_yang_op_eq(xp_yang_ctx *xy1, if ((xy = xy_dup(xy1)) == NULL) goto done; - if(xy1 == NULL || xy2 == NULL || xy1->xy_node == NULL || xy2->xy_node == NULL){ - clicon_err(OE_YANG, EINVAL, "Error in xy1 or xy2 "); + if (xy1 == NULL || xy2 == NULL || xy1->xy_node == NULL || xy2->xy_node == NULL){ + clicon_err(OE_YANG, EINVAL, "Invalid path-arg (Error in xy1 or xy2) "); goto done; } xy->xy_type = XT_BOOL; diff --git a/test/test_pagination_config.sh b/test/test_pagination_config.sh new file mode 100755 index 00000000..6c32fe6e --- /dev/null +++ b/test/test_pagination_config.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# List pagination tests loosely based on draft-wwlh-netconf-list-pagination-00 +# The example-social yang file is used +# This tests contains a large config list: members/member/favorites/uint8-numbers + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +echo "...skipped: Must run interactvely" +if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +fexample=$dir/example-social.yang + +# Common example-module spec (fexample must be set) +. ./example_social.sh + +# Validate internal state xml +: ${validatexml:=false} + +# Number of audit-log entries +#: ${perfnr:=20000} +: ${perfnr:=200} + +cat < $cfg + + $cfg + ietf-netconf:startup + clixon-restconf:allow-auth-none + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + $dir/restconf.pidfile + $dir + xml + true + $APPNAME + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + $validatexml + +EOF + +# Based on draft-wwlh-netconf-list-pagination-00 A.2 but bob has a generated uint8-numbers list +# start file +cat <<'EOF' > $dir/startup_db + + + + alice + alice@example.com + $0$1543 + BASE64VALUE= + Every day is a new day + + false + public + + bob + eric + lin + + + 2020-07-08T13:12:45Z + My first post + Hiya all! + + + 2020-07-09T01:32:23Z + Sleepy... + Catch y'all tomorrow. + + + + 17 + 13 + 11 + 7 + 5 + 3 + -5 + -3 + -1 + 1 + 3 + 5 + + + + bob + bob@example.com + $0$1543 + BASE64VALUE= + Here and now, like never before. + + public + + + + 2020-08-14T03:32:25Z + Just got in. + + + 2020-08-14T03:33:55Z + What's new? + + + 2020-08-14T03:34:30Z + I'm bored... + + + +EOF + +new "generate config with $perfnr leaf-list entries" +for (( i=0; i<$perfnr; i++ )); do + echo " $i" >> $dir/startup_db +done + +# end file +cat <<'EOF' >> $dir/startup_db + -9 + 2 + + + + +EOF + +new "test params: -f $cfg -s startup" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +# XXX How to run without using a terminal? +new "cli show" +$clixon_cli -f $cfg -l o -1 show pagination xpath /es:members/es:member[es:member-id=\'bob\']/es:favorites/es:uint64-numbers cli +#expectpart "$(echo "show pagination xpath /es:members/es:member[es:member-id=\'bob\']/es:favorites/es:uint64-numbers cli" | $clixon_cli -f $cfg -l o)" 0 foo +#expectpart "$($clixon_cli -1 -f $cfg -l o show pagination xpath /es:members/es:member[es:member-id=\'bob\']/es:favorites/es:uint64-numbers cli)" 0 foo + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +unset validatexml +unset perfnr + +rm -rf $dir + +new "endtest" +endtest diff --git a/test/test_pagination.sh b/test/test_pagination_draft.sh similarity index 85% rename from test/test_pagination.sh rename to test/test_pagination_draft.sh index ea1454b4..6e8b86e0 100755 --- a/test/test_pagination.sh +++ b/test/test_pagination_draft.sh @@ -1,9 +1,7 @@ #!/usr/bin/env bash # List pagination tests according to draft-wwlh-netconf-list-pagination-00 -# Backlog items: -# 1. "remaining" annotation RFC 7952 -# 2. pattern '.*[\n].*' { modifier invert-match; -# XXX: handling of empty return ? +# Follow the example-social example in the draft and the tests in Appendix A.2 + A.3.1/A.3.2 +# Basically only offset and limit supported # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -23,10 +21,6 @@ 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 @@ -211,27 +205,59 @@ 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 @@ -382,16 +408,7 @@ testlimit 6 0 0 "" # This is incomplete wrt the draft new "A.3.7. limit=2 offset=2" -testlimitn 2 2 2 "11 7" - -# CLI -# XXX This relies on a very specific clispec command: need a more generic test -#new "cli show" -#expectpart "$($clixon_cli -1 -f $cfg -l o show pagination)" 0 "" "Conflict Resolution" "93" "Management" "23" --not-- "Organization" "44" - -# draft-wwlh-netconf-list-pagination-rc-00.txt -#new "A.1. 'count' Parameter RESTCONF" -#expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-module:get-list-pagination/library/artist=Foo%20Fighters/album/?count=2)" 0 "HTTP/1.1 200 OK" "application/yang-collection+xml" 'Crime and Punishment1995One by One2002' +testlimit 2 2 2 "11 7" if [ $RC -ne 0 ]; then new "Kill restconf daemon" @@ -411,7 +428,6 @@ fi unset RESTCONFIG unset validatexml -unset perfnr rm -rf $dir diff --git a/test/test_pagination_state.sh b/test/test_pagination_state.sh new file mode 100755 index 00000000..0053f5f4 --- /dev/null +++ b/test/test_pagination_state.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# List pagination tests loosely based on draft-wwlh-netconf-list-pagination-00 +# The example-social yang file is used +# This tests contains a large state list: audit-logs from the example +# Only CLI is used + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml +fexample=$dir/example-social.yang +fstate=$dir/mystate.xml + +# Common example-module spec (fexample must be set) +. ./example_social.sh + +# Validate internal state xml +: ${validatexml:=false} + +# Number of audit-log entries +#: ${perfnr:=20000} +: ${perfnr:=200} + +cat < $cfg + + $cfg + ietf-netconf:startup + clixon-restconf:allow-auth-none + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + $dir/restconf.pidfile + $dir + json + true + $APPNAME + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + $validatexml + +EOF + +# See draft-wwlh-netconf-list-pagination-00 A.2 (only stats and audit-log) +# XXX members not currently used, only audit-logs as generated below +cat< $fstate + + + alice + + 2020-07-08T12:38:32Z + admin + 2021-04-01T02:51:11Z + + + + bob + + 2020-08-14T03:30:00Z + standard + 2020-08-14T03:34:30Z + + + + eric + + 2020-09-17T19:38:32Z + pro + 2020-09-17T18:02:04Z + + + + lin + + 2020-07-09T12:38:32Z + standard + 2021-04-01T02:51:11Z + + + + joe + + 2020-10-08T12:38:32Z + pro + 2021-04-01T02:51:11Z + + + +EOF + +# Append generated state data to $fstate file +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 + +new "test params: -f $cfg -s init -- -sS $fstate" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s init -f $cfg -- -sS $fstate" + start_backend -s init -f $cfg -- -sS $fstate +fi + +new "wait backend" +wait_backend + +new "cli show" +expectpart "$($clixon_cli -1 -f $cfg -l o show pagination xpath /es:audit-logs/es:audit-log cli)" 255 "Get configuration: protocol operation-not-supported List pagination for state lists is not yet implemented" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +unset validatexml +unset perfnr + +rm -rf $dir + +new "endtest" +endtest