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