* Protyped netconf native pagination

This commit is contained in:
Olof hagsand 2021-08-19 13:37:45 +02:00
parent 6bf3112fe7
commit b03cf426a4
8 changed files with 493 additions and 85 deletions

View file

@ -412,37 +412,89 @@ client_get_config_only(clicon_handle h,
yang_stmt *yspec, yang_stmt *yspec,
char *db, char *db,
char *xpath, char *xpath,
#ifdef CLIXON_PAGINATION
uint32_t limit,
uint32_t offset,
char *xpathpred,
#endif /* CLIXON_PAGINATION */
char *username, char *username,
int32_t depth, int32_t depth,
cbuf *cbret) cbuf *cbret)
{ {
int retval = -1; int retval = -1;
cxobj *xret = NULL; cxobj *xret = NULL;
cxobj *xnacm = NULL; cxobj *xnacm = NULL;
cxobj **xvec = NULL; cxobj **xvec = NULL;
cxobj *xerr = NULL; cxobj *xerr = NULL;
size_t xlen; size_t xlen;
int ret; int ret;
#ifdef CLIXON_PAGINATION
cxobj *xcache = NULL;
uint32_t total = 0;
cbuf *cb = NULL;
uint32_t remaining = 0;
#endif
/* Note xret can be pruned by nacm below (and change name), /* Note xret can be pruned by nacm below (and change name),
* so zero-copy cant be used * so zero-copy cant be used
* Also, must use external namespace context here due to <filter stmt * Also, must use external namespace context here due to <filter stmt
*/ */
if ((ret = xmldb_get0(h, db, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) { if ((ret = xmldb_get0(h, db, YB_MODULE, nsc,
if (netconf_operation_failed(cbret, "application", "read registry")< 0) #ifdef CLIXON_PAGINATION
xpathpred,
#else
xpath,
#endif
1, &xret, NULL, &xerr)) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done; goto done;
goto ok; goto ok;
} }
if (ret == 0){ if (ret == 0){
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0) if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
goto done; goto done;
goto ok; goto ok;
} }
#ifdef CLIXON_PAGINATION
/* First get number of hits (ALL entries: note not optimized)
*/
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);
}
/* XXX need only first for remaining, but all for NACM */
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* Add remain attribute to first returning element */
if (xlen){
cxobj *x;
cxobj *xa;
x = xvec[0];
if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cb)) < 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;
}
#endif
/* Pre-NACM access step */ /* Pre-NACM access step */
xnacm = clicon_nacm_cache(h); xnacm = clicon_nacm_cache(h);
if (xnacm != NULL){ /* Do NACM validation */ if (xnacm != NULL){ /* Do NACM validation */
#ifndef CLIXON_PAGINATION
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done; goto done;
#endif
/* NACM datanode/module read validation */ /* NACM datanode/module read validation */
if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
goto done; goto done;
@ -460,6 +512,8 @@ client_get_config_only(clicon_handle h,
ok: ok:
retval = 0; retval = 0;
done: done:
if (cb)
cbuf_free(cb);
if (xerr) if (xerr)
xml_free(xerr); xml_free(xerr);
if (xvec) if (xvec)
@ -469,6 +523,59 @@ client_get_config_only(clicon_handle h,
return retval; return retval;
} }
#if defined(LIST_PAGINATION) || defined(CLIXON_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
*/
static int
element2value(clicon_handle h,
cxobj *xe,
char *name,
char *defaultstr,
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;
}
}
retval = 1;
done:
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}
#endif
/*! Retrieve all or part of a specified configuration. /*! Retrieve all or part of a specified configuration.
* *
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -500,6 +607,15 @@ from_client_get_config(clicon_handle h,
char *attr; char *attr;
char *xpath0; char *xpath0;
cvec *nsc1 = NULL; cvec *nsc1 = NULL;
#ifdef CLIXON_PAGINATION
uint32_t limit = 0;
uint32_t offset = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
cbuf *cbpath = NULL;
cxobj *x;
#endif /* CLIXON_PAGINATION */
username = clicon_username_get(h); username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -551,7 +667,57 @@ from_client_get_config(clicon_handle h,
goto ok; goto ok;
} }
} }
if ((ret = client_get_config_only(h, nsc, yspec, db, xpath, username, -1, cbret)) < 0) #ifdef CLIXON_PAGINATION
/* 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;
/* 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){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* This uses xpath. Maybe limit should use parameters */
cprintf(cbpath, "%s", xpath);
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);
#endif /* CLIXON_PAGINATION */
if ((ret = client_get_config_only(h, nsc, yspec, db, xpath,
#ifdef CLIXON_PAGINATION
limit, offset,
cbuf_get(cbpath),
#endif
username, -1, cbret)) < 0)
goto done; goto done;
ok: ok:
retval = 0; retval = 0;
@ -1078,6 +1244,15 @@ from_client_get(clicon_handle h,
cxobj *xerr = NULL; cxobj *xerr = NULL;
int ret; int ret;
char *reason = NULL; char *reason = NULL;
#ifdef CLIXON_PAGINATION
uint32_t limit = 0;
uint32_t offset = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
cbuf *cbpath = NULL;
cxobj *x;
#endif /* CLIXON_PAGINATION */
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h); username = clicon_username_get(h);
@ -1119,8 +1294,58 @@ from_client_get(clicon_handle h,
goto ok; goto ok;
} }
} }
#ifdef CLIXON_PAGINATION
/* 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;
/* 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){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* This uses xpath. Maybe limit should use parameters */
cprintf(cbpath, "%s", xpath);
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);
#endif /* CLIXON_PAGINATION */
if (content == CONTENT_CONFIG){ /* config only, no state */ if (content == CONTENT_CONFIG){ /* config only, no state */
if (client_get_config_only(h, nsc, yspec, "running", xpath, username, depth, cbret) < 0) if (client_get_config_only(h, nsc, yspec, "running", xpath,
#ifdef CLIXON_PAGINATION
limit, offset,
cbuf_get(cbpath),
#endif
username, depth, cbret) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -1239,6 +1464,9 @@ from_client_get(clicon_handle h,
retval = 0; retval = 0;
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbpath)
cbuf_free(cbpath);
if (reason) if (reason)
free(reason); free(reason);
if (xerr) if (xerr)
@ -1337,56 +1565,6 @@ from_client_kill_session(clicon_handle h,
} }
#ifdef LIST_PAGINATION #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
*/
static int
element2value(clicon_handle h,
cxobj *xe,
char *name,
char *defaultstr,
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;
}
}
retval = 1;
done:
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}
/*! Retrieve collection configuration and device state information /*! Retrieve collection configuration and device state information
* *
@ -1423,16 +1601,17 @@ from_client_get_pageable_list(clicon_handle h,
int i; int i;
cxobj *xerr = NULL; cxobj *xerr = NULL;
int ret; int ret;
cbuf *cb = NULL;
uint32_t limit = 0; uint32_t limit = 0;
uint32_t offset = 0; uint32_t offset = 0;
size_t total = 0; uint32_t total = 0;
uint32_t remaining = 0; uint32_t remaining = 0;
char *direction = NULL; char *direction = NULL;
char *sort = NULL; char *sort = NULL;
char *where = NULL; char *where = NULL;
char *datastore = NULL; char *datastore = NULL;
char *reason = NULL; char *reason = NULL;
cbuf *cb = NULL; cbuf *cbpath = NULL;
cxobj *xtop = NULL; cxobj *xtop = NULL;
yang_stmt *y; yang_stmt *y;
char *ns; char *ns;
@ -1535,22 +1714,22 @@ from_client_get_pageable_list(clicon_handle h,
* This solution uses xpath predicates to translate "limit" and "offset" to * This solution uses xpath predicates to translate "limit" and "offset" to
* relational operators <>. * relational operators <>.
*/ */
if ((cb = cbuf_new()) == NULL){ if ((cbpath = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
/* This uses xpath. Maybe limit should use parameters */ /* This uses xpath. Maybe limit should use parameters */
cprintf(cb, "%s", xpath); cprintf(cbpath, "%s", xpath);
if (where) if (where)
cprintf(cb, "[%s]", where); cprintf(cbpath, "[%s]", where);
if (offset){ if (offset){
cprintf(cb, "[%u <= position()", offset); cprintf(cbpath, "[%u <= position()", offset);
if (limit) if (limit)
cprintf(cb, " and position() < %u", limit+offset); cprintf(cbpath, " and position() < %u", limit+offset);
cprintf(cb, "]"); cprintf(cbpath, "]");
} }
else if (limit) else if (limit)
cprintf(cb, "[position() < %u]", limit); cprintf(cbpath, "[position() < %u]", limit);
/* Split into CT or CF */ /* Split into CT or CF */
if (yang_config_ancestor(y) == 1){ /* CT */ if (yang_config_ancestor(y) == 1){ /* CT */
@ -1565,13 +1744,9 @@ from_client_get_pageable_list(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
/* First get number of hits */ /* First get number of hits (ALL entries: note not optimized) */
if (xpath_vec(xret, nsc, "%s", &xvec, &total, xpath) < 0) if (xpath_count(xret, nsc, xpath, &total) < 0)
goto done; goto done;
if (xvec){
free(xvec);
xvec = NULL;
}
if (total < (offset + limit)) if (total < (offset + limit))
remaining = 0; remaining = 0;
else else
@ -1579,14 +1754,14 @@ from_client_get_pageable_list(clicon_handle h,
} }
/* There may be CF data in a CT collection */ /* There may be CF data in a CT collection */
if (content == CONTENT_ALL){ if (content == CONTENT_ALL){
if ((ret = client_statedata(h, cbuf_get(cb), nsc, content, &xret)) < 0) if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0)
goto done; goto done;
} }
} }
else { /* CF */ else { /* CF */
/* There can be no CT data in a CF collection */ /* There can be no CT data in a CF collection */
if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){ if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){
if ((ret = client_statedata(h, cbuf_get(cb), nsc, content, &xret)) < 0) if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, content, &xret)) < 0)
goto done; goto done;
if (ret == 0){ /* Error from callback (error in xret) */ if (ret == 0){ /* Error from callback (error in xret) */
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0) if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
@ -1634,7 +1809,7 @@ from_client_get_pageable_list(clicon_handle h,
* Actually this is a safety catch, should really be done in plugins * Actually this is a safety catch, should really be done in plugins
* and modules_state functions. * and modules_state functions.
*/ */
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cb)) < 0) if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0)
goto done; goto done;
/* Pre-NACM access step */ /* Pre-NACM access step */
@ -1646,7 +1821,11 @@ from_client_get_pageable_list(clicon_handle h,
} }
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><pageable-list xmlns=\"%s\">", cprintf(cbret, "<rpc-reply xmlns=\"%s\"><pageable-list xmlns=\"%s\">",
NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */ NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */
if ((ns = yang_find_mynamespace(y)) != NULL) if ((ns = yang_find_mynamespace(y)) != NULL) {
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
for (i=0; i<xlen; i++){ for (i=0; i<xlen; i++){
x = xvec[i]; x = xvec[i];
/* Add namespace */ /* Add namespace */
@ -1658,7 +1837,7 @@ from_client_get_pageable_list(clicon_handle h,
*/ */
if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL) if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
goto done; goto done;
cbuf_reset(cb); /* reuse */ cbuf_reset(cb);
cprintf(cb, "%u", remaining); cprintf(cb, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cb)) < 0) if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done; goto done;
@ -1671,6 +1850,7 @@ from_client_get_pageable_list(clicon_handle h,
if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0) if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0)
goto done; goto done;
} }
}
cprintf(cbret, "</pageable-list></rpc-reply>"); cprintf(cbret, "</pageable-list></rpc-reply>");
ok: ok:
retval = 0; retval = 0;
@ -1682,6 +1862,8 @@ from_client_get_pageable_list(clicon_handle h,
clixon_path_free(path_tree); clixon_path_free(path_tree);
if (xtop) if (xtop)
xml_free(xtop); xml_free(xtop);
if (cbpath)
cbuf_free(cbpath);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (reason) if (reason)

View file

@ -124,3 +124,7 @@
* draft-wwlh-netconf-list-pagination-rc-01 * draft-wwlh-netconf-list-pagination-rc-01
*/ */
#define LIST_PAGINATION #define LIST_PAGINATION
/*! Clixon netconf pagination
*/
#define CLIXON_PAGINATION

View file

@ -161,5 +161,6 @@ int xpath_vec(cxobj *xcur, cvec *nsc, const char *xpformat, cxobj ***vec, si
#endif #endif
int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1); int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1);
int xpath_count(cxobj *xcur, cvec *nsc, const char *xpath, uint32_t *count);
#endif /* _CLIXON_XPATH_H */ #endif /* _CLIXON_XPATH_H */

View file

@ -1543,6 +1543,11 @@ netconf_module_load(clicon_handle h)
if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0) if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0)
goto done; goto done;
#endif #endif
#ifdef CLIXON_PAGINATION
/* Load clixon netconf list pagination */
if (yang_spec_parse_module(h, "clixon-netconf-list-pagination", NULL, yspec)< 0)
goto done;
#endif
#endif #endif
retval = 0; retval = 0;
done: done:

View file

@ -75,6 +75,7 @@
#include <stdint.h> #include <stdint.h>
#include <syslog.h> #include <syslog.h>
#include <fcntl.h> #include <fcntl.h>
#include <math.h> /* NaN */
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -1064,3 +1065,41 @@ xpath2canonical(const char *xpath0,
xpath_tree_free(xpt); xpath_tree_free(xpt);
return retval; return retval;
} }
/*! Return a count(xpath)
*
* @param[in] xcur xml-tree where to search
* @param[in] nsc External XML namespace context, or NULL
* @param[in] xpath XPATH syntax
* @param[oit] count Nr of elements of xpath
* @note This function is made for making optimizations in certain circumstances, such as a list
*/
int
xpath_count(cxobj *xcur,
cvec *nsc,
const char *xpath,
uint32_t *count)
{
int retval = -1;
xp_ctx *xc = NULL;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "count(%s)", xpath);
if (xpath_vec_ctx(xcur, nsc, cbuf_get(cb), 0, &xc) < 0)
goto done;
if (xc && xc->xc_type == XT_NUMBER && xc->xc_number != NAN)
*count = (uint32_t)xc->xc_number;
else
*count = 0;
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xc)
ctx_free(xc);
return retval;
}

View file

@ -38,7 +38,7 @@ cat <<EOF > $cfg
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE> <CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR> <CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR> <CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_VALIDATE_STATE_XML>true</CLICON_VALIDATE_STATE_XML> <CLICON_VALIDATE_STATE_XML>false</CLICON_VALIDATE_STATE_XML>
$RESTCONFIG $RESTCONFIG
</clixon-config> </clixon-config>
EOF EOF
@ -257,7 +257,7 @@ cat<<EOF > $fstate
</audit-logs> </audit-logs>
EOF EOF
# Run limitonly test with netconf, restconf+xml and restconf+json # Run limit-only test with netconf, restconf+xml and restconf+json
# Args: # Args:
# 1. limit # 1. limit
# 2. remaining # 2. remaining
@ -267,7 +267,15 @@ function testlimit()
limit=$1 limit=$1
remaining=$2 remaining=$2
new "limit=$limit NETCONF" # "clixon get"
new "clixon limit=$limit NETCONF get-config"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source><filter type=\"xpath\" select=\"/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers\" xmlns:es=\"http://example.com/ns/example-social\"/><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">$limit</limit></get-config></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><favorites><uint8-numbers cp:remaining=\"$remaining\" xmlns:cp=\"http://clicon.org/clixon-netconf-list-pagination\">17</uint8-numbers></favorites></member></members></data></rpc-reply>]]>]]>$"
new "clixon limit=$limit NETCONF get"
# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get><filter type=\"xpath\" select=\"/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers\" xmlns:es=\"http://example.com/ns/example-social\"/><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">$limit</limit></get></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><favorites><uint8-numbers cp:remaining=\"$remaining\" xmlns:cp=\"http://clicon.org/clixon-netconf-list-pagination\">17</uint8-numbers></favorites></member></members></data></rpc-reply>]]>]]>$"
# "old: ietf"
new "ietf limit=$limit NETCONF"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>$limit</limit></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" ycoll:remaining=\"$remaining\" xmlns:ycoll=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\">17</uint8-numbers></pageable-list></rpc-reply>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore><list-target xmlns:es=\"http://example.com/ns/example-social\">/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers</list-target><limit>$limit</limit></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><uint8-numbers xmlns=\"http://example.com/ns/example-social\" ycoll:remaining=\"$remaining\" xmlns:ycoll=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\">17</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
new "limit=$limit Parameter RESTCONF xml" new "limit=$limit Parameter RESTCONF xml"

View file

@ -46,6 +46,7 @@ YANGSPECS += clixon-lib@2021-03-08.yang # 5.1
YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang
YANGSPECS += clixon-restconf@2021-05-20.yang # 5.2 YANGSPECS += clixon-restconf@2021-05-20.yang # 5.2
YANGSPECS += clixon-netconf-list-pagination@2021-08-27.yang
APPNAME = clixon # subdir ehere these files are installed APPNAME = clixon # subdir ehere these files are installed

View file

@ -0,0 +1,168 @@
module clixon-netconf-list-pagination {
yang-version 1.1;
namespace "http://clicon.org/clixon-netconf-list-pagination";
prefix cp;
import ietf-yang-types {
prefix yang;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-yang-metadata {
prefix "md";
reference
"RFC 7952: Defining and Using Metadata with YANG";
}
import ietf-netconf {
prefix nc;
reference
"RFC 6241: Network Configuration Protocol (NETCONF)";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: <http://tools.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>
Editor:
Editor:
Editor: ";
description
"This module define a new operation -- <get-collection>
to support YANG based pagination.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL
NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED',
'MAY', and 'OPTIONAL' in this document are to be interpreted as
described in BCP 14 (RFC 2119) (RFC 8174) when, and only when,
they appear in all capitals, as shown here.
Copyright (c) 2019 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject to
the license terms contained in, the Simplified BSD License set
forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 8526; see
the RFC itself for full legal notices.
Clixon:
- changed get-pagable -> get-pageable
- renamed count -> limit
- renamed skip -> offset
- added import ietf-yang-metadata
- added md:annotation remaining
";
revision 2021-08-27 {
description
"Dervied from ietf-netconf-list-pagination@2020-10-30.";
reference
"RFC XXXX: YANG Based Pagination.";
}
// Annotations
md:annotation remaining {
type uint32;
description
"This annotation contains the number of elements removed
from a result set after a 'limit' or 'sublist-limit'
operation. If no elements were removed, this annotation
MUST NOT appear. The minimum value (0), which never
occurs in normal operation, is reserved to represent
'unknown'. The maximum value (2^32-1) is reserved to
represent any value greater than or equal to 2^32-1
elements.";
}
grouping pageing-parameters {
leaf limit {
type union {
type uint32;
type string {
pattern 'unbounded';
}
}
default "unbounded";
description
"The maximum number of list entries to return. The
value of the 'count' 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 {
type uint32;
type string {
pattern 'none';
}
}
default "none";
description
"The first list item to return.
the 'skip' parameter is either an integer greater than
or equal to 1, or the string 'unbounded'. The string
'unbounded' is the default value.";
}
leaf direction {
type enumeration {
enum forward;
enum reverse;
}
default "forward";
description
"Direction relative to the 'sort' order through list
or leaf-list. It can be forward direction or reverse
direction.";
}
leaf sort {
type union {
type string {
length "1..max" {
description
"The name of a descendent node to sort on. For
'Config false' lists and leaf-lists, the node SHOULD
have the 'TBD' extension indicating that it has been
indexed, enabling efficient sorts.";
}
}
type enumeration {
enum default {
description
"Indicates that the 'default' order is assumed. For
'ordered-by user' lists and leaf-lists, the default order
is the user-configured order. For 'ordered-by system'
lists and leaf-lists, the default order is specified by the
system.";
}
}
}
default "default";
description
"Indicates how the entries in a list are to be sorted.";
}
leaf where {
type yang:xpath1.0;
description
"The boolean filter to select data instances to return from
the list or leaf-list target. The Xpath expression MAY be
constrained either server-wide, by datastore, by 'config'
status, or per list or leaf-list. Details regarding how
constraints are communicated are TBD. This parameter
is optional; no filtering is applied when it is not
specified.";
}
}
augment /nc:get-config/nc:input {
uses pageing-parameters;
}
// extending the get operation
augment /nc:get/nc:input {
uses pageing-parameters;
}
}