Skip to content

Commit

Permalink
lib: mgmtd: implement full XPath 1.0 predicate functionality
Browse files Browse the repository at this point in the history
Allow user to specify full YANG compatible XPath 1.0 predicates. This
allows for trimming results of generic queries using functions and other
non-key predicates from XPath 1.0

Signed-off-by: Christian Hopps <[email protected]>
  • Loading branch information
choppsv1 committed Jan 7, 2024
1 parent 00138ff commit cf67a7e
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 22 deletions.
2 changes: 1 addition & 1 deletion lib/mgmt_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms,
}

/**
* Write data from a onto the socket, using streams that have been queued for
* Write data onto the socket, using streams that have been queued for
* sending by mgmt_msg_send_msg. This function should be reschedulable.
*
* Args:
Expand Down
21 changes: 21 additions & 0 deletions lib/northbound.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <zebra.h>

#include "darr.h"
#include "libfrr.h"
#include "log.h"
#include "lib_errors.h"
Expand Down Expand Up @@ -168,6 +169,26 @@ struct nb_node *nb_node_find(const char *path)
return snode->priv;
}

struct nb_node **nb_nodes_find(const char *xpath)
{
struct lysc_node **snodes = NULL;
struct nb_node **nb_nodes = NULL;
bool simple;
LY_ERR err;
uint i;

err = yang_resolve_snode_xpath(ly_native_ctx, xpath, &snodes, &simple);
if (err)
return NULL;

darr_ensure_i(nb_nodes, darr_lasti(snodes));
darr_foreach_i (snodes, i)
nb_nodes[i] = snodes[i]->priv;
darr_free(snodes);
return nb_nodes;
}


void nb_node_set_dependency_cbs(const char *dependency_xpath,
const char *dependant_xpath,
struct nb_dependency_callbacks *cbs)
Expand Down
8 changes: 8 additions & 0 deletions lib/northbound.h
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,14 @@ void nb_nodes_delete(void);
*/
extern struct nb_node *nb_node_find(const char *xpath);

/**
* nb_nodes_find() - find the NB nodes corresponding to complex xpath.
* @xpath: XPath to search for (with or without predicates).
*
* Return: a dynamic array (darr) of `struct nb_node *`s.
*/
extern struct nb_node **nb_nodes_find(const char *xpath);

extern void nb_node_set_dependency_cbs(const char *dependency_xpath,
const char *dependant_xpath,
struct nb_dependency_callbacks *cbs);
Expand Down
24 changes: 20 additions & 4 deletions lib/northbound_oper.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ struct nb_op_node_info {
* @schema_path: the schema nodes for each node in the query string.
# @query_tokstr: the query string tokenized with NUL bytes.
* @query_tokens: the string pointers to each query token (node).
* @non_specific_predicate: tracks if a query_token is non-specific predicate.
* @walk_root_level: The topmost specific node, +1 is where we start walking.
* @walk_start_level: @walk_root_level + 1.
* @query_base_level: the level the query string stops at and full walks
Expand All @@ -85,6 +86,7 @@ struct nb_op_yield_state {
const struct lysc_node **schema_path;
char *query_tokstr;
char **query_tokens;
uint8_t *non_specific_predicate;
int walk_root_level;
int walk_start_level;
int query_base_level;
Expand Down Expand Up @@ -158,6 +160,7 @@ static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys,
if (!nofree_tree && ys_root_node(ys))
lyd_free_all(ys_root_node(ys));
darr_free(ys->query_tokens);
darr_free(ys->non_specific_predicate);
darr_free(ys->query_tokstr);
darr_free(ys->schema_path);
darr_free(ys->node_infos);
Expand Down Expand Up @@ -1142,18 +1145,23 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume)
is_specific_node = false;
if (list_start &&
at_clevel <= darr_lasti(ys->query_tokens) &&
!ys->non_specific_predicate[at_clevel] &&
nb_op_schema_path_has_predicate(ys, at_clevel)) {
err = lyd_new_path(&pni->inner->node, NULL,
ys->query_tokens[at_clevel],
NULL, 0, &node);
if (!err)
/* predicate resolved to specific node */
is_specific_node = true;
else if (err == LY_EVALID)
ys->non_specific_predicate[at_clevel] = true;
else {
flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
"%s: unable to create node for specific query string: %s",
flog_err(EC_LIB_NB_OPERATIONAL_DATA,
"%s: unable to create node for specific query string: %s: %s",
__func__,
ys->query_tokens[at_clevel]);
ys->query_tokens[at_clevel],
yang_ly_strerrcode(err));
ret = NB_ERR;
goto done;
}
}

Expand Down Expand Up @@ -1570,6 +1578,7 @@ static enum nb_error nb_op_yield(struct nb_op_yield_state *ys)
static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
struct nb_node **last)
{
struct nb_node **nb_nodes = NULL;
const struct lysc_node *sn;
struct nb_node *nblast;
char *s, *s2;
Expand All @@ -1587,6 +1596,11 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
* string over each schema trunk in the set.
*/
nblast = nb_node_find(ys->xpath);
if (!nblast) {
nb_nodes = nb_nodes_find(ys->xpath);
nblast = darr_len(nb_nodes) ? nb_nodes[0] : NULL;
darr_free(nb_nodes);
}
if (!nblast) {
flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, ys->xpath);
Expand Down Expand Up @@ -1614,6 +1628,7 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
/* create our arrays */
darr_append_n(ys->schema_path, count);
darr_append_n(ys->query_tokens, count);
darr_append_nz(ys->non_specific_predicate, count);
for (sn = nblast->snode; sn; sn = sn->parent)
ys->schema_path[--count] = sn;

Expand Down Expand Up @@ -1675,6 +1690,7 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
darr_free(ys->query_tokstr);
darr_free(ys->schema_path);
darr_free(ys->query_tokens);
darr_free(ys->non_specific_predicate);
return NB_ERR;
}

Expand Down
105 changes: 105 additions & 0 deletions lib/yang.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,38 @@ void yang_snode_get_path(const struct lysc_node *snode,
}
}

LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath,
struct lysc_node ***snodes, bool *simple)
{
struct lysc_node *snode;
struct ly_set *set;
LY_ERR err;

/* lys_find_path will not resolve complex xpaths */
snode = (struct lysc_node *)lys_find_path(ly_ctx, NULL, xpath, 0);
if (snode) {
*darr_append(*snodes) = snode;
*simple = true;
return LY_SUCCESS;
}

/* Try again to catch complex query cases */
err = lys_find_xpath(ly_native_ctx, NULL, xpath, 0, &set);
if (err)
return err;
if (!set->count) {
ly_set_free(set, NULL);
return LY_ENOTFOUND;
}

*simple = false;
darr_ensure_i(*snodes, set->count - 1);
memcpy(*snodes, set->snodes, set->count * sizeof(set->snodes[0]));
ly_set_free(set, NULL);
return LY_SUCCESS;
}


struct lysc_node *yang_find_snode(struct ly_ctx *ly_ctx, const char *xpath,
uint32_t options)
{
Expand Down Expand Up @@ -1019,3 +1051,76 @@ LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent,
/*NOTREACHED*/
return LY_EINVAL;
}


int yang_trim_tree(struct lyd_node *root, const char *xpath)
{
enum nb_error ret = NB_OK;
LY_ERR err;
#if 0
err = lyd_trim_xpath(&root, xpath, NULL);
if (err) {
flog_err_sys(EC_LIB_LIBYANG,
"cannot obtain specific result for xpath \"%s\"",
xpath);
return NB_ERR;
}
return NB_OK;
#else
struct lyd_node *node;
struct lyd_node **remove = NULL;
struct ly_set *set = NULL;
uint32_t i;

err = lyd_find_xpath3(NULL, root, xpath, NULL, &set);
if (err) {
flog_err_sys(EC_LIB_LIBYANG,
"cannot obtain specific result for xpath \"%s\"",
xpath);
ret = NB_ERR;
goto done;
}
/*
* Mark keepers and sweep deleting non-keepers.
*
* NOTE: We assume the data-nodes have NULL priv pointers and use that
* for our mark.
*/

/* Mark */
for (i = 0; i < set->count; i++) {
for (node = set->dnodes[i]; node; node = &node->parent->node) {
if (node->priv)
break;
if (node == set->dnodes[i])
node->priv = (void *)2;
else
node->priv = (void *)1;
}
}

darr_ensure_cap(remove, 128);
LYD_TREE_DFS_BEGIN (root, node) {
/*
* If this is a direct matching node then include it's subtree
* which won't be marked and would otherwise be removed.
*/
if (node->priv == (void *)2)
LYD_TREE_DFS_continue = 1;
else if (!node->priv) {
LYD_TREE_DFS_continue = 1;
*darr_append(remove) = node;
}
LYD_TREE_DFS_END(root, node);
}
darr_foreach_i (remove, i)
lyd_free_tree(remove[i]);
darr_free(remove);

done:
if (set)
ly_set_free(set, NULL);

return ret;
#endif
}
29 changes: 29 additions & 0 deletions lib/yang.h
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,35 @@ extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent,
const struct lysc_node *snode,
const struct yang_list_keys *keys,
struct lyd_node_inner **node);
/**
* yang_resolve_snodes() - Resolve an XPath to matching schema nodes.
* @ly_ctx: libyang context to operate on.
* @xpath: the path or XPath to resolve.
* @snodes: [OUT] pointer for resulting dynamic array (darr) of schema node
* pointers.
* @simple: [OUT] indicates if @xpath was resolvable simply or not. Non-simple
* means that the @xpath is not a simple path and utilizes XPath 1.0
* functionality beyond simple key predicates.
*
* This function can be used to find the schema node (or nodes) that correspond
* to a given @xpath. If the @xpath includes non-key predicates (e.g., using
* functions) then @simple will be set to false, and @snodes may contain more
* than a single schema node.
*
* Return: a libyang error or LY_SUCCESS.
*/
extern LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath,
struct lysc_node ***snodes, bool *simple);

/**
* yang_trim_tree() - trim the data tree to the given xpath
* @root: the data tree
* @xpath: the xpath to trim @root to.
*
* Return: enum nb_error..
*/
extern int yang_trim_tree(struct lyd_node *root, const char *xpath);

#ifdef __cplusplus
}
#endif
Expand Down
43 changes: 36 additions & 7 deletions mgmtd/mgmt_fe_adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -1132,29 +1132,54 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session,
}

/**
* Handle a get-tree message from the client.
* fe_adapter_handle_get_tree() - Handle a get-tree message from a FE client.
* @session: the client session.
* @msg_raw: the message data.
* @msg_len: the length of the message data.
*/
static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
void *data, size_t len)
void *__msg, size_t msg_len)
{
struct mgmt_msg_get_tree *msg = data;
struct mgmt_msg_get_tree *msg = __msg;
struct lysc_node **snodes = NULL;
char *xpath_resolved = NULL;
uint64_t req_id = msg->req_id;
uint64_t clients;
bool simple_xpath;
LY_ERR err;
int ret;

MGMTD_FE_ADAPTER_DBG("Received get-tree request from client %s for session-id %" PRIu64
" req-id %" PRIu64,
session->adapter->name, session->session_id,
msg->req_id);

if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) {
fe_adapter_send_error(session, req_id, false, -EINVAL,
"Invalid message rcvd from session-id: %" PRIu64,
session->session_id);
goto done;
}

if (session->txn_id != MGMTD_TXN_ID_NONE) {
fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
"Transaction in progress txn-id: %" PRIu64
" for session-id: %" PRIu64,
session->txn_id, session->session_id);
return;
goto done;
}


err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes,
&simple_xpath);
if (err) {
fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
"XPath doesn't resolve for session-id: %" PRIu64,
session->session_id);
goto done;
}
darr_free(snodes);

clients = mgmt_be_interested_clients(msg->xpath, false);
if (!clients) {
MGMTD_FE_ADAPTER_DBG("No backends provide xpath: %s for txn-id: %" PRIu64
Expand All @@ -1164,7 +1189,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,

fe_adapter_send_tree_data(session, req_id, false,
msg->result_type, NULL, 0);
return;
goto done;
}

/* Start a SHOW Transaction */
Expand All @@ -1173,7 +1198,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
if (session->txn_id == MGMTD_SESSION_ID_NONE) {
fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
"failed to create a 'show' txn");
return;
goto done;
}

MGMTD_FE_ADAPTER_DBG("Created new show txn-id: %" PRIu64
Expand All @@ -1182,13 +1207,17 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,

/* Create a GET-TREE request under the transaction */
ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients,
msg->result_type, msg->xpath);
msg->result_type, simple_xpath,
msg->xpath);
if (ret) {
/* destroy the just created txn */
mgmt_destroy_txn(&session->txn_id);
fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
"failed to create a 'show' txn");
}
done:
darr_free(snodes);
darr_free(xpath_resolved);
}

/**
Expand Down
Loading

0 comments on commit cf67a7e

Please sign in to comment.