- Moved restrconf code from pageing rpc to get

This commit is contained in:
Olof hagsand 2021-08-24 17:35:18 +02:00
parent c9843b34a6
commit fb0b9409f3
15 changed files with 192 additions and 415 deletions

View file

@ -35,6 +35,18 @@ Expected: September, 2021
### New features
* List pageing for Netconf and Restconf
* Experimental, work-in-progress
* Enable with LIST_PAGINATION compile-time option
* According to:
* draft-wwlh-netconf-list-pagination-00.txt
* draft-wwlh-netconf-list-pagination-rc-01
* Added yangs:
* ietf-restconf-list-pagination@2015-01-30.yang
* clixon-netconf-list-pagination@2021-08-27.yang
* ietf-origin@2018-02-14.yang
* ietf-yang-metadata@2016-08-05.yang
* ietf-netconf-with-defaults@2011-06-01.yang
* YANG Leafref feature update
* Closer adherence to RFC 7950. Some of this is changed behavior, some is new feature.
* Essentially instead of looking at the referring leaf, context is referred(target) node

View file

@ -1396,12 +1396,6 @@ backend_rpc_init(clicon_handle h)
if (rpc_callback_register(h, from_client_validate, NULL,
NETCONF_BASE_NAMESPACE, "validate") < 0)
goto done;
#ifdef LIST_PAGINATION
/* draft-ietf-netconf-restconf-collection-00 */
if (rpc_callback_register(h, from_client_get_pageable_list, NULL,
NETCONF_COLLECTION_NAMESPACE, "get-pageable-list") < 0)
goto done;
#endif
/* In backend_client.? RPC from RFC 5277 */
if (rpc_callback_register(h, from_client_create_subscription, NULL,
EVENT_RFC5277_NAMESPACE, "create-subscription") < 0)

View file

@ -272,7 +272,7 @@ client_statedata(clicon_handle h,
goto done;
}
#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION)
#ifdef LIST_PAGINATION
/*! Help function for parsing restconf query parameter and setting netconf attribute
*
* If not "unbounded", parse and set a numeric value
@ -364,7 +364,7 @@ get_common(clicon_handle h,
int ret;
char *reason = NULL;
cbuf *cbmsg = NULL; /* For error msg */
#ifdef CLIXON_PAGINATION
#ifdef LIST_PAGINATION
uint32_t limit = 0;
uint32_t offset = 0;
uint32_t total = 0;
@ -379,7 +379,7 @@ get_common(clicon_handle h,
char *valstr;
yang_stmt *ylist;
cxobj *xcache = NULL;
#endif /* CLIXON_PAGINATION */
#endif /* LIST_PAGINATION */
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
@ -418,7 +418,7 @@ get_common(clicon_handle h,
goto ok;
}
}
#ifdef CLIXON_PAGINATION
#ifdef LIST_PAGINATION
/* Check if list pagination */
if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL &&
(valstr = xml_body(x)) != NULL &&
@ -525,7 +525,7 @@ get_common(clicon_handle h,
}
} /* list_pagination */
#endif /* CLIXON_PAGINATION */
#endif /* LIST_PAGINATION */
/* Read config
* XXX This seems unnecessary complex
*/
@ -661,7 +661,7 @@ get_common(clicon_handle h,
if (xml_default_recurse(xret, 0) < 0)
goto done;
#ifdef CLIXON_PAGINATION
#ifdef LIST_PAGINATION
/* Add remaining attribute */
if (list_pagination && remaining && xlen){
cxobj *xa;
@ -685,7 +685,7 @@ get_common(clicon_handle h,
if (cba)
cbuf_free(cba);
}
#endif /* CLIXON_PAGINATION */
#endif /* LIST_PAGINATION */
/* Pre-NACM access step */
xnacm = clicon_nacm_cache(h);
if (xnacm != NULL){ /* Do NACM validation */
@ -710,7 +710,7 @@ get_common(clicon_handle h,
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
#ifdef CLIXON_PAGINATION
#ifdef LIST_PAGINATION
if (cbpath)
cbuf_free(cbpath);
#endif
@ -790,319 +790,3 @@ from_client_get(clicon_handle h,
content = netconf_content_str2int(attr);
return get_common(h, xe, content, "running", cbret);
}
#ifdef LIST_PAGINATION
/*! Retrieve collection configuration and device state information
*
* @param[in] h Clicon handle
* @param[in] xe Request: <rpc><xn></rpc>
* @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error..
* @param[in] arg client-entry
* @param[in] regarg User argument given at rpc_callback_register()
* @retval 0 OK
* @retval -1 Error
*
* @see from_client_get
*/
int
from_client_get_pageable_list(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg,
void *regarg)
{
int retval = -1;
cxobj *x;
char *xpath = NULL;
cxobj *xret = NULL;
cxobj **xvec = NULL;
size_t xlen;
cxobj *xnacm = NULL;
char *username;
cvec *nsc = NULL; /* Create a netconf namespace context from filter */
char *attr;
netconf_content content = CONTENT_ALL;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
yang_stmt *yspec;
int i;
cxobj *xerr = NULL;
int ret;
cbuf *cb = NULL;
uint32_t limit = 0;
uint32_t offset = 0;
uint32_t total = 0;
uint32_t remaining = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
char *datastore = NULL;
char *reason = NULL;
cbuf *cbpath = NULL;
cxobj *xtop = NULL;
yang_stmt *y;
char *ns;
clixon_path *path_tree = NULL;
clixon_path *cp;
cxobj *xa; /* attribute */
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
/* Clixon extensions: content */
if ((attr = xml_find_value(xe, "content")) != NULL)
content = netconf_content_str2int(attr);
/* 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;
}
}
/* 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);
/* datastore */
if (ret && (x = xml_find_type(xe, NULL, "datastore", CX_ELMNT)) != NULL){
if (nodeid_split(xml_body(x), NULL, &datastore) < 0)
goto done;
}
if (ret == 0)
goto ok;
/* list-target, create (xml and) yang to check if is state (CF) or config (CT)
* is mandatory
*/
if (ret && (x = xml_find_type(xe, NULL, "list-target", CX_ELMNT)) != NULL){
xpath = xml_body(x);
if (xml_nsctx_node(x, &nsc) < 0)
goto done;
}
if (xpath == NULL){
clicon_err(OE_NETCONF, 0, "Missing list-target/xpath, is mandatory");
goto done;
}
if ((xtop = xml_new("top", NULL, CX_ELMNT)) == NULL)
goto done;
/* Parse xpath -> stuctured path tree */
if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0)
goto done;
if (ret == 0){
if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
goto done;
goto ok;
}
/* get last element of path, eg /a/b/c, get c */
if ((cp = PREVQ(clixon_path *, path_tree)) == NULL){
if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
goto done;
goto ok;
}
/* get yang of last element */
if ((y = cp->cp_yang) == NULL){
if (netconf_bad_element(cbret, "application", "list-target", "No yang associated with path") < 0)
goto done;
goto ok;
}
if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
if (netconf_bad_element(cbret, "application", "list-target", "path invalid") < 0)
goto done;
goto ok;
}
/* 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);
/* Split into CT or CF */
if (yang_config_ancestor(y) == 1){ /* CT */
if (content == CONTENT_CONFIG || content == CONTENT_ALL){
if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) {
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
if (ret == 0){
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
goto done;
goto ok;
}
/* First get number of hits (ALL entries: note not optimized) */
if (xpath_count(xret, nsc, xpath, &total) < 0)
goto done;
if (total < (offset + limit))
remaining = 0;
else
remaining = total - (offset + limit);
}
/* There may be CF data in a CT collection */
if (content == CONTENT_ALL){
if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0)
goto done;
}
}
else { /* CF */
/* There can be no CT data in a CF collection */
if (content == CONTENT_NONCONFIG || content == CONTENT_ALL){
if ((ret = client_statedata(h, cbuf_get(cbpath), nsc, &xret)) < 0)
goto done;
if (ret == 0){ /* Error from callback (error in xret) */
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
goto done;
goto ok;
}
}
}
if (0 && clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
/* XXX: subset in collection may not have mandatory variables */
/* 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 not 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.
*/
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cbpath)) < 0)
goto done;
/* Pre-NACM access step */
xnacm = clicon_nacm_cache(h);
if (xnacm != NULL){ /* Do NACM validation */
/* NACM datanode/module read validation */
if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply xmlns=\"%s\"><pageable-list xmlns=\"%s\">",
NETCONF_BASE_NAMESPACE, NETCONF_COLLECTION_NAMESPACE); /* OK */
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++){
x = xvec[i];
/* Add namespace */
if (xmlns_set(x, NULL, ns) < 0)
goto done;
if (i == 0 && remaining != total){
/* Add remaining annotation to first element
* If no elements were removed, this annotation MUST NOT appear
*/
if ((xa = xml_new("remaining", x, CX_ATTR)) == NULL)
goto done;
cbuf_reset(cb);
cprintf(cb, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
if (xml_prefix_set(xa, "ycoll") < 0)
goto done;
if (xmlns_set(x, "ycoll", "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination") < 0)
goto done;
}
/* Top level is data, so add 1 to depth if significant */
if (clicon_xml2cbuf(cbret, x, 0, 0, depth>0?depth+1:depth) < 0)
goto done;
}
}
cprintf(cbret, "</pageable-list></rpc-reply>");
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (datastore)
free(datastore);
if (path_tree)
clixon_path_free(path_tree);
if (xtop)
xml_free(xtop);
if (cbpath)
cbuf_free(cbpath);
if (cb)
cbuf_free(cb);
if (reason)
free(reason);
if (xerr)
xml_free(xerr);
if (xvec)
free(xvec);
if (nsc)
xml_nsctx_free(nsc);
if (xret)
xml_free(xret);
return retval;
}
#endif /* LIST_PAGINATION */

View file

@ -1348,7 +1348,7 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
}
if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL)
goto done;
if (clicon_rpc_get_pageable_list(h, "running", xpath, NULL, nsc, CONTENT_CONFIG, NULL,
if (clicon_rpc_get_pageable_list(h, "running", xpath, NULL, nsc, CONTENT_CONFIG, -1,
"2", "0",
NULL, NULL, NULL,
&xret) < 0){

View file

@ -53,8 +53,8 @@ enum restconf_media{
YANG_DATA_XML, /* "application/yang-data+xml" */
YANG_PATCH_JSON, /* "application/yang-patch+json" */
YANG_PATCH_XML, /* "application/yang-patch+xml" */
YANG_COLLECTION_XML, /* draft-ietf-netconf-restconf-collection-00.txt */
YANG_COLLECTION_JSON /* draft-ietf-netconf-restconf-collection-00.txt */
YANG_COLLECTION_XML, /* draft-wwlh-netconf-list-pagination-rc-01.txt */
YANG_COLLECTION_JSON /* draft-wwlh-netconf-list-pagination-rc-01.txt */
};
typedef enum restconf_media restconf_media;

View file

@ -207,7 +207,7 @@ static const map_str2int http_media_map[] = {
{"application/yang-data+json", YANG_DATA_JSON},
{"application/yang-patch+xml", YANG_PATCH_XML},
{"application/yang-patch+json", YANG_PATCH_JSON},
{"application/yang-collection+xml", YANG_COLLECTION_XML},
{"application/yang-collection+xml", YANG_COLLECTION_XML}, /* XXX -data+xml-list?? */
{"application/yang-collection+json", YANG_COLLECTION_JSON},
{NULL, -1}
};

View file

@ -149,7 +149,6 @@ api_data_get2(clicon_handle h,
goto ok;
}
}
/* Check for content attribute */
if ((attr = cvec_find_str(qvec, "content")) != NULL){
clicon_debug(1, "%s content=%s", __FUNCTION__, attr);
@ -181,19 +180,9 @@ api_data_get2(clicon_handle h,
}
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, xpath);
switch (content){
case CONTENT_CONFIG:
case CONTENT_NONCONFIG:
case CONTENT_ALL:
ret = clicon_rpc_get(h, xpath, nsc, content, depth, &xret);
break;
default:
clicon_err(OE_XML, EINVAL, "Invalid content attribute %d", content);
goto done;
break;
}
if (ret < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
@ -356,9 +345,11 @@ api_data_collection(clicon_handle h,
netconf_content content = CONTENT_ALL;
cxobj *xtop = NULL;
cxobj *xbot = NULL;
cxobj *xp;
cxobj *xpr;
yang_stmt *y = NULL;
cbuf *cbrpc = NULL;
char *depth;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
char *limit;
char *offset;
char *direction;
@ -434,13 +425,30 @@ api_data_collection(clicon_handle h,
goto done;
}
/* Clixon extensions and collection attributes */
depth = cvec_find_str(qvec, "depth");
/* Check for depth attribute */
if ((attr = cvec_find_str(qvec, "depth")) != NULL){
clicon_debug(1, "%s depth=%s", __FUNCTION__, attr);
if (strcmp(attr, "unbounded") != 0){
char *reason = 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_xml(&xerr, "application",
"depth", "Unrecognized value of depth attribute") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
}
limit = cvec_find_str(qvec, "limit");
offset = cvec_find_str(qvec, "offset");
direction = cvec_find_str(qvec, "direction");
sort = cvec_find_str(qvec, "sort");
where = cvec_find_str(qvec, "where");
if (clicon_rpc_get_pageable_list(h, "running", xpath, y, nsc, content,
depth, limit, offset, direction, sort, where,
&xret) < 0){
@ -454,7 +462,6 @@ api_data_collection(clicon_handle h,
goto done;
goto ok;
}
/* We get return via netconf which is complete tree from root
* We need to cut that tree to only the object.
*/
@ -468,16 +475,34 @@ api_data_collection(clicon_handle h,
goto done;
goto ok;
}
if ((xpr = xml_new("yang-collection", NULL, CX_ELMNT)) == NULL)
goto done;
if (xmlns_set(xpr, NULL, RESTCONF_PAGINATON_NAMESPACE) < 0)
goto done;
if ((xp = xpath_first(xret, nsc, "%s", xpath)) != NULL){
char *ns=NULL;
if (xml2ns(xp, NULL, &ns) < 0)
goto done;
if (ns != NULL){
if (xmlns_set(xp, NULL, ns) < 0)
goto done;
}
if (xml_rm(xp) < 0)
goto done;
if (xml_insert(xpr, xp, INS_LAST, NULL, NULL) < 0)
goto done;
}
/* Normal return, no error */
if ((cbx = cbuf_new()) == NULL)
goto done;
switch (media_out){
case YANG_COLLECTION_XML:
if (clicon_xml2cbuf(cbx, xret, 0, pretty, -1) < 0) /* Dont print top object? */
if (clicon_xml2cbuf(cbx, xpr, 0, pretty, -1) < 0) /* Dont print top object? */
goto done;
break;
case YANG_COLLECTION_JSON:
if (xml2json_cbuf(cbx, xret, pretty) < 0)
if (xml2json_cbuf(cbx, xpr, pretty) < 0)
goto done;
break;
default:
@ -518,6 +543,8 @@ api_data_collection(clicon_handle h,
free(xpath);
if (nsc)
xml_nsctx_free(nsc);
if (xpr)
xml_free(xpr);
if (xtop)
xml_free(xtop);
if (cbx)

View file

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

View file

@ -55,7 +55,10 @@ int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(clicon_handle h, char *db);
int clicon_rpc_get(clicon_handle h, char *xpath, cvec *nsc, netconf_content content, int32_t depth, cxobj **xret);
int clicon_rpc_get_pageable_list(clicon_handle h, char *datastore, char *xpath, yang_stmt *yco, cvec *nsc, netconf_content content, char *depth, char *count, char *skip, char *direction, char *sort, char *where, cxobj **xt);
int clicon_rpc_get_pageable_list(clicon_handle h, char *datastore, char *xpath, yang_stmt *yli,
cvec *nsc, netconf_content content, int32_t depth,
char *limit, char *offset, char *direction, char *sort, char *where,
cxobj **xt);
int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id);
int clicon_rpc_validate(clicon_handle h, char *db);

View file

@ -55,13 +55,17 @@
#define NETCONF_INPUT_CONFIG "config"
/* Collections namespace from draft-ietf-netconf-restconf-collection-00.txt
* XXX: Obsolete but may come back in style
*/
#define NETCONF_COLLECTION_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination"
/* See RFC 7950 Sec 5.3.1: YANG defines an XML namespace for NETCONF <edit-config>
* operations, <error-info> content, and the <action> element.
/* Collections namespace for Clixon
*/
#define NETCONF_COLLECTION_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination"
#define CLIXON_PAGINATON_NAMESPACE "http://clicon.org/clixon-netconf-list-pagination"
/* Collections namespace for restconf
*/
#define RESTCONF_PAGINATON_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination"
/* Output symbol for netconf get/get-config
* ietf-netconf.yang defines it as output:

View file

@ -1531,9 +1531,13 @@ netconf_module_load(clicon_handle h)
xml_bind_netconf_message_id_optional(1);
#endif
#ifdef LIST_PAGINATION
/* Load netconf list pagination */
if (yang_spec_parse_module(h, "ietf-netconf-list-pagination", NULL, yspec)< 0)
/* Load clixon netconf list pagination */
if (yang_spec_parse_module(h, "clixon-netconf-list-pagination", NULL, yspec)< 0)
goto done;
/* Load restconf list pagination */
if (yang_spec_parse_module(h, "ietf-restconf-list-pagination", NULL, yspec)< 0)
goto done;
#if 0
/* XXX Clixon test harness problem: when loading ietf-list-pagination, it loads
* ietf-system-capabilities which in turn loads ietf-netconf-acm. As this is a
@ -1543,10 +1547,8 @@ netconf_module_load(clicon_handle h)
if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0)
goto done;
#endif
#ifdef CLIXON_PAGINATION
/* Load clixon netconf list pagination */
if (yang_spec_parse_module(h, "clixon-netconf-list-pagination", NULL, yspec)< 0)
goto done;
#ifdef LIST_PAGINATION
#endif
#endif
retval = 0;

View file

@ -822,7 +822,7 @@ clicon_rpc_get(clicon_handle h,
/* Clixon extension, depth=<level> */
if (depth != -1)
cprintf(cb, " depth=\"%d\"", depth);
cprintf(cb, ">");
cprintf(cb, ">"); /* get */
/* If xpath, add a filter */
if (xpath && strlen(xpath)) {
cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
@ -882,7 +882,8 @@ clicon_rpc_get(clicon_handle h,
/*! Get database configuration and state data collection
* @param[in] h Clicon handle
* @param[in] xpath To identify a list/leaf-list
* @param[in] yli Yang-stmt of list/leaf-list of collection, if given make sanity check
* @param[in] yli Yang-stmt of list/leaf-list of collection, if given yang populate return
* xt and make sanity check
* @param[in] namespace Namespace associated w xpath
* @param[in] nsc Namespace context for filter
* @param[in] content Clixon extension: all, config, noconfig. -1 means all
@ -896,7 +897,6 @@ clicon_rpc_get(clicon_handle h,
* Either <config> or <rpc-error>.
* @retval 0 OK
* @retval -1 Error, fatal or xml
* @see clicon_rpc_get
* @see draft-ietf-netconf-restconf-collection-00
* @note the netconf return message is yang populated, as well as the return data
@ -908,7 +908,7 @@ clicon_rpc_get_pageable_list(clicon_handle h,
yang_stmt *yli,
cvec *nsc, /* namespace context for xpath */
netconf_content content,
char *depth,
int32_t depth,
char *limit,
char *offset,
char *direction,
@ -921,15 +921,16 @@ clicon_rpc_get_pageable_list(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL;
cxobj *xr;
cxobj *xr; /* return msg */
cxobj *xd; /* return data */
char *username;
uint32_t session_id;
int ret;
yang_stmt *yspec;
cxobj *x;
if (datastore == NULL){
clicon_err(OE_XML, EINVAL, "datastore not given");
goto done;
}
if (session_id_check(h, &session_id) < 0)
goto done;
@ -941,31 +942,39 @@ clicon_rpc_get_pageable_list(clicon_handle h,
cprintf(cb, " xmlns:%s=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
cprintf(cb, " %s", NETCONF_MESSAGE_ID_ATTR);
cprintf(cb, "><get-pageable-list xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
cprintf(cb, "><get ");
/* Clixon extension, content=all,config, or nonconfig */
if ((int)content != -1)
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
if (depth)
cprintf(cb, " depth=\"%s\"", depth);
cprintf(cb, ">");
cprintf(cb, "<datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:%s</datastore>", datastore);
if (xpath){
cprintf(cb, "<list-target");
/* Clixon extension, depth=<level> */
if (depth != -1)
cprintf(cb, " depth=\"%d\"", depth);
/* declare cp prefix in get, so sub-elements dont need to */
cprintf(cb, " xmlns:cp=\"%s\"", CLIXON_PAGINATON_NAMESPACE);
cprintf(cb, ">"); /* get */
/* If xpath, add a filter */
if (xpath && strlen(xpath)) {
cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
xpath);
if (xml_nsctx_cbuf(cb, nsc) < 0)
goto done;
cprintf(cb, ">%s</list-target>", xpath);
cprintf(cb, "/>");
}
/* Explicit use of list-pagination */
cprintf(cb, "<cp:list-pagination>true</cp:list-pagination>");
if (limit)
cprintf(cb, "<limit>%s</limit>", limit);
cprintf(cb, "<cp:limit>%s</cp:limit>", limit);
if (offset)
cprintf(cb, "<offset>%s</offset>", offset);
cprintf(cb, "<cp:offset>%s</cp:offset>", offset);
if (direction)
cprintf(cb, "<direction>%s</direction>", direction);
cprintf(cb, "<cp:direction>%s</cp:direction>", direction);
if (sort)
cprintf(cb, "<sort>%s</sort>", sort);
cprintf(cb, "<cp:sort>%s</cp:sort>", sort);
if (where)
cprintf(cb, "<where>%s</where>", where);
cprintf(cb, "</get-pageable-list></rpc>");
cprintf(cb, "<cp:where>%s</cp:where>", where);
cprintf(cb, "</get>");
cprintf(cb, "</rpc>");
if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret) < 0)
@ -973,34 +982,29 @@ clicon_rpc_get_pageable_list(clicon_handle h,
/* Send xml error back: first check error, then ok */
if ((xr = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
xr = xml_parent(xr); /* point to rpc-reply */
else if ((xr = xpath_first(xret, NULL, "/rpc-reply/pageable-list")) == NULL){
if ((xr = xml_new("pageable_list", NULL, CX_ELMNT)) == NULL)
else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){
if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL)
goto done;
}
else if (yli != NULL) {
else{
yspec = clicon_dbspec_yang(h);
/* Populate all children with y */
x = NULL;
while ((x = xml_child_each(xr, x, CX_ELMNT)) != NULL){
xml_spec_set(x, yli);
if ((ret = xml_bind_yang(x, YB_PARENT, yspec, &xerr)) < 0)
if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0)
goto done;
if (ret == 0){
if (clixon_netconf_internal_error(xerr,
". Internal error, backend returned XML tat doid not match given YANG.",
yli?yang_argument_get(yli):NULL) < 0)
". Internal error, backend returned invalid XML.",
NULL) < 0)
goto done;
if ((xr = xpath_first(xerr, NULL, "rpc-error")) == NULL){
if ((xd = xpath_first(xerr, NULL, "rpc-error")) == NULL){
clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)");
goto done;
}
}
}
}
if (xr){
if (xml_rm(xr) < 0)
if (xt){
if (xml_rm(xd) < 0)
goto done;
*xt = xr;
*xt = xd;
}
retval = 0;
done:

View file

@ -3,7 +3,6 @@
# Backlog items:
# 1. "remaining" annotation RFC 7952
# 2. pattern '.*[\n].*' { modifier invert-match;
# XXX: augment Netconf GET instead, not RPC
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -277,16 +276,12 @@ function testlimit()
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\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination><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>]]>]]>$"
new "limit=$limit Parameter RESTCONF xml"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "<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>"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "<yang-collection xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination\"><uint8-numbers cp:remaining=\"$remaining\" xmlns:cp=\"http://clicon.org/clixon-netconf-list-pagination\" xmlns=\"http://example.com/ns/example-social\">17</uint8-numbers></yang-collection>"
# XXX [17]
new "limit=$limit Parameter RESTCONF json"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "{\"pageable-list\":{\"example-social:uint8-numbers\":17,\"@example-social:uint8-numbers\": \[{\"ietf-netconf-list-pagination:remaining\": $remaining}\]}}"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+json" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers?limit=$limit)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" '{"yang-collection":{"example-social:uint8-numbers":17,"@example-social:uint8-numbers": \[{"clixon-netconf-list-pagination:remaining": 5}\]}}'
}
new "test params: -f $cfg -s startup -- -sS $fstate"

View file

@ -56,6 +56,8 @@ YANGSPECS += ietf-yang-metadata@2016-08-05.yang
YANGSPECS += ietf-netconf-with-defaults@2011-06-01.yang
# in draft-wwlh-netconf-list-pagination:
YANGSPECS += ietf-netconf-list-pagination@2020-10-30.yang
# in draft-wwlh-netconf-list-pagination-rc:
YANGSPECS += ietf-restconf-list-pagination@2015-01-30.yang
all:

View file

@ -0,0 +1,54 @@
module ietf-restconf-list-pagination {
namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination";
prefix rlpg;
import ietf-restconf {
prefix rc;
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: <http://tools.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>";
description
"This module contains conceptual YANG specifications
for the RESTCONF Collection resource type.
Note that the YANG definitions within this module do not
represent configuration data of any kind.
The YANG grouping statements provide a normative syntax
for XML and JSON message encoding purposes.
Copyright (c) 2015 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
(http://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC XXXX; see
the RFC itself for full legal notices.";
revision 2015-01-30 {
description
"Initial revision.";
reference
"RFC XXXX: RESTCONF Collection Resource.";
}
rc:yang-data yang-collection {
uses collection;
}
grouping collection {
description
"Conceptual container representing the
yang-collection resource type.";
container yang-collection {
description
"Container representing the yang-collection
resource type.";
}
}
}