- 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 ### 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 * YANG Leafref feature update
* Closer adherence to RFC 7950. Some of this is changed behavior, some is new feature. * 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 * 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, if (rpc_callback_register(h, from_client_validate, NULL,
NETCONF_BASE_NAMESPACE, "validate") < 0) NETCONF_BASE_NAMESPACE, "validate") < 0)
goto done; 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 */ /* In backend_client.? RPC from RFC 5277 */
if (rpc_callback_register(h, from_client_create_subscription, NULL, if (rpc_callback_register(h, from_client_create_subscription, NULL,
EVENT_RFC5277_NAMESPACE, "create-subscription") < 0) EVENT_RFC5277_NAMESPACE, "create-subscription") < 0)

View file

@ -272,7 +272,7 @@ client_statedata(clicon_handle h,
goto done; goto done;
} }
#if defined(LIST_PAGINATION) || defined(CLIXON_PAGINATION) #ifdef LIST_PAGINATION
/*! Help function for parsing restconf query parameter and setting netconf attribute /*! Help function for parsing restconf query parameter and setting netconf attribute
* *
* If not "unbounded", parse and set a numeric value * If not "unbounded", parse and set a numeric value
@ -364,7 +364,7 @@ get_common(clicon_handle h,
int ret; int ret;
char *reason = NULL; char *reason = NULL;
cbuf *cbmsg = NULL; /* For error msg */ cbuf *cbmsg = NULL; /* For error msg */
#ifdef CLIXON_PAGINATION #ifdef LIST_PAGINATION
uint32_t limit = 0; uint32_t limit = 0;
uint32_t offset = 0; uint32_t offset = 0;
uint32_t total = 0; uint32_t total = 0;
@ -379,7 +379,7 @@ get_common(clicon_handle h,
char *valstr; char *valstr;
yang_stmt *ylist; yang_stmt *ylist;
cxobj *xcache = NULL; cxobj *xcache = NULL;
#endif /* CLIXON_PAGINATION */ #endif /* LIST_PAGINATION */
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h); username = clicon_username_get(h);
@ -418,7 +418,7 @@ get_common(clicon_handle h,
goto ok; goto ok;
} }
} }
#ifdef CLIXON_PAGINATION #ifdef LIST_PAGINATION
/* Check if list pagination */ /* Check if list pagination */
if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL && if ((x = xml_find_type(xe, NULL, "list-pagination", CX_ELMNT)) != NULL &&
(valstr = xml_body(x)) != NULL && (valstr = xml_body(x)) != NULL &&
@ -525,7 +525,7 @@ get_common(clicon_handle h,
} }
} /* list_pagination */ } /* list_pagination */
#endif /* CLIXON_PAGINATION */ #endif /* LIST_PAGINATION */
/* Read config /* Read config
* XXX This seems unnecessary complex * XXX This seems unnecessary complex
*/ */
@ -661,7 +661,7 @@ get_common(clicon_handle h,
if (xml_default_recurse(xret, 0) < 0) if (xml_default_recurse(xret, 0) < 0)
goto done; goto done;
#ifdef CLIXON_PAGINATION #ifdef LIST_PAGINATION
/* Add remaining attribute */ /* Add remaining attribute */
if (list_pagination && remaining && xlen){ if (list_pagination && remaining && xlen){
cxobj *xa; cxobj *xa;
@ -685,7 +685,7 @@ get_common(clicon_handle h,
if (cba) if (cba)
cbuf_free(cba); cbuf_free(cba);
} }
#endif /* CLIXON_PAGINATION */ #endif /* LIST_PAGINATION */
/* 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 */
@ -710,7 +710,7 @@ get_common(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);
#ifdef CLIXON_PAGINATION #ifdef LIST_PAGINATION
if (cbpath) if (cbpath)
cbuf_free(cbpath); cbuf_free(cbpath);
#endif #endif
@ -790,319 +790,3 @@ from_client_get(clicon_handle h,
content = netconf_content_str2int(attr); content = netconf_content_str2int(attr);
return get_common(h, xe, content, "running", cbret); 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) if ((nsc = xml_nsctx_init(prefix, namespace)) == NULL)
goto done; 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", "2", "0",
NULL, NULL, NULL, NULL, NULL, NULL,
&xret) < 0){ &xret) < 0){

View file

@ -53,8 +53,8 @@ enum restconf_media{
YANG_DATA_XML, /* "application/yang-data+xml" */ YANG_DATA_XML, /* "application/yang-data+xml" */
YANG_PATCH_JSON, /* "application/yang-patch+json" */ YANG_PATCH_JSON, /* "application/yang-patch+json" */
YANG_PATCH_XML, /* "application/yang-patch+xml" */ YANG_PATCH_XML, /* "application/yang-patch+xml" */
YANG_COLLECTION_XML, /* draft-ietf-netconf-restconf-collection-00.txt */ YANG_COLLECTION_XML, /* draft-wwlh-netconf-list-pagination-rc-01.txt */
YANG_COLLECTION_JSON /* draft-ietf-netconf-restconf-collection-00.txt */ YANG_COLLECTION_JSON /* draft-wwlh-netconf-list-pagination-rc-01.txt */
}; };
typedef enum restconf_media restconf_media; 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-data+json", YANG_DATA_JSON},
{"application/yang-patch+xml", YANG_PATCH_XML}, {"application/yang-patch+xml", YANG_PATCH_XML},
{"application/yang-patch+json", YANG_PATCH_JSON}, {"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}, {"application/yang-collection+json", YANG_COLLECTION_JSON},
{NULL, -1} {NULL, -1}
}; };

View file

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

View file

@ -124,7 +124,3 @@
* 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

@ -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_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(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(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_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id); int clicon_rpc_kill_session(clicon_handle h, uint32_t session_id);
int clicon_rpc_validate(clicon_handle h, char *db); int clicon_rpc_validate(clicon_handle h, char *db);

View file

@ -55,13 +55,17 @@
#define NETCONF_INPUT_CONFIG "config" #define NETCONF_INPUT_CONFIG "config"
/* Collections namespace from draft-ietf-netconf-restconf-collection-00.txt /* 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" #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> /* Collections namespace for Clixon
* operations, <error-info> content, and the <action> element.
*/ */
#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 /* Output symbol for netconf get/get-config
* ietf-netconf.yang defines it as output: * 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); xml_bind_netconf_message_id_optional(1);
#endif #endif
#ifdef LIST_PAGINATION #ifdef LIST_PAGINATION
/* Load netconf list pagination */ /* Load clixon netconf list pagination */
if (yang_spec_parse_module(h, "ietf-netconf-list-pagination", NULL, yspec)< 0) if (yang_spec_parse_module(h, "clixon-netconf-list-pagination", NULL, yspec)< 0)
goto done; goto done;
/* Load restconf list pagination */
if (yang_spec_parse_module(h, "ietf-restconf-list-pagination", NULL, yspec)< 0)
goto done;
#if 0 #if 0
/* XXX Clixon test harness problem: when loading ietf-list-pagination, it loads /* 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 * 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) if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0)
goto done; goto done;
#endif #endif
#ifdef CLIXON_PAGINATION #ifdef LIST_PAGINATION
/* Load clixon netconf list pagination */
if (yang_spec_parse_module(h, "clixon-netconf-list-pagination", NULL, yspec)< 0)
goto done;
#endif #endif
#endif #endif
retval = 0; retval = 0;

View file

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

View file

@ -3,7 +3,6 @@
# Backlog items: # Backlog items:
# 1. "remaining" annotation RFC 7952 # 1. "remaining" annotation RFC 7952
# 2. pattern '.*[\n].*' { modifier invert-match; # 2. pattern '.*[\n].*' { modifier invert-match;
# XXX: augment Netconf GET instead, not RPC
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi 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" 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>]]>]]>$" 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" 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] # XXX [17]
new "limit=$limit Parameter RESTCONF json" 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" new "test params: -f $cfg -s startup -- -sS $fstate"

View file

@ -55,7 +55,9 @@ YANGSPECS += ietf-origin@2018-02-14.yang
YANGSPECS += ietf-yang-metadata@2016-08-05.yang YANGSPECS += ietf-yang-metadata@2016-08-05.yang
YANGSPECS += ietf-netconf-with-defaults@2011-06-01.yang YANGSPECS += ietf-netconf-with-defaults@2011-06-01.yang
# in draft-wwlh-netconf-list-pagination: # in draft-wwlh-netconf-list-pagination:
YANGSPECS += ietf-netconf-list-pagination@2020-10-30.yang 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: 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.";
}
}
}