From fb0b9409f31eab44c12f22f96cf544ac299e9f62 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 24 Aug 2021 17:35:18 +0200 Subject: [PATCH] - Moved restrconf code from pageing rpc to get --- CHANGELOG.md | 12 + apps/backend/backend_client.c | 6 - apps/backend/backend_get.c | 332 +----------------- apps/cli/cli_common.c | 2 +- apps/restconf/clixon_restconf.h | 4 +- apps/restconf/restconf_lib.c | 2 +- apps/restconf/restconf_methods_get.c | 65 +++- include/clixon_custom.h | 4 - lib/clixon/clixon_proto_client.h | 5 +- lib/clixon/clixon_xml.h | 10 +- lib/src/clixon_netconf_lib.c | 14 +- lib/src/clixon_proto_client.c | 84 ++--- test/test_pagination.sh | 9 +- yang/mandatory/Makefile.in | 4 +- ...f-restconf-list-pagination@2015-01-30.yang | 54 +++ 15 files changed, 192 insertions(+), 415 deletions(-) create mode 100644 yang/mandatory/ietf-restconf-list-pagination@2015-01-30.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index 375bdb39..e6772a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index e28647cd..44b335a1 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -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) diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 57d36e2a..f1c132f3 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -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: - * @param[out] cbret Return xml tree, eg ..., 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, "", - 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; i0?depth+1:depth) < 0) - goto done; - } - } - cprintf(cbret, ""); - 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 */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 3eaa1954..471aafb1 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -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){ diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index c7d6a368..4ba23cf5 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -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; diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index a3e7be81..b29f50f4 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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} }; diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index 7541e110..392c3f01 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -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; - } + ret = clicon_rpc_get(h, xpath, nsc, content, depth, &xret); + 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) diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 669268b9..3f7f6aa9 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -124,7 +124,3 @@ * draft-wwlh-netconf-list-pagination-rc-01 */ #define LIST_PAGINATION - -/*! Clixon netconf pagination - */ -#define CLIXON_PAGINATION diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index c27d929b..39130a3b 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -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); diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index ac232095..dab22508 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -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 - * operations, content, and the 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: diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 17db459a..21aadd8f 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -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; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index f952293f..80ff1eda 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -822,7 +822,7 @@ clicon_rpc_get(clicon_handle h, /* Clixon extension, depth= */ 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 or . * @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, ">"); - cprintf(cb, "ds:%s", datastore); - if (xpath){ - cprintf(cb, " */ + 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", xpath); + cprintf(cb, "/>"); } + /* Explicit use of list-pagination */ + cprintf(cb, "true"); if (limit) - cprintf(cb, "%s", limit); + cprintf(cb, "%s", limit); if (offset) - cprintf(cb, "%s", offset); + cprintf(cb, "%s", offset); if (direction) - cprintf(cb, "%s", direction); + cprintf(cb, "%s", direction); if (sort) - cprintf(cb, "%s", sort); + cprintf(cb, "%s", sort); if (where) - cprintf(cb, "%s", where); - cprintf(cb, ""); + cprintf(cb, "%s", where); + cprintf(cb, ""); + cprintf(cb, ""); 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 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; - 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 (xml_rm(xr) < 0) + if (xt){ + if (xml_rm(xd) < 0) goto done; - *xt = xr; + *xt = xd; } retval = 0; done: diff --git a/test/test_pagination.sh b/test/test_pagination.sh index 66ac0ecf..2aea412f 100755 --- a/test/test_pagination.sh +++ b/test/test_pagination.sh @@ -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 "$DEFAULTHELLOtrue$limit]]>]]>" "alicepublic17]]>]]>$" - # "old: ietf" - new "ietf limit=$limit NETCONF" - expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOds:running/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers$limit]]>]]>" "17]]>]]>$" - 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" "17" + 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" "17" # 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" diff --git a/yang/mandatory/Makefile.in b/yang/mandatory/Makefile.in index fc31d905..cc481cab 100644 --- a/yang/mandatory/Makefile.in +++ b/yang/mandatory/Makefile.in @@ -55,7 +55,9 @@ YANGSPECS += ietf-origin@2018-02-14.yang 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 +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: diff --git a/yang/mandatory/ietf-restconf-list-pagination@2015-01-30.yang b/yang/mandatory/ietf-restconf-list-pagination@2015-01-30.yang new file mode 100644 index 00000000..9e9e340d --- /dev/null +++ b/yang/mandatory/ietf-restconf-list-pagination@2015-01-30.yang @@ -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: + WG List: "; + + 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."; + } + } +}