- New netconf-specific uint32 parse functions

- Added failure handling to xpath traverse_canonical
- Started pagination cli code
This commit is contained in:
Olof hagsand 2021-08-30 14:43:24 +02:00
parent 390b0886ed
commit a046306270
16 changed files with 410 additions and 241 deletions

View file

@ -732,7 +732,7 @@ from_client_kill_session(clicon_handle h,
{
int retval = -1;
uint32_t id; /* session id */
char *str;
char *str = NULL;
struct client_entry *ce;
char *db = "running"; /* XXX */
cxobj *x;
@ -745,15 +745,10 @@ from_client_kill_session(clicon_handle h,
goto done;
goto ok;
}
if ((ret = parse_uint32(str, &id, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint32");
if ((ret = netconf_parse_uint32("session-id", str, NULL, 0, cbret, &id)) < 0)
goto done;
}
if (ret == 0){
if (netconf_bad_element(cbret, "protocol", "session-id", reason) < 0)
goto done;
goto done;
}
if (ret == 0)
goto ok;
/* may or may not be in active client list, probably not */
if ((ce = ce_find_byid(backend_client_list(h), id)) != NULL){
xmldb_unlock_all(h, id); /* Removes locks on all databases */

View file

@ -273,13 +273,14 @@ client_statedata(clicon_handle h,
}
#ifdef LIST_PAGINATION
/*! Help function for parsing restconf query parameter and setting netconf attribute
*
* If not "unbounded", parse and set a numeric value
* @param[in] h Clixon handle
* @param[in] name Name of attribute
* @param[in] defaultstr Default string which is accepted and sets value to 0
* @param[in,out] cbret Output buffer for internal RPC message
* @param[in,out] cbret Output buffer for internal RPC message if invalid
* @param[out] value Value
* @retval -1 Error
* @retval 0 Invalid, cbret set
@ -293,35 +294,15 @@ element2value(clicon_handle h,
cbuf *cbret,
uint32_t *value)
{
int retval = -1;
char *valstr;
int ret;
char *reason = NULL;
cxobj *x;
*value = 0;
if ((x = xml_find_type(xe, NULL, name, CX_ELMNT)) != NULL &&
(valstr = xml_body(x)) != NULL &&
strcmp(valstr, defaultstr) != 0){
if ((ret = parse_uint32(valstr, value, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint32");
goto done;
(valstr = xml_body(x)) != NULL){
return netconf_parse_uint32(name, valstr, defaultstr, 0, cbret, value);
}
if (ret == 0){
if (netconf_bad_element(cbret, "application",
name, "Unrecognized value") < 0)
goto done;
goto fail;
}
}
retval = 1;
done:
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
return 1;
}
#endif
@ -368,8 +349,8 @@ get_common(clicon_handle h,
char *reason = NULL;
cbuf *cbmsg = NULL; /* For error msg */
#ifdef LIST_PAGINATION
uint32_t limit = 0;
uint32_t offset = 0;
uint32_t limit = 0;
uint32_t total = 0;
uint32_t remaining = 0;
char *direction = NULL;
@ -393,6 +374,8 @@ get_common(clicon_handle h,
if ((xfilter = xml_find(xe, "filter")) != NULL){
char *xpath0;
cvec *nsc1 = NULL;
cbuf *cbreason = NULL;
if ((xpath0 = xml_find_value(xfilter, "select"))==NULL)
xpath0 = "/";
/* Create namespace context for xpath from <filter>
@ -402,8 +385,16 @@ get_common(clicon_handle h,
else
if (xml_nsctx_node(xfilter, &nsc) < 0)
goto done;
if (xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1) < 0)
if ((ret = xpath2canonical(xpath0, nsc, yspec, &xpath, &nsc1, &cbreason)) < 0)
goto done;
if (ret == 0){
if (netconf_bad_attribute(cbret, "application",
"select", cbuf_get(cbreason)) < 0)
goto done;
goto ok;
}
if (cbreason)
cbuf_free(cbreason);
if (nsc)
xml_nsctx_free(nsc);
nsc = nsc1;
@ -435,36 +426,42 @@ get_common(clicon_handle h,
if (yang_path_arg(yspec, xpath, &ylist) < 0)
goto done;
if (ylist == NULL){
if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not found") < 0)
if ((cbmsg = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* error reason should be in clicon_err_reason */
cprintf(cbmsg, "Netconf get list-pagination: \"%s\" not found", xpath);
if (netconf_invalid_value(cbret, "application", cbuf_get(cbmsg)) < 0)
goto done;
goto ok;
}
if (yang_keyword_get(ylist) != Y_LIST &&
yang_keyword_get(ylist) != Y_LEAF_LIST){
if (netconf_invalid_value_xml(&xerr, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0)
if (netconf_invalid_value(cbret, "application", "list-pagination is enabled but target is not leaf or leaf-list") < 0)
goto done;
goto ok;
}
if ((list_config = yang_config_ancestor(ylist)) != 0){ /* config list */
if (content == CONTENT_NONCONFIG){
if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a config list but content request is nonconfig") < 0)
if (netconf_invalid_value(cbret, "application", "list-pagination targets a config list but content request is nonconfig") < 0)
goto done;
goto ok;
}
}
else { /* state list */
if (content == CONTENT_CONFIG){
if (netconf_invalid_value_xml(&xerr, "application", "list-pagination targets a state list but content request is config") < 0)
if (netconf_invalid_value(cbret, "application", "list-pagination targets a state list but content request is config") < 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;
/* limit */
if ((ret = element2value(h, xe, "limit", "unbounded", cbret, &limit)) < 0)
goto done;
/* direction */
if (ret && (x = xml_find_type(xe, NULL, "direction", CX_ELMNT)) != NULL){
direction = xml_body(x);
@ -517,7 +514,11 @@ get_common(clicon_handle h,
}
}
else{
/* XXX remaining of state list??*/
/* Remaining of state list. Two strategies:
* 1. New api where state callback is registered, lock, iterative, unlock
* 2. Read all here and count (fallback)
*/
}
/* Append predicate to original xpath and replace it */
xpath2 = cbuf_get(cbpath);
@ -525,9 +526,7 @@ get_common(clicon_handle h,
else
xpath2 = xpath;
#endif /* LIST_PAGINATION */
/* Read config
* XXX This seems unnecessary complex
*/
/* Read configuration */
switch (content){
case CONTENT_CONFIG: /* config data only */
/* specific xpath */

View file

@ -1317,10 +1317,13 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv)
* XXX: This is hardcoded only for test_pagination.sh
* @param[in] h Clicon handle
* @param[in] cvv Vector of cli string and instantiated variables
* @param[in] argv Vector. Format: <xpath> <prefix> <namespace>
* @param[in] argv Vector. Format: <xpath> <prefix> <namespace> <format>
* Also, if there is a cligen variable called "xpath" it will override argv xpath arg
*/
int
cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
cli_pagination(clicon_handle h,
cvec *cvv,
cvec *argv)
{
int retval = -1;
cbuf *cb = NULL;
@ -1333,11 +1336,18 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
char *formatstr;
enum format_enum format;
cxobj *xc;
cg_var *cv;
int i;
const int win = 20;
if (cvec_len(argv) != 4){
clicon_err(OE_PLUGIN, 0, "Expected usage: <xpath> <prefix> <namespace> <format>");
goto done;
}
/* prefix:variable overrides argv */
if ((cv = cvec_find(cvv, "xpath")) != NULL)
xpath = cv_string_get(cv);
else
xpath = cvec_i_str(argv, 0);
prefix = cvec_i_str(argv, 1);
namespace = cvec_i_str(argv, 2);
@ -1348,9 +1358,13 @@ 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, -1,
"2", "0",
NULL, NULL, NULL,
for (i = 0; i < 10; i++){
if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc,
CONTENT_NONCONFIG,
-1, /* depth */
win*i, /* offset */
win*(i+1), /* limit */
NULL, NULL, NULL, /* nyi */
&xret) < 0){
goto done;
}
@ -1376,6 +1390,7 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
case FORMAT_NETCONF:
break;
}
}
retval = 0;
done:
if (xret)
@ -1386,7 +1401,7 @@ cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
}
#else
int
cli_pagination(clicon_handle h, cvec *vars, cvec *argv)
cli_pagination(clicon_handle h, cvec *cvv, cvec *argv)
{
fprintf(stderr, "Not yet implemented\n");
return 0;

View file

@ -207,7 +207,6 @@ expand_dbvar(void *h,
cxobj *xbot = NULL; /* xpath, NULL if datastore */
yang_stmt *y = NULL; /* yang spec of xpath */
yang_stmt *yp;
char *reason = NULL;
cvec *nsc = NULL;
int ret;
int cvvi = 0;
@ -368,8 +367,6 @@ expand_dbvar(void *h,
xml_free(xerr);
if (nsc)
xml_nsctx_free(nsc);
if (reason)
free(reason);
if (api_path)
free(api_path);
if (xvec)

View file

@ -351,8 +351,8 @@ api_data_collection(clicon_handle h,
yang_stmt *y = NULL;
cbuf *cbrpc = NULL;
int32_t depth = -1; /* Nr of levels to print, -1 is all, 0 is none */
char *limit;
char *offset;
uint32_t limit = 0;
uint32_t offset = 0;
char *direction;
char *sort;
char *where;
@ -446,13 +446,29 @@ api_data_collection(clicon_handle h,
}
}
}
limit = cvec_find_str(qvec, "limit");
offset = cvec_find_str(qvec, "offset");
if ((attr = cvec_find_str(qvec, "limit")) != NULL){ /* 1-uint32 or "unbounded" */
if ((ret = netconf_parse_uint32_xml("limit", attr, "unbounded", 0, &xerr, &limit)) < 0)
goto done;
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
if ((attr = cvec_find_str(qvec, "offset")) != NULL){ /* 1-uint32 or "none" */
if ((ret = netconf_parse_uint32_xml("offset", attr, "none", 0, &xerr, &offset)) < 0)
goto done;
if (ret == 0){
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
}
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,
if (clicon_rpc_get_pageable_list(h, "running", xpath, nsc, content,
depth, offset, limit, direction, sort, where,
&xret) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
@ -483,23 +499,12 @@ api_data_collection(clicon_handle h,
goto done;
if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath) < 0)
goto done;
#if 0
/* Check if not exists */
if (xlen == 0){
/* 4.3: If a retrieval request for a data resource represents an
instance that does not exist, then an error response containing
a "404 Not Found" status-line MUST be returned by the server.
The error-tag value "invalid-value" is used in this case. */
if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
goto done;
/* override invalid-value default 400 with 404 */
if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){
if (api_return_err(h, req, xe, pretty, media_out, 404) < 0)
goto done;
}
goto ok;
}
#endif
/* Note, the netconf GET pageable list can not distinguish between:
* - not-exists, ie there are no entries
* - no matching entries, eg there are entries, just not that match
* Here we take the latter approach to return an empty list and do not
* handle the non-exist case differently.
*/
for (i=0; i<xlen; i++){
xp = xvec[i];
ns = NULL;

View file

@ -408,6 +408,7 @@ example_statedata(clicon_handle h,
}
if ((xt = xml_new("config", NULL, CX_ELMNT)) == NULL)
goto done;
/* Note, does not care about xpath / list-pagination */
if (clixon_xml_parse_file(fp, YB_MODULE, yspec, &xt, NULL) < 0)
goto done;
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)

View file

@ -140,5 +140,8 @@ int netconf_hello_server(clicon_handle h, cbuf *cb, uint32_t session_id);
int netconf_hello_req(clicon_handle h, cbuf *cb);
int clixon_netconf_error_fn(const char *fn, const int line, cxobj *xerr, const char *fmt, const char *arg);
int clixon_netconf_internal_error(cxobj *xerr, char *msg, char *arg);
int netconf_parse_uint32(char *name, char *valstr, char *defaultstr, uint32_t defaultval, cbuf *cbret, uint32_t *value);
int netconf_parse_uint32_xml(char *name, char *valstr, char *defaultstr, uint32_t defaultval, cxobj **xerr, uint32_t *value);
#endif /* _CLIXON_NETCONF_LIB_H */

View file

@ -55,9 +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 *yli,
int clicon_rpc_get_pageable_list(clicon_handle h, char *datastore, char *xpath,
cvec *nsc, netconf_content content, int32_t depth,
char *limit, char *offset, char *direction, char *sort, char *where,
uint32_t offset, uint32_t limit,
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);

View file

@ -160,7 +160,7 @@ cxobj *xpath_first_localonly(cxobj *xcur, const char *xpformat, ...);
int xpath_vec(cxobj *xcur, cvec *nsc, const char *xpformat, cxobj ***vec, size_t *veclen, ...);
#endif
int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1);
int xpath2canonical(const char *xpath0, cvec *nsc0, yang_stmt *yspec, char **xpath1, cvec **nsc1, cbuf **cbreason);
int xpath_count(cxobj *xcur, cvec *nsc, const char *xpath, uint32_t *count);
#endif /* _CLIXON_XPATH_H */

View file

@ -1841,3 +1841,111 @@ clixon_netconf_internal_error(cxobj *xerr,
done:
return retval;
}
/*! Parse string into uint32 and return netconf bad-element msg on error
*
* @param[in] name Name of attribute (for error msg)
* @param[in] valstr Value string to be parsed
* @param[in] defaultstr If given, default string which is accepted and sets value to 0
* @param[in,out] cbret Output buffer for internal RPC message if invalid
* @param[out] value Value if valid
* @retval -1 Error
* @retval 0 Invalid, cbret set
* @retval 1 OK
* @code
* char *name = "a";
* char *valstr = "322";
* char *defaultstr = "unbounded";
* cbuf *cbret = cbuf_new();
* uint32_t value = 0;
*
* if ((ret = netconf_parse_uint32(name, valstr, defaultstr, cbret, &value) < 0)
* goto err;
* if (ret == 0)
* // cbret contains netconf errmsg
* else
* // value contains uin32
* @endcode
*/
int
netconf_parse_uint32(char *name,
char *valstr,
char *defaultstr,
uint32_t defaultvalue,
cbuf *cbret,
uint32_t *value)
{
int retval = -1;
int ret;
char *reason = NULL;
if (valstr == NULL){
clicon_err(OE_NETCONF, EINVAL, "valstr is NULL");
goto done;
}
if (defaultstr && strcmp(valstr, defaultstr) == 0)
*value = defaultvalue;
else {
if ((ret = parse_uint32(valstr, value, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint32");
goto done;
}
if (ret == 0){
if (netconf_bad_element(cbret, "application",
name, "Unrecognized value") < 0)
goto done;
goto fail;
}
}
retval = 1;
done:
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}
/*! Parse string into uint32 and return netconf bad-element msg on error xml variant
* @see netconf_parse_uint32_xml
*/
int
netconf_parse_uint32_xml(char *name,
char *valstr,
char *defaultstr,
uint32_t defaultvalue,
cxobj **xerr,
uint32_t *value)
{
int retval = -1;
int ret;
char *reason = NULL;
if (valstr == NULL){
clicon_err(OE_NETCONF, EINVAL, "valstr is NULL");
goto done;
}
if (defaultstr && strcmp(valstr, defaultstr) == 0)
*value = defaultvalue;
else {
if ((ret = parse_uint32(valstr, value, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_uint32");
goto done;
}
if (ret == 0){
if (netconf_bad_element_xml(xerr, "application",
name, "Unrecognized value") < 0)
goto done;
goto fail;
}
}
retval = 1;
done:
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}

View file

@ -435,7 +435,7 @@ clicon_rpc_get_config(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL;
cxobj *xd;
cxobj *xd = NULL;
uint32_t session_id;
int ret;
yang_stmt *yspec;
@ -488,7 +488,7 @@ clicon_rpc_get_config(clicon_handle h,
}
}
}
if (xt){
if (xt && xd){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;
@ -799,7 +799,7 @@ clicon_rpc_get(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL;
cxobj *xd;
cxobj *xd = NULL;
char *username;
uint32_t session_id;
int ret;
@ -860,7 +860,7 @@ clicon_rpc_get(clicon_handle h,
}
}
}
if (xt){
if (xt && xd){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;
@ -882,14 +882,12 @@ 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 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
* @param[in] depth Nr of XML levels to get, -1 is all, 0 is none
* @param[in] limit Collection/clixon extension
* @param[in] offset Collection/clixon extension
* @param[in] offset uint32, 0 means none
* @param[in] limit uint32, 0 means unbounded
* @param[in] direction Collection/clixon extension
* @param[in] sort Collection/clixon extension
* @param[in] where Collection/clixon extension
@ -905,12 +903,11 @@ int
clicon_rpc_get_pageable_list(clicon_handle h,
char *datastore,
char *xpath,
yang_stmt *yli,
cvec *nsc, /* namespace context for xpath */
netconf_content content,
int32_t depth,
char *limit,
char *offset,
uint32_t offset,
uint32_t limit,
char *direction,
char *sort,
char *where,
@ -921,8 +918,7 @@ clicon_rpc_get_pageable_list(clicon_handle h,
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr = NULL;
cxobj *xr; /* return msg */
cxobj *xd; /* return data */
cxobj *xd = NULL; /* return data */
char *username;
uint32_t session_id;
int ret;
@ -963,10 +959,10 @@ clicon_rpc_get_pageable_list(clicon_handle h,
}
/* Explicit use of list-pagination */
cprintf(cb, "<cp:list-pagination>true</cp:list-pagination>");
if (limit)
cprintf(cb, "<cp:limit>%s</cp:limit>", limit);
if (offset)
cprintf(cb, "<cp:offset>%s</cp:offset>", offset);
if (offset != 0)
cprintf(cb, "<cp:offset>%u</cp:offset>", offset);
if (limit != 0)
cprintf(cb, "<cp:limit>%u</cp:limit>", limit);
if (direction)
cprintf(cb, "<cp:direction>%s</cp:direction>", direction);
if (sort)
@ -980,8 +976,8 @@ clicon_rpc_get_pageable_list(clicon_handle h,
if (clicon_rpc_msg(h, msg, &xret) < 0)
goto done;
/* 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 */
if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
xd = xml_parent(xd); /* point to rpc-reply */
else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){
if ((xd = xml_new(NETCONF_OUTPUT_DATA, NULL, CX_ELMNT)) == NULL)
goto done;
@ -1001,7 +997,7 @@ clicon_rpc_get_pageable_list(clicon_handle h,
}
}
}
if (xt){
if (xt && xd){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;

View file

@ -930,38 +930,65 @@ xpath_vec_bool(cxobj *xcur,
return retval;
}
/*!
/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes
*
* @param[in] xs Parsed xpath - xpath_tree
* @param[in] yspec Yang spec containing all modules, associated with namespaces
* @param[in] nsc0 Input namespace context
* @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free
* @param[out] reason Error reason if result is 0 - failed
* @retval 1 OK with nsc1 containing the transformed nsc
* @retval 0 XPath failure with reason set to why
* @retval -1 Fatal Error
*/
static int
traverse_canonical(xpath_tree *xs,
yang_stmt *yspec,
cvec *nsc0,
cvec *nsc1)
cvec *nsc1,
cbuf **reason)
{
int retval = -1;
char *prefix0;
char *prefix1;
char *namespace;
yang_stmt *ymod;
cbuf *cb = NULL;
int ret;
switch (xs->xs_type){
case XP_NODE: /* s0 is namespace prefix, s1 is name */
prefix0 = xs->xs_s0;
if ((namespace = xml_nsctx_get(nsc0, prefix0)) == NULL){
clicon_err(OE_XML, ENOENT, "No namespace found for prefix: %s",
prefix0);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "No namespace found for prefix: %s", prefix0);
if (reason)
*reason = cb;
goto failed;
}
if ((ymod = yang_find_module_by_namespace(yspec, namespace)) == NULL){
clicon_err(OE_XML, ENOENT, "No modules found for namespace: %s",
namespace);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "No modules found for namespace: %s", namespace);
if (reason)
*reason = cb;
goto failed;
}
if ((prefix1 = yang_find_myprefix(ymod)) == NULL){
clicon_err(OE_XML, ENOENT, "No prefix found in module: %s",
yang_argument_get(ymod));
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "No prefix found in module: %s", yang_argument_get(ymod));
if (reason)
*reason = cb;
goto failed;
}
if (xml_nsctx_get(nsc1, prefix1) == NULL)
if (xml_nsctx_add(nsc1, prefix1, namespace) < 0)
goto done;
@ -977,25 +1004,36 @@ traverse_canonical(xpath_tree *xs,
default:
break;
}
if (xs->xs_c0)
if (traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1) < 0)
if (xs->xs_c0){
if ((ret = traverse_canonical(xs->xs_c0, yspec, nsc0, nsc1, reason)) < 0)
goto done;
if (xs->xs_c1)
if (traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1) < 0)
if (ret == 0)
goto failed;
}
if (xs->xs_c1){
if ((ret = traverse_canonical(xs->xs_c1, yspec, nsc0, nsc1, reason)) < 0)
goto done;
retval = 0;
if (ret == 0)
goto failed;
}
retval = 1;
done:
return retval;
failed:
retval = 0;
goto done;
}
/*! Translate an xpath/nsc pair to a "canonical" form using yang prefixes
*
* @param[in] xpath0 Input xpath
* @param[in] nsc0 Input namespace context
* @param[in] yspec Yang spec containing all modules, associated with namespaces
* @param[out] xpath1 Output xpath. Free after use with free
* @param[out] nsc1 Output namespace context. Free after use with xml_nsctx_free
* @retval 0 OK, xpath1 and nsc1 allocated
* @retval -1 Error
* @retval 1 OK, xpath1 and nsc1 allocated
* @retval 0 XPath failure with reason set to why
* @retval -1 Fatal Error
* Example:
* Module A has prefix a and namespace urn:example:a and symbols x
* Module B with prefix b and namespace urn:example:b and symbols y
@ -1008,7 +1046,8 @@ traverse_canonical(xpath_tree *xs,
* @code
* char *xpath1 = NULL;
* cvec *nsc1 = NULL;
* if (xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1) < 0)
* cbuf *reason = NULL;
* if ((ret = xpath2canonical(xpath0, nsc0, yspec, &xpath1, &nsc1, &reason)) < 0)
* err;
* ...
* if (xpath1) free(xpath1);
@ -1020,12 +1059,14 @@ xpath2canonical(const char *xpath0,
cvec *nsc0,
yang_stmt *yspec,
char **xpath1,
cvec **nsc1p)
cvec **nsc1p,
cbuf **cbreason)
{
int retval = -1;
xpath_tree *xpt = NULL;
cvec *nsc1 = NULL;
cbuf *xcb = NULL;
int ret;
/* Parse input xpath into an xpath-tree */
if (xpath_parse(xpath0, &xpt) < 0)
@ -1036,8 +1077,10 @@ xpath2canonical(const char *xpath0,
/* Traverse tree to find prefixes, transform them to canonical form and
* create a canonical network namespace
*/
if (traverse_canonical(xpt, yspec, nsc0, nsc1) < 0)
if ((ret = traverse_canonical(xpt, yspec, nsc0, nsc1, cbreason)) < 0)
goto done;
if (ret == 0)
goto failed;
/* Print tree with new prefixes */
if ((xcb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@ -1055,7 +1098,7 @@ xpath2canonical(const char *xpath0,
*nsc1p = nsc1;
nsc1 = NULL;
}
retval = 0;
retval = 1;
done:
if (xcb)
cbuf_free(xcb);
@ -1064,6 +1107,9 @@ xpath2canonical(const char *xpath0,
if (xpt)
xpath_tree_free(xpt);
return retval;
failed:
retval = 0;
goto done;
}
/*! Return a count(xpath)

View file

@ -3,6 +3,7 @@
# Backlog items:
# 1. "remaining" annotation RFC 7952
# 2. pattern '.*[\n].*' { modifier invert-match;
# XXX: handling of empty return ?
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -22,6 +23,10 @@ RESTCONFIG=$(restconf_config none false)
# Validate internal state xml
: ${validatexml:=false}
# Number of list/leaf-list entries in file
#: ${perfnr:=20000}
: ${perfnr:=200}
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
@ -206,59 +211,27 @@ cat<<EOF > $fstate
</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
# Check this later with committed data
new "generate state with $perfnr list entries"
echo "<audit-logs xmlns=\"http://example.com/ns/example-social\">" >> $fstate
for (( i=0; i<$perfnr; i++ )); do
echo " <audit-log>" >> $fstate
mon=$(( ( RANDOM % 10 ) ))
day=$(( ( RANDOM % 10 ) ))
hour=$(( ( RANDOM % 10 ) ))
echo " <timestamp>2020-0$mon-0$dayT0$hour:48:11Z</timestamp>" >> $fstate
echo " <member-id>bob</member-id>" >> $fstate
ip1=$(( ( RANDOM % 255 ) ))
ip2=$(( ( RANDOM % 255 ) ))
echo " <source-ip>192.168.$ip1.$ip2</source-ip>" >> $fstate
echo " <request>POST</request>" >> $fstate
echo " <outcome>true</outcome>" >> $fstate
echo " </audit-log>" >> $fstate
done
echo -n "</audit-logs>" >> $fstate # No CR
# Run limit-only test with netconf, restconf+xml and restconf+json
# Args:
# 1. offset
@ -278,6 +251,7 @@ function testlimit()
jsonlist="" # for restconf json
jsonmeta=""
let i=0
for li in $list; do
if [ $i = 0 ]; then
if [ $limit == 0 ]; then
@ -315,19 +289,40 @@ function testlimit()
jsonstr="${jsonstr}&offset=$offset"
fi
fi
new "limit=$limit NETCONF get-config"
# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source><filter type=\"xpath\" select=\"/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination>$limitxmlstr$offsetxmlstr</get-config></rpc>]]>]]>" "<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><favorites>$xmllist</favorites></member></members></data></rpc-reply>]]>]]>$"
new "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>$limitxmlstr$offsetxmlstr</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>$xmllist</favorites></member></members></data></rpc-reply>]]>]]>$"
if [ -z "$list" ]; then
reply="<rpc-reply $DEFAULTNS><data/></rpc-reply>]]>]]>$"
else
reply="<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>$xmllist</favorites></member></members></data></rpc-reply>]]>]]>$"
fi
new "limit=$limit offset=$offset NETCONF get-config"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get-config><source><running/></source><filter type=\"xpath\" select=\"/es:members/es:member[es:member-id='alice']/es:favorites/es:uint8-numbers\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination>$limitxmlstr$offsetxmlstr</get-config></rpc>]]>]]>" "$reply"
new "limit=$limit Parameter RESTCONF xml"
expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-collection+xml" $RCPROTO://localhost/restconf/data/example-social:members/member=alice/favorites/uint8-numbers${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "<yang-collection xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination\">$xmllist2</yang-collection>"
if [ -z "$list" ]; then
reply="<rpc-reply $DEFAULTNS><data/></rpc-reply>]]>]]>$"
else
reply="<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>$xmllist</favorites></member></members></data></rpc-reply>]]>]]>$"
fi
new "limit=$limit offset=$offset 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>$limitxmlstr$offsetxmlstr</get></rpc>]]>]]>" "$reply"
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${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "{\"yang-collection\":{\"example-social:uint8-numbers\":\[$jsonlist\]$jsonmeta}"
if [ -z "$list" ]; then
reply="<yang-collection xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination\"/>"
else
reply="<yang-collection xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf-list-pagination\">$xmllist2</yang-collection>"
fi
new "limit=$limit offset=$offset 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${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+xml" "$reply"
} # testrunf
if [ -z "$list" ]; then
reply="{\"yang-collection\":{}}"
else
reply="{\"yang-collection\":{\"example-social:uint8-numbers\":\[$jsonlist\]$jsonmeta}"
fi
new "limit=$limit offset=$offset 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${jsonstr})" 0 "HTTP/$HVER 200" "Content-Type: application/yang-collection+json" "$reply"
} # testlimit
new "test params: -f $cfg -s startup -- -sS $fstate"
@ -357,6 +352,7 @@ fi
new "wait restconf"
wait_restconf
new "A.3.1.1. limit=1"
testlimit 0 1 5 "17"
@ -381,12 +377,12 @@ testlimit 2 0 0 "11 7 5 3"
new "A.3.2.3. offset=5"
testlimit 5 0 0 "3"
#new "A.3.2.4. offset=6"
#testlimit 6 0 0 ""
new "A.3.2.4. offset=6"
testlimit 6 0 0 ""
# This is incomplete wrt the draft
new "A.3.7. limit=2 offset=2"
testlimit 2 2 2 "11 7"
testlimitn 2 2 2 "11 7"
# CLI
# XXX This relies on a very specific clispec command: need a more generic test
@ -415,6 +411,7 @@ fi
unset RESTCONFIG
unset validatexml
unset perfnr
rm -rf $dir

View file

@ -57,10 +57,10 @@ new "xpath canonical form descendants"
expectpart "$($clixon_util_xpath -c -y $ydir -p "//x[.='42']" -n null:urn:example:a -n j:urn:example:b)" 0 "//a:x\[.='42'\]" '0 : a = "urn:example:a"'
new "xpath canonical form (no default should fail)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2> /dev/null)" 255
expectpart "$($clixon_util_xpath -c -y $ydir -p /x/j:y -n i:urn:example:a -n j:urn:example:b 2>&1)" 0 "/x/j:y: No namespace found for prefix"
new "xpath canonical form (wrong namespace should fail)"
expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>/dev/null)" 255
expectpart "$($clixon_util_xpath -c -y $ydir -p /i:x/j:y -n i:urn:example:c -n j:urn:example:b 2>&1)" 0 "/i:x/j:y: No modules found for namespace"
rm -rf $dir

View file

@ -281,8 +281,14 @@ main(int argc,
if (canonical){
char *xpath1 = NULL;
cvec *nsc1 = NULL;
if (xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1) < 0)
cbuf *cbreason = NULL;
if ((ret = xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1, &cbreason)) < 0)
goto done;
if (ret == 0){
fprintf(stderr, "Error with %s: %s", xpath, cbuf_get(cbreason));
goto ok;
}
xpath = xpath1;
if (xpath)
fprintf(stdout, "%s\n", xpath);

View file

@ -121,7 +121,7 @@ module clixon-netconf-list-pagination {
"The first list item to return.
the 'offset' parameter is either an integer greater than
or equal to 1, or the string 'unbounded'. The string
'unbounded' is the default value.";
'none' is the default value.";
}
leaf direction {
type enumeration {