* Pagination according to new draft

* count/skip -> limit/offset
* ietf-yang-metadata RFC 7952 support, placeholder parsing and extension
This commit is contained in:
Olof hagsand 2021-07-27 21:36:47 +02:00
parent 77bacc93bb
commit 0c7f2043f3
26 changed files with 751 additions and 620 deletions

View file

@ -81,6 +81,11 @@ Users may have to change how they access the system
* Added linenumbers to all YANG symbols for better debug and errors
* Improved error messages for YANG identityref:s and leafref:s by adding original line numbers
### Minor features
* ietf-yang-metadata RFC 7952 support, placeholder parsing and extension
* No actual json/xml semantics
### Corrected Bugs
* Partly Fixed: [String concatenation in YANG model leads to syntax error](https://github.com/clicon/clixon/issues/265)

View file

@ -359,6 +359,7 @@ client_statedata(clicon_handle h,
goto done;
}
cbuf_reset(cb);
/* XXX This code does not filter state data with xpath */
cprintf(cb, "<restconf-state xmlns=\"%s\"/>", namespace);
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, xret, NULL) < 0)
goto done;
@ -1422,8 +1423,10 @@ from_client_get_pageable_list(clicon_handle h,
int i;
cxobj *xerr = NULL;
int ret;
uint32_t count = 0;
uint32_t skip = 0;
uint32_t limit = 0;
uint32_t offset = 0;
size_t total = 0;
uint32_t remaining = 0;
char *direction = NULL;
char *sort = NULL;
char *where = NULL;
@ -1435,6 +1438,7 @@ from_client_get_pageable_list(clicon_handle h,
char *ns;
clixon_path *path_tree = NULL;
clixon_path *cp;
cxobj *xa; /* attribute */
clicon_debug(1, "%s", __FUNCTION__);
username = clicon_username_get(h);
@ -1458,11 +1462,11 @@ from_client_get_pageable_list(clicon_handle h,
goto ok;
}
}
/* count */
if ((ret = element2value(h, xe, "count", "unbounded", cbret, &count)) < 0)
/* limit */
if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0)
goto done;
/* skip */
if (ret && (ret = element2value(h, xe, "skip", "none", cbret, &skip)) < 0)
/* 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){
@ -1496,13 +1500,17 @@ from_client_get_pageable_list(clicon_handle h,
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, "%s", xpath)) < 0)
if ((ret = clixon_instance_id_parse(yspec, &path_tree, &xerr, "%s", xpath)) < 0)
goto done;
if (ret == 0){
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
if (xerr && clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
goto done;
goto ok;
}
@ -1524,39 +1532,50 @@ from_client_get_pageable_list(clicon_handle h,
goto ok;
}
/* Build a "predicate" cbuf
* This solution uses xpath predicates to translate "count" and "skip" to
* This solution uses xpath predicates to translate "limit" and "offset" to
* relational operators <>.
*/
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* This uses xpath. Maybe count/limit should use parameters */
/* This uses xpath. Maybe limit should use parameters */
cprintf(cb, "%s", xpath);
if (where)
cprintf(cb, "[%s]", where);
if (skip){
cprintf(cb, "[%u <= position()", skip);
if (count)
cprintf(cb, " and position() < %u", count+skip);
if (offset){
cprintf(cb, "[%u <= position()", offset);
if (limit)
cprintf(cb, " and position() < %u", limit+offset);
cprintf(cb, "]");
}
else if (count)
cprintf(cb, "[position() < %u]", count);
else if (limit)
cprintf(cb, "[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, cbuf_get(cb), 1, &xret, NULL, &xerr)) < 0) {
if ((ret = xmldb_get0(h, datastore, YB_MODULE, nsc, xpath, 1, &xret, NULL, &xerr)) < 0) {
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done;
goto ok;
}
}
if (ret == 0){
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
if (ret == 0){
if (clicon_xml2cbuf(cbret, xerr, 0, 0, -1) < 0)
goto done;
goto ok;
}
/* First get number of hits */
if (xpath_vec(xret, nsc, "%s", &xvec, &total, xpath) < 0)
goto done;
goto ok;
if (xvec){
free(xvec);
xvec = NULL;
}
if (total < (offset + limit))
remaining = 0;
else
remaining = total - (offset + limit);
}
/* There may be CF data in a CT collection */
if (content == CONTENT_ALL){
@ -1576,7 +1595,8 @@ from_client_get_pageable_list(clicon_handle h,
}
}
}
if (clicon_option_bool(h, "CLICON_VALIDATE_STATE_XML")){
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
@ -1614,7 +1634,7 @@ from_client_get_pageable_list(clicon_handle h,
* Actually this is a safety catch, should really be done in plugins
* and modules_state functions.
*/
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, cbuf_get(cb)) < 0)
goto done;
/* Pre-NACM access step */
@ -1632,6 +1652,21 @@ from_client_get_pageable_list(clicon_handle h,
/* 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); /* reuse */
cprintf(cb, "%u", remaining);
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
if (xml_prefix_set(xa, "lpg") < 0)
goto done;
if (xmlns_set(x, "lpg", "urn:ietf:params:xml:ns:yang:ietf-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;
@ -2291,7 +2326,7 @@ backend_rpc_init(clicon_handle h)
#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)
NETCONF_COLLECTION_NAMESPACE, "get-pagable-list") < 0)
goto done;
#endif
/* In backend_client.? RPC from RFC 5277 */

View file

@ -767,6 +767,9 @@ main(int argc,
if (netconf_module_features(h) < 0)
goto done;
/* In case ietf-yang-metadata is loaded by application, handle annotation extension */
if (yang_metadata_init(h) < 0)
goto done;
/* External NACM file?
* Note, loads yang -> extensions -> plugins
*/

View file

@ -237,23 +237,25 @@ clixon_plugin_daemon_all(clicon_handle h)
*
* @param[in] cp Plugin handle
* @param[in] h clicon handle
* @param[in] nsc namespace context for xpath
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xret If retval=1, state tree created and returned: <config>...
* @param[out] xp If retval=1, state tree created and returned: <config>...
* @retval -1 Fatal error
* @retval 0 Statedata callback failed. no XML tree returned
* @retval 1 OK if callback found (and called) xret is set
*/
static int
clixon_plugin_statedata_one(clixon_plugin_t *cp,
clicon_handle h,
cvec *nsc,
char *xpath,
cxobj **xp)
clixon_plugin_statedata_one(clixon_plugin_t *cp,
clicon_handle h,
cvec *nsc,
char *xpath,
cxobj **xp)
{
int retval = -1;
plgstatedata_t *fn; /* Plugin statedata fn */
cxobj *x = NULL;
clicon_debug(1, "%s %s", __FUNCTION__, clixon_plugin_name_get(cp));
if ((fn = clixon_plugin_api_get(cp)->ca_statedata) != NULL){
if ((x = xml_new(XML_TOP_SYMBOL, NULL, CX_ELMNT)) == NULL)
goto done;
@ -263,7 +265,6 @@ clixon_plugin_statedata_one(clixon_plugin_t *cp,
__FUNCTION__, clixon_plugin_name_get(cp));
goto fail; /* Dont quit here on user callbacks */
}
}
if (xp && x)
*xp = x;
@ -295,12 +296,12 @@ clixon_plugin_statedata_all(clicon_handle h,
char *xpath,
cxobj **xret)
{
int retval = -1;
int ret;
cxobj *x = NULL;
clixon_plugin_t *cp = NULL;
cbuf *cberr = NULL;
cxobj *xerr = NULL;
int retval = -1;
int ret;
cxobj *x = NULL;
clixon_plugin_t *cp = NULL;
cbuf *cberr = NULL;
cxobj *xerr = NULL;
clicon_debug(1, "%s", __FUNCTION__);
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
@ -328,10 +329,8 @@ clixon_plugin_statedata_all(clicon_handle h,
x = NULL;
continue;
}
#if 1
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__);
#endif
clicon_log_xml(LOG_DEBUG, x, "%s %s STATE:", __FUNCTION__, clixon_plugin_name_get(cp));
/* XXX: ret == 0 invalid yang binding should be handled as internal error */
if ((ret = xml_bind_yang(x, YB_MODULE, yspec, &xerr)) < 0)
goto done;

View file

@ -633,6 +633,9 @@ main(int argc,
*/
if (netconf_module_features(h) < 0)
goto done;
/* In case ietf-yang-metadata is loaded by application, handle annotation extension */
if (yang_metadata_init(h) < 0)
goto done;
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
xml_nsctx_namespace_netconf_default(h);
/* Create top-level and store as option */

View file

@ -800,7 +800,9 @@ main(int argc,
/* Initialize plugin module by creating a handle holding plugin and callback lists */
if (clixon_plugin_module_init(h) < 0)
goto done;
/* In case ietf-yang-metadata is loaded by application, handle annotation extension */
if (yang_metadata_init(h) < 0)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;

View file

@ -370,7 +370,9 @@ main(int argc,
*/
if (netconf_module_features(h) < 0)
goto done;
/* In case ietf-yang-metadata is loaded by application, handle annotation extension */
if (yang_metadata_init(h) < 0)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;

View file

@ -1724,7 +1724,9 @@ restconf_clixon_init(clicon_handle h,
*/
if (netconf_module_features(h) < 0)
goto done;
/* In case ietf-yang-metadata is loaded by application, handle annotation extension */
if (yang_metadata_init(h) < 0)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;

View file

@ -359,8 +359,8 @@ api_data_collection(clicon_handle h,
yang_stmt *y = NULL;
cbuf *cbrpc = NULL;
char *depth;
char *count;
char *skip;
char *limit;
char *offset;
char *direction;
char *sort;
char *where;
@ -435,14 +435,14 @@ api_data_collection(clicon_handle h,
}
/* Clixon extensions and collection attributes */
depth = cvec_find_str(qvec, "depth");
count = cvec_find_str(qvec, "count");
skip = cvec_find_str(qvec, "skip");
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, count, skip, direction, sort, where,
depth, limit, offset, direction, sort, where,
&xret) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;

View file

@ -149,7 +149,7 @@ How to debug
Send debug level in run-time to backend:
```
echo "<rpc username=\"root\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><debug xmlns=\"http://clicon.org/lib\"><level>1</level></debug></rpc>]]>]]>" | clixon_netconf -q
echo "<rpc username=\"root\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><debug xmlns=\"http://clicon.org/lib\"><level>1</level></debug></rpc>]]>]]>" | clixon_netconf -q -o CLICON_NETCONF_HELLO_OPTIONAL=true
```
### Set backend debug

View file

@ -120,7 +120,7 @@
/*! Enable list pagination drafts
* draft-wwlh-netconf-list-pagination-00,
* draft-wwlh-netconf-list-pagination-nc-00
* draft-wwlh-netconf-list-pagination-rc-00
* draft-wwlh-netconf-list-pagination-nc-01
* draft-wwlh-netconf-list-pagination-rc-01
*/
#define LIST_PAGINATION

View file

@ -91,12 +91,12 @@ int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen,
int clixon_xml_find_instance_id(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format,
...) __attribute__ ((format (printf, 5, 6)));;
int clixon_instance_id_bind(yang_stmt *yt, cvec *nsctx, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
int clixon_instance_id_parse(yang_stmt *yt, clixon_path **cplistp, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
int clixon_instance_id_parse(yang_stmt *yt, clixon_path **cplistp, cxobj **xerr, const char *format, ...) __attribute__ ((format (printf, 4, 5)));
#else
int clixon_xml_find_api_path(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format, ...);
int clixon_xml_find_instance_id(cxobj *xt, yang_stmt *yt, cxobj ***xvec, int *xlen, const char *format, ...);
int clixon_instance_id_bind(yang_stmt *yt, cvec *nsctx, const char *format, ...);
int clixon_instance_id_parse(yang_stmt *yt, clixon_path **cplistp, const char *format, ...);
int clixon_instance_id_parse(yang_stmt *yt, clixon_path **cplistp, cxobj **xerr, const char *format, ...);
#endif
#endif /* _CLIXON_PATH_H_ */

View file

@ -76,5 +76,6 @@ yang_stmt *yang_find_module_by_namespace(yang_stmt *yspec, char *ns);
yang_stmt *yang_find_module_by_namespace_revision(yang_stmt *yspec, const char *ns, const char *revision);
yang_stmt *yang_find_module_by_name_revision(yang_stmt *yspec, const char *name, const char *revision);
yang_stmt *yang_find_module_by_name(yang_stmt *yspec, char *name);
int yang_metadata_init(clicon_handle h);
#endif /* _CLIXON_YANG_MODULE_H_ */

View file

@ -1256,9 +1256,9 @@ _json_parse(char *str,
/* RFC 7951 Section 4: A namespace-qualified member name MUST be used for all
* members of a top-level JSON object
*/
if (yspec && xml_prefix(x) == NULL
/* && yb != YB_MODULE_NEXT XXX Dont know what this is for */
){
if (yspec && xml_prefix(x) == NULL &&
/* XXX: For top-level config file: */
(yb != YB_NONE || strcmp(xml_name(x),DATASTORE_TOP_SYMBOL)!=0)){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;

View file

@ -50,6 +50,7 @@
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <sys/param.h>
/* cligen */
#include <cligen/cligen.h>
@ -72,6 +73,8 @@
#include "clixon_xpath.h"
#include "clixon_yang_module.h"
#include "clixon_yang_parse_lib.h"
#include "clixon_plugin.h"
#include "clixon_netconf_lib.h"
/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A
@ -1485,6 +1488,7 @@ netconf_module_features(clicon_handle h)
return retval;
}
/*! Load generic yang specs, ie ietf netconf yang module and set enabled features
* @param[in] h Clixon handle
* @retval 0 OK
@ -1494,8 +1498,8 @@ netconf_module_features(clicon_handle h)
int
netconf_module_load(clicon_handle h)
{
int retval = -1;
yang_stmt *yspec;
int retval = -1;
yang_stmt *yspec;
yspec = clicon_dbspec_yang(h);
/* Load yang spec */
@ -1530,6 +1534,15 @@ netconf_module_load(clicon_handle h)
/* Load netconf list pagination */
if (yang_spec_parse_module(h, "ietf-netconf-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
* system module (always loaded) it means all test-cases
*/
/* Load list pagination */
if (yang_spec_parse_module(h, "ietf-list-pagination", NULL, yspec)< 0)
goto done;
#endif
#endif
retval = 0;
done:

View file

@ -1403,9 +1403,8 @@ api_path_resolve(clixon_path *cplist,
/*! Resolve instance-id prefix:names to yang statements
* @param[in] cplist Lisp of clixon-path
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[in] xt XML statement for prefix context
* @retval -1 Error
* @retval 0 Fail
* @retval 0 Fail error in xerr
* @retval 1 OK
* @note: The spec says: prefixes depend on the XML context in which the value occurs.
* However, canonical prefixes/namespaces are used based on loaded yang modules.
@ -1440,7 +1439,7 @@ instance_id_resolve(clixon_path *cplist,
}
if (yang_keyword_get(yt) == Y_SPEC){
if ((yt = yang_find_module_by_prefix_yspec(yspec, cp->cp_prefix)) == NULL){
clicon_err(OE_YANG, ENOENT, "Prefix does not correspond to existing module.");
clicon_err(OE_YANG, ENOENT, "Prefix \"%s\" does not correspond to any existing module", cp->cp_prefix);
goto fail;
}
}
@ -1857,6 +1856,7 @@ clixon_instance_id_bind(yang_stmt *yt,
* example.
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] cplistp Path parse-tree
* @param[out] xerr Contains error if retval=0
* @param[in] format Format string for xpath syntax
* @retval -1 Error
* @retval 0 Non-fatal failure, yang bind failures, etc,
@ -1865,6 +1865,7 @@ clixon_instance_id_bind(yang_stmt *yt,
int
clixon_instance_id_parse(yang_stmt *yt,
clixon_path **cplistp,
cxobj **xerr,
const char *format,
...)
{
@ -1898,8 +1899,11 @@ clixon_instance_id_parse(yang_stmt *yt,
/* Resolve module:name to pointer to yang-stmt, fail if not successful */
if ((ret = instance_id_resolve(cplist, yt)) < 0)
goto done;
if (ret == 0)
if (ret == 0){
if (xerr && netconf_invalid_value_xml(xerr, "application", clicon_err_reason) < 0)
goto done;
goto fail;
}
if (cplistp){
*cplistp = cplist;
cplist = NULL;

View file

@ -159,7 +159,6 @@ plugin_module_struct_set(clicon_handle h,
return 0;
}
/* Access functions */
/*! Get plugin api

View file

@ -887,8 +887,8 @@ clicon_rpc_get(clicon_handle h,
* @param[in] nsc Namespace context for filter
* @param[in] content Clixon extension: all, config, noconfig. -1 means all
* @param[in] depth Nr of XML levels to get, -1 is all, 0 is none
* @param[in] count Collection/clixon extension
* @param[in] skip Collection/clixon extension
* @param[in] limit Collection/clixon extension
* @param[in] offset Collection/clixon extension
* @param[in] direction Collection/clixon extension
* @param[in] sort Collection/clixon extension
* @param[in] where Collection/clixon extension
@ -909,8 +909,8 @@ clicon_rpc_get_pageable_list(clicon_handle h,
cvec *nsc, /* namespace context for xpath */
netconf_content content,
char *depth,
char *count,
char *skip,
char *limit,
char *offset,
char *direction,
char *sort,
char *where,
@ -941,7 +941,7 @@ 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-pagable-list xmlns=\"%s\"", NETCONF_COLLECTION_NAMESPACE);
/* Clixon extension, content=all,config, or nonconfig */
if ((int)content != -1)
cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
@ -955,17 +955,17 @@ clicon_rpc_get_pageable_list(clicon_handle h,
goto done;
cprintf(cb, ">%s</list-target>", xpath);
}
if (count)
cprintf(cb, "<count>%s</count>", count);
if (skip)
cprintf(cb, "<skip>%s</skip>", skip);
if (limit)
cprintf(cb, "<limit>%s</limit>", limit);
if (offset)
cprintf(cb, "<offset>%s</offset>", offset);
if (direction)
cprintf(cb, "<direction>%s</direction>", direction);
if (sort)
cprintf(cb, "<sort>%s</sort>", sort);
if (where)
cprintf(cb, "<where>%s</where>", where);
cprintf(cb, "</get-pageable-list></rpc>");
cprintf(cb, "</get-pagable-list></rpc>");
if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret) < 0)

View file

@ -736,7 +736,7 @@ xpath_first_localonly(cxobj *xcur,
* If result is not nodeset, return empty nodeset
* @param[in] xcur xml-tree where to search
* @param[in] nsc External XML namespace context, or NULL
* @param[in] xpformat Format string for XPATH syntax
* @param[in] xpformat Format string for XPATH syntax
* @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value
* @retval 0 OK

View file

@ -1604,8 +1604,8 @@ yang_print(FILE *f,
}
/* Log/debug info about top-level (sub)modules no recursion
* @param[in] f File to print to.
* @param[in] yspec Yang spec
* @param[in] dbglevel Debug level
*/
int
yang_spec_dump(yang_stmt *yspec,
@ -2402,7 +2402,6 @@ ys_populate_unknown(clicon_handle h,
*/
if (clixon_plugin_extension_all(h, yext, ys) < 0)
goto done;
retval = 0;
done:
if (prefix)

View file

@ -679,3 +679,60 @@ yang_find_module_by_name(yang_stmt *yspec,
return ymod;
return NULL;
}
/*! Callback for handling RFC 7952 annotations
*
* a server indicates that it is prepared to handle that annotation according to the
* annotation's definition. That is, an annotation advertised by the
* server may be attached to an instance of a data node defined in any
* YANG module that is implemented by the server.
* Possibly add them to yang parsing, cardinality, etc?
* as described in Section 3.
* Note this is called by the module using the extension md:annotate, not by
* ietf-yang-metadata.yang
*/
static int
ietf_yang_metadata_extension_cb(clicon_handle h,
yang_stmt *yext,
yang_stmt *ys)
{
int retval = -1;
char *extname;
char *modname;
yang_stmt *ymod;
ymod = ys_module(yext);
modname = yang_argument_get(ymod);
extname = yang_argument_get(yext);
if (strcmp(modname, "ietf-yang-metadata") != 0 || strcmp(extname, "annotation") != 0)
goto ok;
clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
/* XXX Nothing yet - this should signal that xml attribute annotations are allowed
* Possibly, add an "annotation" YANG node.
*/
ok:
retval = 0;
// done:
return retval;
}
/*! In case ietf-yang-metadata is loaded by application, handle annotation extension
* Consider moving fn
*/
int
yang_metadata_init(clicon_handle h)
{
int retval = -1;
clixon_plugin_t *cp = NULL;
/* Create a pseudo-plugin to create extension callback to set the ietf-yang-meta
* yang-data extension for api-root top-level restconf function.
*/
if (clixon_pseudo_plugin(h, "pseudo yang metadata", &cp) < 0)
goto done;
clixon_plugin_api_get(cp)->ca_extension = ietf_yang_metadata_extension_cb;
retval = 0;
done:
return retval;
}

View file

@ -1,271 +0,0 @@
#!/usr/bin/env bash
# Example-module from draft-netconf-list-pagination-nc-00.txt
# Assumes variable fexample is set to name of yang file
# An extra leaf-list is added (clixon)
cat <<EOF > $fexample
module example-module {
yang-version 1.1;
namespace "http://example.com/ns/example-module";
prefix exm;
import iana-crypt-hash {
prefix ianach;
}
import ietf-inet-types {
prefix inet;
}
import ietf-yang-types {
prefix yang;
}
organization
"Example, Inc.";
contact
"support at example.com";
description
"Example Data Model Module.";
revision 2020-10-06 {
description
"Initial version.";
reference
"example.com document 1-4673.";
}
container admins {
description
"Admin Group configuration.";
list admin {
key "name";
description
"List of admins for admin group configuration.";
ordered-by system;
leaf name {
type string {
length "1 .. max";
}
description
"The name of the admin.";
}
leaf access {
type enumeration {
enum permit {
description
"Permit access privilege.";
}
enum deny {
description
"Deny access privilege.";
}
enum limited {
description
"Limited access privilege.";
}
}
default "permit";
description
"The Access privilege type for this admin.";
}
leaf email-address {
type inet:email-address;
description
"Contact email of the admin.";
}
leaf password {
type ianach:crypt-hash;
description
"The password for this entry.";
}
leaf-list status {
type string;
config false;
description
"The status for this entry.";
}
container preference {
leaf-list number {
type uint8;
description
"Defines the perference numbers for the admin.";
}
description
"Preference parameters.";
}
list skill {
key "name";
description
"Represents one 'sill' resource within one
'admin' resource.";
leaf name {
type string {
length "1 .. max";
}
description
"The name of the skill.";
}
leaf rank {
type uint16;
description
"The rank identifying the rank on
the skill.";
}
}
}
}
container rulebase {
description
"Rule base configuration";
list rule {
key "name";
description
"List of rules for rulebase.";
ordered-by user;
leaf name {
type string {
length "1 .. max";
}
description
"The name of the rule.";
}
leaf match {
type string {
length "1 .. max";
}
description
"The rules in this rulebase determine what fields will be
matched upon before any action is taken on them.";
}
leaf action {
type enumeration {
enum forwarding {
description
"Specify forwarding behavior per rule entry.";
}
enum logging {
description
"Specify logging behavior per rule entry.";
}
}
default "logging";
description
"Defintion of the action for this rule entry.";
}
}
}
container device-logs {
description
"Device log configuration";
list device-log {
description
"List of device logs.";
config false;
leaf device-id {
type string;
description
"The device id of the device log.";
}
leaf time-received {
type yang:date-and-time;
description
"The timestamp value at the time this
log was received.";
}
leaf time-generated {
type yang:date-and-time;
description
"The timestamp value at the time this
log was generated.";
}
leaf message {
type string;
description
"Message given at start of login session.";
}
}
}
container audit-logs {
description
"Audit log configuration";
list audit-log {
key "log-creation";
description
"List of audit logs.";
config false;
leaf source-ip {
type inet:ip-address;
description
"The IP address of the targeted object.";
}
leaf log-creation {
type yang:date-and-time;
description
"The timestamp value at the time this
log was created.";
}
leaf request {
type string;
description
"Request type of audit log.";
}
leaf outcome {
type boolean;
default "true";
description
"Indicate the audit log is retrieved sucessfully or not.";
}
}
}
container prefixes {
description
"Enclosing container for the list of prefixes in a policy
prefix list";
list prefix-list {
key "ip-prefix masklength-lower masklength-upper";
description
"List of prefixes in the prefix set";
leaf ip-prefix {
type inet:ip-prefix;
mandatory true;
description
"The prefix member in CIDR notation -- while the
prefix may be either IPv4 or IPv6, most
implementations require all members of the prefix set
to be the same address family. Mixing address types in
the same prefix set is likely to cause an error.";
}
leaf masklength-lower {
type uint8;
description
"Masklength range lower bound.";
}
leaf masklength-upper {
type uint8 {
range "1..128";
}
must '../masklength-upper >= ../masklength-lower' {
error-message "The upper bound should not be lessthan lower bound.";
}
description
"Masklength range upper bound.
The combination of masklength-lower and masklength-upper
define a range for the mask length, or single 'exact'
length if masklength-lower and masklenght-upper are equal.
Example: 10.3.192.0/21 through 10.3.192.0/24 would be
expressed as prefix: 10.3.192.0/21,
masklength-lower=21,
masklength-upper=24
Example: 10.3.192.0/21 (an exact match) would be
expressed as prefix: 10.3.192.0/21,
masklength-lower=21,
masklength-upper=21";
}
}
}
}
EOF

299
test/example_social.sh Executable file
View file

@ -0,0 +1,299 @@
#!/usr/bin/env bash
# Example-social from draft-netconf-list-pagination-00.txt appendix A.1
# Assumes variable fexample is set to name of yang file
# Note inverted pattern is commented
cat <<EOF > $fexample
module example-social {
yang-version 1.1;
namespace "http://example.com/ns/example-social";
prefix es;
import ietf-yang-types {
prefix yang;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-inet-types {
prefix inet;
reference
"RFC 6991: Common YANG Data Types";
}
import iana-crypt-hash {
prefix ianach;
reference
"RFC 7317: A YANG Data Model for System Management";
}
organization "Example, Inc.";
contact "support@example.com";
description "Example Social Data Model.";
revision 2021-07-21 { /* clixon edit */
description
"Initial version.";
reference
"RFC XXXX: Example social module.";
}
container members {
description
"Container for list of members.";
list member {
key "member-id";
description
"List of members.";
leaf member-id {
type string {
length "1..80";
/*
pattern '.*[\n].*' {
modifier invert-match;
}
*/
}
description
"The member's identifier.";
}
leaf email-address {
type inet:email-address;
mandatory true;
description
"The member's email address.";
}
leaf password {
type ianach:crypt-hash;
mandatory true;
description
"The member's hashed-password.";
}
leaf avatar {
type binary;
description
"An binary image file.";
}
leaf tagline {
type string {
length "1..80";
/*
pattern '.*[\n].*' {
modifier invert-match;
}
*/
}
description
"The member's tagline.";
}
container privacy-settings {
leaf hide-network {
type boolean;
description
"Hide who you follow and who follows you.";
}
leaf post-visibility {
type enumeration {
enum public {
description
"Posts are public.";
}
enum unlisted {
description
"Posts are unlisted, though visable to all.";
}
enum followers-only {
description
"Posts only visible to followers.";
}
}
default public;
description
"The post privacy setting.";
}
description
"Preferences for the member.";
}
leaf-list following {
type leafref {
path "/members/member/member-id";
}
description
"Other members this members is following.";
}
container posts {
description
"The member's posts.";
list post {
key timestamp;
leaf timestamp {
type yang:date-and-time;
description
"The timestamp for the member's post.";
}
leaf title {
type string {
length "1..80";
/*
pattern '.*[\n].*' {
modifier invert-match;
}
*/
}
description
"A one-line title.";
}
leaf body {
type string;
mandatory true;
description
"The body of the post.";
}
description
"A list of posts.";
}
}
container favorites {
description
"The member's favorites.";
leaf-list uint8-numbers {
type uint8;
ordered-by user;
description
"The member's favorite uint8 numbers.";
}
leaf-list uint64-numbers {
type uint64;
ordered-by user;
description
"The member's favorite uint64 numbers.";
}
leaf-list int8-numbers {
type int8;
ordered-by user;
description
"The member's favorite int8 numbers.";
}
leaf-list int64-numbers {
type int64;
ordered-by user;
description
"The member's favorite uint64 numbers.";
}
leaf-list decimal64-numbers {
type decimal64 {
fraction-digits 5;
}
ordered-by user;
description
"The member's favorite decimal64 numbers.";
}
leaf-list bits {
type bits {
bit zero {
position 0;
description "zero";
}
bit one {
position 1;
description "one";
}
bit two {
position 2;
description "two";
}
}
ordered-by user;
description
"The member's favorite bits.";
}
}
container stats {
config false;
description
"Operational state members values.";
leaf joined {
type yang:date-and-time;
mandatory true;
description
"Timestamp when member joined.";
}
leaf membership-level {
type enumeration {
enum admin {
description
"Site administrator.";
}
enum standard {
description
"Standard membership level.";
}
enum pro {
description
"Professional membership level.";
}
}
mandatory true;
description
"The membership level for this member.";
}
leaf last-activity {
type yang:date-and-time;
description
"Timestamp of member's last activity.";
}
}
}
}
container audit-logs {
config false;
description
"Audit log configuration";
list audit-log {
description
"List of audit logs.";
leaf timestamp {
type yang:date-and-time;
mandatory true;
description
"The timestamp for the event.";
}
leaf member-id {
type string;
mandatory true;
description
"The 'member-id' of the member.";
}
leaf source-ip {
type inet:ip-address;
mandatory true;
description
"The apparent IP address the member used.";
}
leaf request {
type string;
mandatory true;
description
"The member's request.";
}
leaf outcome {
type boolean;
mandatory true;
description
"Indicate if request was permitted.";
}
}
}
}
EOF

View file

@ -1,15 +1,21 @@
#!/usr/bin/env bash
# Restconf RFC8040 Appendix A and B "jukebox" example
# For pagination / scaling I-D activity
# List pagination tests according to draft-wwlh-netconf-list-pagination-00
# Backlog items:
# 1. "remaining" annotation RFC 7952
# 2. pattern '.*[\n].*' { modifier invert-match;
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
APPNAME=example
cfg=$dir/conf.xml
fexample=$dir/example-module.yang
fexample=$dir/example-social.yang
fstate=$dir/mystate.xml
# Common example-module spec (fexample must be set)
. ./example_social.sh
# Define default restconfig config: RESTCONFIG
RESTCONFIG=$(restconf_config none false)
@ -26,265 +32,230 @@ cat <<EOF > $cfg
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_FORMAT>json</CLICON_XMLDB_FORMAT>
<CLICON_STREAM_DISCOVERY_RFC8040>true</CLICON_STREAM_DISCOVERY_RFC8040>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_VALIDATE_STATE_XML>true</CLICON_VALIDATE_STATE_XML>
$RESTCONFIG
</clixon-config>
EOF
# See draft-wwlh-netconf-list-pagination-00 A.2 (except stats and audit-log)
cat <<'EOF' > $dir/startup_db
<config>
<admins xmlns="http://example.com/ns/example-module">
<admin>
<name>Alice</name>
<access>permit</access>
<email-address>alice@example.com</email-address>
<password>$0$1543</password>
<preference>
<number>1</number>
<number>2</number>
</preference>
<skill>
<name>Customer Service</name>
<rank>99</rank>
</skill>
<skill>
<name>Problem Solving</name>
<rank>90</rank>
</skill>
</admin>
<admin>
<name>Bob</name>
<access>limited</access>
<email-address>bob@example.com</email-address>
<password>$0$2789</password>
<preference>
<number>2</number>
<number>3</number>
</preference>
<skill>
<name>Conflict Resolution</name>
<rank>93</rank>
</skill>
<skill>
<name>Management</name>
<rank>23</rank>
</skill>
<skill>
<name>Organization</name>
<rank>44</rank>
</skill>
<skill>
<name>Problem Solving</name>
<rank>98</rank>
</skill>
</admin>
<admin>
<name>Joe</name>
<access>permit</access>
<email-address>joe@example.com</email-address>
<password>$0$6523</password>
<preference>
<number>1</number>
<number>4</number>
</preference>
<skill>
<name>Management</name>
<rank>96</rank>
</skill>
<skill>
<name>Collaboration</name>
<rank>92</rank>
</skill>
</admin>
<admin>
<name>Frank</name>
<access>deny</access>
<email-address>frank@example.com</email-address>
<password>$0$4030</password>
<preference>
<number>5</number>
<number>9</number>
</preference>
<skill>
<name>Organization</name>
<rank>90</rank>
</skill>
<skill>
<name>Negotiation</name>
<rank>80</rank>
</skill>
</admin>
<admin>
<name>Tom</name>
<access>permit</access>
<email-address>tom@example.com</email-address>
<password>$0$2376</password>
<preference>
<number>2</number>
<number>5</number>
</preference>
<skill>
<name>Adaptability.</name>
<rank>98</rank>
</skill>
<skill>
<name>Active Listening</name>
<rank>85</rank>
</skill>
</admin>
</admins>
<rulebase xmlns="http://example.com/ns/example-module">
<rule>
<name>SvrA-http</name>
<match>92.0.2.0/24</match>
<action>forwarding</action>
</rule>
<rule>
<name>SvrA-ftp</name>
<match>203.0.113.1/32</match>
<action>forwarding</action>
</rule>
<rule>
<name>p2p</name>
<match>p2p</match>
<action>logging</action>
</rule>
<rule>
<name>any</name>
<match>any</match>
<action>logging</action>
</rule>
<rule>
<name>SvrA-tcp</name>
<match>80</match>
<action>forwarding</action>
</rule>
</rulebase>
<prefixes xmlns="http://example.com/ns/example-module">
<prefix-list>
<ip-prefix>10.0.0.0/8</ip-prefix>
<masklength-lower>17</masklength-lower>
<masklength-upper>18</masklength-upper>
</prefix-list>
<prefix-list>
<ip-prefix>2000:1::/48</ip-prefix>
<masklength-lower>48</masklength-lower>
<masklength-upper>48</masklength-upper>
</prefix-list>
<prefix-list>
<ip-prefix>2000:2::/48</ip-prefix>
<masklength-lower>48</masklength-lower>
<masklength-upper>48</masklength-upper>
</prefix-list>
<prefix-list>
<ip-prefix>2000:3::/48</ip-prefix>
<masklength-lower>16</masklength-lower>
<masklength-upper>16</masklength-upper>
</prefix-list>
<prefix-list>
<ip-prefix>::/0</ip-prefix>
<masklength-lower>0</masklength-lower>
<masklength-upper>128</masklength-upper>
</prefix-list>
</prefixes>
</config>
{"config":
{
"example-social:members": {
"member": [
{
"member-id": "alice",
"email-address": "alice@example.com",
"password": "$0$1543",
"avatar": "BASE64VALUE=",
"tagline": "Every day is a new day",
"privacy-settings": {
"hide-network": "false",
"post-visibility": "public"
},
"following": ["bob", "eric", "lin"],
"posts": {
"post": [
{
"timestamp": "2020-07-08T13:12:45Z",
"title": "My first post",
"body": "Hiya all!"
},
{
"timestamp": "2020-07-09T01:32:23Z",
"title": "Sleepy...",
"body": "Catch y'all tomorrow."
}
]
},
"favorites": {
"uint8-numbers": [17, 13, 11, 7, 5, 3],
"int8-numbers": [-5, -3, -1, 1, 3, 5]
}
},
{
"member-id": "bob",
"email-address": "bob@example.com",
"password": "$0$1543",
"avatar": "BASE64VALUE=",
"tagline": "Here and now, like never before.",
"posts": {
"post": [
{
"timestamp": "2020-08-14T03:32:25Z",
"body": "Just got in."
},
{
"timestamp": "2020-08-14T03:33:55Z",
"body": "What's new?"
},
{
"timestamp": "2020-08-14T03:34:30Z",
"body": "I'm bored..."
}
]
},
"favorites": {
"decimal64-numbers": ["3.14159", "2.71828"]
}
},
{
"member-id": "eric",
"email-address": "eric@example.com",
"password": "$0$1543",
"avatar": "BASE64VALUE=",
"tagline": "Go to bed with dreams; wake up with a purpose.",
"following": ["alice"],
"posts": {
"post": [
{
"timestamp": "2020-09-17T18:02:04Z",
"title": "Son, brother, husband, father",
"body": "What's your story?"
}
]
},
"favorites": {
"bits": ["two", "one", "zero"]
}
},
{
"member-id": "lin",
"email-address": "lin@example.com",
"password": "$0$1543",
"privacy-settings": {
"hide-network": "true",
"post-visibility": "followers-only"
},
"following": ["joe", "eric", "alice"]
},
{
"member-id": "joe",
"email-address": "joe@example.com",
"password": "$0$1543",
"avatar": "BASE64VALUE=",
"tagline": "Greatness is measured by courage and heart.",
"privacy-settings": {
"post-visibility": "unlisted"
},
"following": ["bob"],
"posts": {
"post": [
{
"timestamp": "2020-10-17T18:02:04Z",
"body": "What's your status?"
}
]
}
}
]
}
}
}
EOF
# See draft-wwlh-netconf-list-pagination-00 A.2 (only stats and audit-log)
cat<<EOF > $fstate
<admins xmlns="http://example.com/ns/example-module">
<admin>
<name>Alice</name>
<status>Available</status>
</admin>
<admin>
<name>Bob</name>
<status>Busy</status>
</admin>
<admin>
<name>Joe</name>
<status>Do Not Disturb</status>
</admin>
<admin>
<name>Frank</name>
<status>Offline</status>
</admin>
<admin>
<name>Tom</name>
<status>Do Not Disturb</status>
</admin>
</admins>
<device-logs xmlns="http://example.com/ns/example-module">
<device-log>
<device-id>Cloud-IoT-Device-A</device-id>
<time-received>2020-07-08T12:38:32Z</time-received>
<time-generated>2020-07-08T12:37:12Z</time-generated>
<message>Upload contains 6 datapoints</message>
</device-log>
<device-log>
<device-id>Cloud-IoT-Device-B</device-id>
<time-received>2020-07-08T16:20:54Z</time-received>
<time-generated>2020-07-08T16:20:14Z</time-generated>
<message>Upload successful</message>
</device-log>
<device-log>
<device-id>Cloud-IoT-Device-C</device-id>
<time-received>2020-07-08T17:30:34Z</time-received>
<time-generated>2020-07-08T17:30:12Z</time-generated>
<message>Receive a configuration update</message>
</device-log>
<device-log>
<device-id>Cloud-IoT-Device-D</device-id>
<time-received>2020-07-08T18:40:13Z</time-received>
<time-generated>2020-07-08T18:40:00Z</time-generated>
<message>Keep-alive ping sent to server</message>
</device-log>
<device-log>
<device-id>Cloud-IoT-Device-E</device-id>
<time-received>2020-07-08T19:48:34Z</time-received>
<time-generated>2020-07-08T19:48:00Z</time-generated>
<message>Uploading data to DataPoint</message>
</device-log>
</device-logs>
<audit-logs xmlns="http://example.com/ns/example-module">
<audit-log>
<source-ip>192.168.0.92</source-ip>
<log-creation>2020-11-01T06:47:59Z</log-creation>
<request>User-logged-out</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<source-ip>192.168.0.92</source-ip>
<log-creation>2020-11-01T06:49:03Z</log-creation>
<request>User-logged-in</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<source-ip>192.168.0.92</source-ip>
<log-creation>2020-11-01T06:51:34Z</log-creation>
<request>Patron-card-viewed</request>
<outcome>false</outcome>
</audit-log>
<audit-log>
<source-ip>192.168.0.92</source-ip>
<log-creation>2020-11-01T06:53:01Z</log-creation>
<request>User-logged-out</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<source-ip>192.168.0.92</source-ip>
<log-creation>2020-11-01T06:56:22Z</log-creation>
<request>User-logged-in</request>
<outcome>false</outcome>
</audit-log>
</audit-logs>
<members xmlns="http://example.com/ns/example-social">
<member>
<member-id>alice</member-id>
<stats>
<joined>2020-07-08T12:38:32Z</joined>
<membership-level>admin</membership-level>
<last-activity>2021-04-01T02:51:11Z</last-activity>
</stats>
</member>
<member>
<member-id>bob</member-id>
<stats>
<joined>2020-08-14T03:30:00Z</joined>
<membership-level>standard</membership-level>
<last-activity>2020-08-14T03:34:30Z</last-activity>
</stats>
</member>
<member>
<member-id>eric</member-id>
<stats>
<joined>2020-09-17T19:38:32Z</joined>
<membership-level>pro</membership-level>
<last-activity>2020-09-17T18:02:04Z</last-activity>
</stats>
</member>
<member>
<member-id>lin</member-id>
<stats>
<joined>2020-07-09T12:38:32Z</joined>
<membership-level>standard</membership-level>
<last-activity>2021-04-01T02:51:11Z</last-activity>
</stats>
</member>
<member>
<member-id>joe</member-id>
<stats>
<joined>2020-10-08T12:38:32Z</joined>
<membership-level>pro</membership-level>
<last-activity>2021-04-01T02:51:11Z</last-activity>
</stats>
</member>
</members>
<audit-logs xmlns="http://example.com/ns/example-social">
<audit-log>
<timestamp>": "2020-10-11T06:47:59Z",</timestamp>
<member-id>alice</member-id>
<source-ip>192.168.0.92</source-ip>
<request>POST /groups/group/2043</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<timestamp>2020-11-01T15:22:01Z</timestamp>
<member-id>bob</member-id>
<source-ip>192.168.2.16</source-ip>
<request>POST /groups/group/123</request>
<outcome>false</outcome>
</audit-log>
<audit-log>
<timestamp>2020-12-12T21:00:28Z</timestamp>
<member-id>eric</member-id>
<source-ip>192.168.254.1</source-ip>
<request>POST /groups/group/10</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<timestamp>2021-01-03T06:47:59Z</timestamp>
<member-id>alice</member-id>
<source-ip>192.168.0.92</source-ip>
<request>POST /groups/group/333</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<timestamp>2021-01-21T10:00:00Z</timestamp>
<member-id>bob</member-id>
<source-ip>192.168.2.16</source-ip>
<request>POST /groups/group/42</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<timestamp>2020-02-07T09:06:21Z</timestamp>
<member-id>alice</member-id>
<source-ip>192.168.0.92</source-ip>
<request>POST /groups/group/1202</request>
<outcome>true</outcome>
</audit-log>
<audit-log>
<timestamp>2020-02-28T02:48:11Z</timestamp>
<member-id>bob</member-id>
<source-ip>192.168.2.16</source-ip>
<request>POST /groups/group/345</request>
<outcome>true</outcome>
</audit-log>
</audit-logs>
EOF
# Common example-module spec (fexample must be set)
. ./example_module.sh
new "test params: -f $cfg -s startup -- -sS $fstate"
if [ $BE -ne 0 ]; then
@ -295,7 +266,7 @@ if [ $BE -ne 0 ]; then
fi
sudo pkill -f clixon_backend # to be sure
new "start backend -s startup -f $cfg -- -sS $mystate"
new "start backend -s startup -f $cfg -- -sS $fstate"
start_backend -s startup -f $cfg -- -sS $fstate
fi
@ -313,18 +284,24 @@ fi
new "wait restconf"
wait_restconf
# draft-wwlh-netconf-list-pagination-nc-00.txt
new "C.1. 'count' Parameter 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:exm=\"http://example.com/ns/example-module\">/exm:admins/exm:admin[exm:name='Bob']/exm:skill</list-target><count>2</count></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><skill xmlns=\"http://example.com/ns/example-module\"><name>Conflict Resolution</name><rank>93</rank></skill><skill xmlns=\"http://example.com/ns/example-module\"><name>Management</name><rank>23</rank></skill></pageable-list></rpc-reply>]]>]]>$"
new "A.3.1.1. 'limit=1' NETCONF"
# XXX: augment GET instead, not RPC
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pagable-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>1</limit></get-pagable-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\" lpg:remaining=\"5\" xmlns:lpg=\"urn:ietf:params:xml:ns:yang:ietf-list-pagination\">17</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
#new "A.3.1.1. 'limit' Parameter RESTCONF"
#expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang.collection+xml" $RCPROTO://localhost/restconf/data/ietf-netconf-list-pagination:get-pagable-list -d "<get-pagable-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>1</limit></get-pagable-list>")" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" foo
new "A.3.1.2. 'limit=2' NETCONF"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-pagable-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>2</limit></get-pagable-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\" lpg:remaining=\"4\" xmlns:lpg=\"urn:ietf:params:xml:ns:yang:ietf-list-pagination\">17</uint8-numbers><uint8-numbers xmlns=\"http://example.com/ns/example-social\">13</uint8-numbers></pageable-list></rpc-reply>]]>]]>$"
exit
new "C.2. 'skip' Parameter 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:exm=\"http://example.com/ns/example-module\">/exm:admins/exm:admin[exm:name='Bob']/exm:skill</list-target><count>2</count><skip>2</skip></get-pageable-list></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><pageable-list xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination\"><skill xmlns=\"http://example.com/ns/example-module\"><name>Organization</name><rank>44</rank></skill><skill xmlns=\"http://example.com/ns/example-module\"><name>Problem Solving</name><rank>98</rank></skill></pageable-list></rpc-reply>]]>]]>$"
# CLI
# XXX This relies on a very specific clispec command: need a more generic test
new "cli show"
echo "$clixon_cli -1 -f $cfg -l o show pagination"
expectpart "$($clixon_cli -1 -f $cfg -l o show pagination)" 0 "<skill xmlns=\"http://example.com/ns/example-module\">" "<name>Conflict Resolution</name>" "<rank>93</rank>" "<name>Management</name>" "<rank>23</rank>" --not-- "<name>Organization</name>" "<rank>44</rank></skill>"
#new "cli show"
#expectpart "$($clixon_cli -1 -f $cfg -l o show pagination)" 0 "<skill xmlns=\"http://example.com/ns/example-module\">" "<name>Conflict Resolution</name>" "<rank>93</rank>" "<name>Management</name>" "<rank>23</rank>" --not-- "<name>Organization</name>" "<rank>44</rank></skill>"
# draft-wwlh-netconf-list-pagination-rc-00.txt
#new "A.1. 'count' Parameter RESTCONF"

View file

@ -204,7 +204,10 @@ module clixon-config {
"Datastore format.";
type enumeration{
enum xml{
description "Save and load xmldb as XML";
description
"Save and load xmldb as XML
More specifically, such a file looks like: <config>...</config> provided
DATASTORE_TOP_SYMBOL is 'config'";
}
enum json{
description "Save and load xmldb as JSON";

View file

@ -3,6 +3,16 @@ module ietf-netconf-list-pagination {
namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-list-pagination";
prefix ycoll;
import ietf-netconf {
prefix nc;
reference
"RFC 6241: Network Configuration Protocol (NETCONF)";
}
import ietf-netconf-with-defaults {
prefix ncwd;
reference
"RFC 6243: With-defaults Capability for NETCONF";
}
import ietf-yang-types {
prefix yang;
reference
@ -20,16 +30,6 @@ module ietf-netconf-list-pagination {
"RFC 8342: Network Management Datastore Architecture
(NMDA)";
}
import ietf-netconf {
prefix nc;
reference
"RFC 6241: Network Configuration Protocol (NETCONF)";
}
import ietf-netconf-with-defaults {
prefix ncwd;
reference
"RFC 6243: With-defaults Capability for NETCONF";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
@ -64,7 +64,6 @@ module ietf-netconf-list-pagination {
This version of this YANG module is part of RFC 8526; see
the RFC itself for full legal notices.";
revision 2020-10-30 {
description
"Initial revision.";
@ -91,7 +90,7 @@ module ietf-netconf-list-pagination {
Datastore Architecture, Section 3.1.1.2";
}
rpc get-pageable-list {
rpc get-pagable-list {
description
"Use enhanced filtering features to retrieve data from a
specific NMDA datastore. The content returned by get-data
@ -240,7 +239,7 @@ module ietf-netconf-list-pagination {
mandatory true;
type string;
}
leaf count {
leaf limit {
type union {
type uint32;
type string {
@ -254,7 +253,7 @@ module ietf-netconf-list-pagination {
greater than or equal to 1, or the string 'unbounded'.
The string 'unbounded' is the default value.";
}
leaf skip {
leaf offset {
type union {
type uint32;
type string {