Removed remaining and replaced pagination-mode with locked parameter

Dispatcher: Added dispatcher_free(), fixed mem-leaks and malloc return
checks
This commit is contained in:
Olof hagsand 2021-10-09 15:50:13 +02:00
parent ce06f25be7
commit edbbb43e1f
14 changed files with 204 additions and 161 deletions

View file

@ -43,6 +43,7 @@ Thanks netgate for providing the dispatcher code!
* Register callback with: `clixon_pagination_cb_register()`
* Use accessor functions `pagination_offset()`, `pagination_limit()`, etc
* Reverted state data callback API to pre-5.3 (see C/CLI API changes below)
* See https://clixon-docs.readthedocs.io/en/latest/pagination.html
### API changes on existing protocol/config features

View file

@ -467,11 +467,8 @@ get_list_pagination(clicon_handle h,
char *xpath2; /* With optional pagination predicate */
int ret;
uint32_t iddb; /* DBs lock, if any */
pagination_mode_t pagmode;
int locked;
cxobj *x1 = NULL;
cxobj *xcache = NULL;
uint32_t total = 0;
uint32_t remaining = 0;
/* Check if list/leaf-list */
if (yang_path_arg(yspec, xpath, &ylist) < 0)
@ -588,6 +585,7 @@ get_list_pagination(clicon_handle h,
}/* switch content */
if (list_config){
#ifdef LIST_PAGINATION_REMAINING
/* Get total/remaining
* XXX: Works only for cache
*/
@ -597,15 +595,16 @@ get_list_pagination(clicon_handle h,
if (total >= (offset + limit))
remaining = total - (offset + limit);
}
#endif
}
else {/* Check if running locked (by this session) */
if ((iddb = xmldb_islocked(h, "running")) != 0 &&
iddb == ce->ce_id)
pagmode = PAGINATION_LOCK;
locked = 1;
else
pagmode = PAGINATION_STATELESS;
if ((ret = clixon_pagination_cb_call(h, xpath, pagmode,
offset, limit, &remaining,
locked = 0;
if ((ret = clixon_pagination_cb_call(h, xpath, locked,
offset, limit,
xret)) < 0)
goto done;
if (ret == 0)

View file

@ -131,7 +131,7 @@ backend_terminate(clicon_handle h)
clixon_process_delete_all(h);
xpath_optimize_exit();
clixon_pagination_free(h);
if (pidfile)
unlink(pidfile);
if (sockfamily==AF_UNIX && lstat(sockpath, &st) == 0)

View file

@ -452,23 +452,24 @@ clixon_plugin_lockdb_all(clicon_handle h,
* @param[in] xpath Registered XPath using canonical prefixes
*/
int
clixon_pagination_cb_call(clicon_handle h,
char *xpath,
pagination_mode_t pagmode,
uint32_t offset,
uint32_t limit,
uint32_t *remaining,
cxobj *xstate)
clixon_pagination_cb_call(clicon_handle h,
char *xpath,
int locked,
uint32_t offset,
uint32_t limit,
cxobj *xstate)
{
int retval = -1;
pagination_data_t pd = {pagmode, offset, limit, 0, xstate};
pagination_data_t pd;
dispatcher_entry_t *htable = NULL;
pd.pd_offset = offset;
pd.pd_limit = limit;
pd.pd_locked = locked;
pd.pd_xstate = xstate;
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
if (htable && dispatcher_call_handlers(htable, h, xpath, &pd) < 0)
goto done;
if (remaining)
*remaining = pd.pd_remaining;
retval = 1;
done:
return retval;
@ -503,6 +504,21 @@ clixon_pagination_cb_register(clicon_handle h,
return retval;
}
/*! Free pagination callback structure
*
* @param[in] h Clixon handle
*/
int
clixon_pagination_free(clicon_handle h)
{
dispatcher_entry_t *htable = NULL;
clicon_ptr_get(h, "pagination-entries", (void**)&htable);
if (htable)
dispatcher_free(htable);
return 0;
}
/*! Create and initialize a validate/commit transaction
* @retval td New alloced transaction,
* @retval NULL Error

View file

@ -68,17 +68,22 @@ typedef struct {
} transaction_data_t;
/*! Pagination userdata
* @param[in] pagmode List pagination mode
* Pagination can use a lock/transaction mechanism
* If locking is not used, the plugin cannot expect more pagination calls, and no state or
* caching should be used
* If locking is used, the pagination is part of a session transaction and the plugin may cache
* state (such as a cache) and can expect more pagination calls until the running db-lock is
* released, (see ca_lockdb)
* The transaction is the regular lock/unlock db of running-db of a specific session.
* @param[in] locked "running" datastore is locked by this caller
* @param[in] offset Offset, for list pagination
* @param[in] limit Limit, for list pagination
* @param[out] remaining Remaining elements (if limit is non-zero)
* see also pagination_data in clixon_plugin.h
*/
typedef struct {
pagination_mode_t pd_pagmode; /* Pagination mode, stateless or locked */
uint32_t pd_offset; /* Start of pagination interval */
uint32_t pd_limit; /* Number of elemenents (limit) */
uint32_t pd_remaining; /* If limit, then remaining nr of elements */
int pd_locked; /* Running datastore is locked by this caller */
cxobj *pd_xstate; /* Returned xml state tree */
} pagination_data_t;
@ -96,9 +101,10 @@ int clixon_plugin_statedata_all(clicon_handle h, yang_stmt *yspec, cvec *nsc, ch
int clixon_plugin_lockdb_all(clicon_handle h, char *db, int lock, int id);
int clixon_pagination_cb_register(clicon_handle h, handler_function fn, char *path, void *arg);
int clixon_pagination_cb_call(clicon_handle h, char *xpath, pagination_mode_t pagmode,
uint32_t offset, uint32_t limit, uint32_t *remaining,
int clixon_pagination_cb_call(clicon_handle h, char *xpath, int locked,
uint32_t offset, uint32_t limit,
cxobj *xstate);
int clixon_pagination_free(clicon_handle h);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);

View file

@ -285,16 +285,6 @@ transaction_log(clicon_handle h,
return 0;
}
/*! Get pagination data: mode parameter
*
* @param[in] pd Pagination userdata
* @retval mode Pagination mode, stateless or locked
*/
pagination_mode_t
pagination_pagmode(pagination_data pd)
{
return ((pagination_data_t *)pd)->pd_pagmode;
}
/*! Get pagination data: offset parameter
*
@ -318,16 +308,22 @@ pagination_limit(pagination_data pd)
return ((pagination_data_t *)pd)->pd_limit;
}
/*! Set pagination data: remaining nr of elements
/*! Get pagination data: locked parameter
*
* @param[in] pd Pagination userdata
* @param[in] remaining If limit, then remaining nr of elements
* Pagination can use a lock/transaction mechanism
* If locking is not used, the plugin cannot expect more pagination calls, and no state or
* caching should be used
* If locking is used, the pagination is part of a session transaction and the plugin may cache
* state (such as a cache) and can expect more pagination calls until the running db-lock is
* released, (see ca_lockdb)
* The transaction is the regular lock/unlock db of running-db of a specific session.
* @param[in] pd Pagination userdata
* @retval locked 0: unlocked/stateless 1: locked by this caller
*/
int
pagination_remaining_set(pagination_data pd,
uint32_t remaining)
pagination_locked(pagination_data pd)
{
return ((pagination_data_t *)pd)->pd_remaining = remaining;
return ((pagination_data_t *)pd)->pd_locked;
}
/*! Get pagination data: Returned xml state tree

View file

@ -67,10 +67,9 @@ int transaction_log(clicon_handle h, transaction_data th, int level, const char
/* Pagination callbacks
* @see pagination_data_t internal structure
*/
pagination_mode_t pagination_pagmode(pagination_data pd);
int pagination_locked(pagination_data pd);
uint32_t pagination_offset(pagination_data pd);
uint32_t pagination_limit(pagination_data pd);
int pagination_remaining_set(pagination_data pd, uint32_t remaining);
cxobj *pagination_xstate(pagination_data pd);
#endif /* _CLIXON_BACKEND_TRANSACTION_H_ */

View file

@ -95,7 +95,7 @@ static char *_state_file = NULL;
static char *_state_xpath = NULL;
/*! Read state file init on startup instead of on request
* Primarily for testing
* Primarily for testing: -i
* Start backend with -- -siS <file>
*/
static int _state_file_cached = 0;
@ -551,10 +551,9 @@ example_pagination(void *h0,
{
int retval = -1;
clicon_handle h = (clicon_handle)h0;
pagination_mode_t pagmode;
int locked;
uint32_t offset;
uint32_t limit;
uint32_t remaining;
cxobj *xstate;
cxobj **xvec = NULL;
size_t xlen = 0;
@ -566,13 +565,13 @@ example_pagination(void *h0,
uint32_t lower;
uint32_t upper;
int ret;
cvec *nsc;
cvec *nsc = NULL;
/* If -S is set, then read state data from file */
if (!_state || !_state_file)
goto ok;
pagmode = pagination_pagmode(pd);
locked = pagination_locked(pd);
offset = pagination_offset(pd);
limit = pagination_limit(pd);
xstate = pagination_xstate(pd);
@ -599,25 +598,12 @@ example_pagination(void *h0,
xt = _state_xml_cache;
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
goto done;
lower = 0;
upper = xlen;
switch (pagmode){
case PAGINATION_NONE:
lower = 0;
lower = offset;
if (limit == 0)
upper = xlen;
break;
case PAGINATION_STATELESS:
case PAGINATION_LOCK:
lower = offset;
if (limit == 0)
else{
if ((upper = offset+limit) > xlen)
upper = xlen;
else{
if ((upper = offset+limit)>xlen)
upper = xlen;
remaining = xlen - upper;
pagination_remaining_set(pd, remaining);
}
break;
}
/* Mark elements to copy:
* For every node found in x0, mark the tree as changed
@ -636,9 +622,10 @@ example_pagination(void *h0,
/* Unmark returned state tree */
if (xml_apply(xstate, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)(XML_FLAG_MARK|XML_FLAG_CHANGE)) < 0)
goto done;
if (_state_file_cached)
if (_state_file_cached){
xt = NULL; /* ensure cache is not cleared */
if (pagmode == PAGINATION_LOCK)
}
if (locked)
_state_file_transaction++;
ok:
retval = 0;
@ -649,6 +636,8 @@ example_pagination(void *h0,
xml_free(xt);
if (xvec)
free(xvec);
if (nsc)
cvec_free(nsc);
return retval;
}
@ -1173,7 +1162,7 @@ example_daemon(clicon_handle h)
yang_stmt *yspec;
/* Read state file (or should this be in init/start?) */
if (_state && _state_file && _state_file_cached && 0){
if (_state && _state_file && _state_file_cached){
yspec = clicon_dbspec_yang(h);
if ((fp = fopen(_state_file, "r")) == NULL){
clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
@ -1192,6 +1181,10 @@ example_daemon(clicon_handle h)
int
example_exit(clicon_handle h)
{
if (_state_xml_cache){
xml_free(_state_xml_cache);
_state_xml_cache = NULL;
}
return 0;
}

View file

@ -46,6 +46,7 @@ struct _dispatcher_entry {
/*
* peer_head points at leftmost peer at this level
* if NULL, then this is the leftmost and first on the list
* XXX: it seems it points to itself if it is first on the list?
*/
dispatcher_entry_t *peer_head;
@ -71,5 +72,6 @@ struct _dispatcher_entry {
*/
int dispatcher_register_handler(dispatcher_entry_t **root, dispatcher_definition *x);
int dispatcher_call_handlers(dispatcher_entry_t *root, void *handle, char *path, void *user_args);
int dispatcher_free(dispatcher_entry_t *root);
#endif /* DISPATCH_DISPATCHER_H */

View file

@ -196,26 +196,6 @@ typedef int (plgauth_t)(clicon_handle h, void *req, clixon_auth_type_t auth_type
*/
typedef int (plgreset_t)(clicon_handle h, const char *db);
/*! List pagination status in the plugin state data callback
*
* List pagination is either enabled or not.
* If pagination is enabled, the xpath addresses a list/ leaf-list and the plugin should return
* entries according to the values of offset and limit.
* Pagination can use a lock/transaction mechanism
* If locking is not used, the plugin cannot expect more pagination calls, and no state or caching
* should be used
* If locking is used, the pagination is part of a session transaction and the plugin may cache
* state (such as a cache) and can expect more pagination calls until the running db-lock is released,
* (see ca_lockdb)
* The transaction is the regular lock/unlock db of running-db of a specific session.
*/
enum pagination_mode{
PAGINATION_NONE, /* No list pagination: limit/offset are no-ops */
PAGINATION_STATELESS, /* Stateless list pagination, dont expect more pagination calls */
PAGINATION_LOCK /* Transactional list pagination, can expect more pagination until lock release */
};
typedef enum pagination_mode pagination_mode_t;
/* Plugin statedata
* @param[in] h Clicon handle
* @param[in] xpath Part of state requested

View file

@ -71,33 +71,41 @@
#define PATH_CHUNKS 32
/**
* spilt a path into elements
/*! Spilt a path into elements
*
* given an api-path, break it up into chunks separated by '/'
* characters. it is expected that api-paths are URI encoded, so no need
* to deal with unescaped special characters like ', ", and /
*
* @param path [input] path string
* @param plist [output] pointer to split path array
* @param plist_len [output] pointer to storage space for path array length
* @param[in] path Path string
* @param[in] plist Pointer to split path array
* @param[out] plist_len Pointer to storage space for path array length
* @retval 0 OK
* @retval -1 Error
* XXX consider using clixon_strsep
*/
static void split_path(char *path, char ***plist, size_t *plist_len)
static int
split_path(char *path,
char ***plist,
size_t *plist_len)
{
int retval = -1;
size_t allocated = PATH_CHUNKS;
/* don't modify the original copy */
char *work = strdup(path);
char **list = malloc(allocated * sizeof(char *));
memset(list, 0, allocated * sizeof(char *));
char *work; /* don't modify the original copy */
char **list;
size_t len = 0;
char *ptr;
char *new_element;
char *ptr = work;
if ((work = strdup(path)) == NULL)
goto done;
if ((list = malloc(allocated * sizeof(char *))) == NULL)
goto done;
memset(list, 0, allocated * sizeof(char *));
ptr = work;
if (*ptr == '/') {
char *new_element = strdup("/");
if ((new_element = strdup("/")) == NULL)
goto done;
list[len++] = new_element;
ptr++;
}
@ -108,10 +116,12 @@ static void split_path(char *path, char ***plist, size_t *plist_len)
if (len > allocated) {
/* we've run out of space, allocate a bigger list */
allocated += PATH_CHUNKS;
list = realloc(list, allocated * sizeof(char *));
if ((list = realloc(list, allocated * sizeof(char *))) == NULL)
goto done;
}
char *new_element = strdup(ptr);
if ((new_element = strdup(ptr)) == NULL)
goto done;
list[len++] = new_element;
ptr = strtok(NULL, "/");
@ -121,16 +131,19 @@ static void split_path(char *path, char ***plist, size_t *plist_len)
*plist_len = len;
free(work);
retval = 0;
done:
return retval;
}
/**
* free a split path structure
/*! Free a split path structure
*
* @param list [input] pointer to split path array
* @param len [input] length of split path array
* @param[in] list pointer to split path array
* @param[in] len length of split path array
*/
static void split_path_free(char **list, size_t len)
static void
split_path_free(char **list,
size_t len)
{
size_t i;
@ -140,23 +153,25 @@ static void split_path_free(char **list, size_t len)
free(list);
}
/**
* find a peer of this node by name
/*! Find a peer of this node by name
* search through the list pointed at by peer
*
* @param node [input] pointer to a node in the peer list
* @param node_name [input] name of node we're looking for
* @return pointer to found node or NULL
* @param[in] node Pointer to a node in the peer list
* @param[in] node_name Name of node we're looking for
* @retval pointer Pointer to found node or NULL
* @retval NULL
*/
static dispatcher_entry_t *find_peer(dispatcher_entry_t *node, char *node_name)
static dispatcher_entry_t *
find_peer(dispatcher_entry_t *node, char *node_name)
{
dispatcher_entry_t *i;
if ((node == NULL) || (node_name == NULL)) {
/* protect against idiot users */
return NULL;
}
dispatcher_entry_t *i = node->peer_head;
i = node->peer_head;
while (i != NULL) {
if (strcmp(node_name, i->node_name) == 0) {
@ -168,19 +183,24 @@ static dispatcher_entry_t *find_peer(dispatcher_entry_t *node, char *node_name)
return i;
}
/**
* add a node as the last node in peer list
/*! Add a node as the last node in peer list
*
* @param node [input] pointer to an element of the peer list
* @param name [input] name of new node
* @return pointer to added/existing node
* @param[in] node Pointer to an element of the peer list
* @param[in] name Name of new node
* @retval pointer Pointer to added/existing node
* @retval NULL Error
*/
static dispatcher_entry_t *add_peer_node(dispatcher_entry_t *node, char *name)
static dispatcher_entry_t *
add_peer_node(dispatcher_entry_t *node,
char *name)
{
dispatcher_entry_t *new_node = malloc(sizeof(dispatcher_entry_t));
memset(new_node, 0, sizeof(dispatcher_entry_t));
dispatcher_entry_t *new_node = NULL;
dispatcher_entry_t *eptr;
if ((new_node = malloc(sizeof(dispatcher_entry_t))) == NULL)
return NULL;
memset(new_node, 0, sizeof(dispatcher_entry_t));
if (node == NULL) {
/* this is a new node */
@ -194,7 +214,7 @@ static dispatcher_entry_t *add_peer_node(dispatcher_entry_t *node, char *name)
/* possibly adding to the list */
/* search for existing, or get tail end of list */
dispatcher_entry_t *eptr = node->peer_head;
eptr = node->peer_head;
while (eptr->peer != NULL) {
if (strcmp(eptr->node_name, name) == 0) {
return eptr;
@ -218,21 +238,26 @@ static dispatcher_entry_t *add_peer_node(dispatcher_entry_t *node, char *name)
}
}
/**
* add a node as a child of this node
/*! Add a node as a child of this node
*
* this is different from add_peer_node() in that it returns a
* pointer to the head_peer of the children list where the node was
* added.
*
* @param node [input] pointer to parent node of children list
* @param name [input] name of child node
* @return pointer to head of children list
* @param[in] node Pointer to parent node of children list
* @param[in] name Name of child node
* @retval pointer Pointer to head of children list
* @retval NULL Error
*/
static dispatcher_entry_t *add_child_node(dispatcher_entry_t *node, char *name)
static dispatcher_entry_t *
add_child_node(dispatcher_entry_t *node,
char *name)
{
dispatcher_entry_t *child_ptr = add_peer_node(node->children, name);
dispatcher_entry_t *child_ptr;
if ((child_ptr = add_peer_node(node->children, name)) == NULL)
return NULL;
node->children = child_ptr->peer_head;
return child_ptr;
@ -242,18 +267,21 @@ static dispatcher_entry_t *add_child_node(dispatcher_entry_t *node, char *name)
*
* @param root
* @param path
* @return
* @retval
* @retval NULL Error
*/
static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
static dispatcher_entry_t *
get_entry(dispatcher_entry_t *root,
char *path)
{
char **split_path_list = NULL;
size_t split_path_len = 0;
char **split_path_list = NULL;
size_t split_path_len = 0;
dispatcher_entry_t *ptr = root;
dispatcher_entry_t *best = root;
/* cut the path up into individual elements */
split_path(path, &split_path_list, &split_path_len);
if (split_path(path, &split_path_list, &split_path_len) < 0)
return NULL;
/* some elements may have keys defined, strip them off */
for (int i = 0; i < split_path_len; i++) {
@ -268,9 +296,8 @@ static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
for (int i = 0; i < split_path_len; i++) {
char *query = split_path_list[i];
ptr = find_peer(ptr, query);
if (ptr == NULL) {
if ((ptr = find_peer(ptr, query)) == NULL) {
split_path_free(split_path_list, split_path_len);
/* we ran out of matches, use last found handler */
return best;
}
@ -283,6 +310,9 @@ static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
ptr = ptr->children;
}
/* clean up */
split_path_free(split_path_list, split_path_len);
return best;
}
@ -292,7 +322,7 @@ static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
*
* @param entry
* @param path
* @return
* @retval
*/
static int
call_handler_helper(dispatcher_entry_t *entry,
@ -330,12 +360,13 @@ int
dispatcher_register_handler(dispatcher_entry_t **root,
dispatcher_definition *x)
{
char **split_path_list = NULL;
size_t split_path_len = 0;
char **split_path_list = NULL;
size_t split_path_len = 0;
dispatcher_entry_t *ptr;
if (*x->dd_path != '/') {
errno = EINVAL;
fprintf(stderr, "%s: part '%s' must start at root\n", __func__, x->dd_path);
// fprintf(stderr, "%s: part '%s' must start at root\n", __func__, x->dd_path);
return -1;
}
@ -343,20 +374,23 @@ dispatcher_register_handler(dispatcher_entry_t **root,
* get the path from the dispatcher_definition, break it
* up to create the elements of the dispatcher table
*/
split_path(x->dd_path, &split_path_list, &split_path_len);
if (split_path(x->dd_path, &split_path_list, &split_path_len) < 0)
return -1;
/*
* the first element is always a peer to the top level
*/
dispatcher_entry_t *ptr = *root;
ptr = *root;
ptr = add_peer_node(ptr, split_path_list[0]);
if ((ptr = add_peer_node(ptr, split_path_list[0])) == NULL)
return -1;
if (*root == NULL) {
*root = ptr;
}
for (size_t i = 1; i < split_path_len; i++) {
ptr = add_child_node(ptr, split_path_list[i]);
if ((ptr = add_child_node(ptr, split_path_list[i])) == NULL)
return -1;
}
/* when we get here, ptr points at last entry added */
@ -366,8 +400,8 @@ dispatcher_register_handler(dispatcher_entry_t **root,
* you could make this an error optionally
*/
if (ptr->handler != NULL) {
printf("%s: warning: replacing existing handler: (%s) %p -> %p\n", __func__,
ptr->node_name, ptr->handler, x->dd_handler);
// fprintf(stderr, "%s: warning: replacing existing handler: (%s) %p -> %p\n", __func__,
// ptr->node_name, ptr->handler, x->dd_handler);
}
ptr->handler = x->dd_handler;
}
@ -409,3 +443,20 @@ dispatcher_call_handlers(dispatcher_entry_t *root,
}
return ret;
}
/*! Free a dispatcher tree
*/
int
dispatcher_free(dispatcher_entry_t *root)
{
if (root == NULL)
return 0;
if (root->children)
dispatcher_free(root->children);
if (root->peer)
dispatcher_free(root->peer);
if (root->node_name)
free(root->node_name);
free(root);
return 0;
}

View file

@ -77,6 +77,7 @@
* @param[out] nvec Number of entries in returned vector
* @retval vec Vector of strings. NULL terminated. Free after use
* @retval NULL Error *
* @see clicon_strsplit
*/
char **
clicon_strsep(char *string,

View file

@ -140,13 +140,12 @@ if [ $BE -ne 0 ]; then
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -- -sS $fstate -x /lib:global-state"
start_backend -s init -f $cfg -- -sS $fstate -x /lib:global-state
new "start backend -s init -f $cfg -- -sS $fstate"
start_backend -s init -f $cfg -- -sS $fstate
fi
new "waiting"
wait_backend
#-----------------------------
new "1. Empty config/state, expect global default state"

View file

@ -146,7 +146,7 @@ testrun_stop
#----------------------------
testrun_start "/es:members/es:member/es:stats/es:numbers"
new "NETCONF get leaf-list member/numbers 0-10 alice"
new "NETCONF get leaf-list member/numbers all"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><get content=\"nonconfig\"><filter type=\"xpath\" select=\"$xpath\" xmlns:es=\"http://example.com/ns/example-social\"/><list-pagination xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">true</list-pagination><offset xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">0</offset><limit xmlns=\"http://clicon.org/clixon-netconf-list-pagination\">10</limit></get></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><data><members xmlns=\"http://example.com/ns/example-social\"><member><member-id>alice</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><stats><numbers>3</numbers><numbers>4</numbers><numbers>5</numbers><numbers>6</numbers><numbers>7</numbers><numbers>8</numbers></stats></member><member><member-id>bob</member-id><privacy-settings><post-visibility>public</post-visibility></privacy-settings><stats><numbers>13</numbers><numbers>14</numbers><numbers>15</numbers><numbers>16</numbers></stats></member></members></data></rpc-reply>]]>]]>$"
testrun_stop