]> git.puffer.fish Git - matthieu/frr.git/commitdiff
lib: mgmtd: implement full XPath 1.0 predicate functionality
authorChristian Hopps <chopps@labn.net>
Sat, 6 Jan 2024 09:45:29 +0000 (09:45 +0000)
committerChristian Hopps <chopps@labn.net>
Sun, 7 Jan 2024 15:17:30 +0000 (15:17 +0000)
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 <chopps@labn.net>
lib/mgmt_msg.c
lib/northbound.c
lib/northbound.h
lib/northbound_oper.c
lib/yang.c
lib/yang.h
mgmtd/mgmt_fe_adapter.c
mgmtd/mgmt_txn.c
mgmtd/mgmt_txn.h

index 99d000537ce9974e9376407d862947801974908e..782707b4631420ce591f08551748f5c41e28b545 100644 (file)
@@ -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:
index 18d65e47f13e9413c1fa221f8218add23d47060c..03d252ee52108c09983363eb6c885e36b7be2932 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <zebra.h>
 
+#include "darr.h"
 #include "libfrr.h"
 #include "log.h"
 #include "lib_errors.h"
@@ -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)
index 018d09fac71640d86570c912a6c9cbf774a0536c..37b7055c10df26f956e2ced7344bb6736320e2ec 100644 (file)
@@ -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);
index bd6d870ebcaf9955208ea4463b13407311a21b13..4e131154e7793a421278640173004e68542a49c8 100644 (file)
@@ -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
@@ -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;
@@ -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);
@@ -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;
                                }
                        }
 
@@ -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;
@@ -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);
@@ -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;
 
@@ -1675,6 +1690,7 @@ error:
        darr_free(ys->query_tokstr);
        darr_free(ys->schema_path);
        darr_free(ys->query_tokens);
+       darr_free(ys->non_specific_predicate);
        return NB_ERR;
 }
 
index 18d2ac58d3479927e38c2fbc4febd251a8f9f165..b2cc71b309184cb9eefa750e91c8b39d9a41901c 100644 (file)
@@ -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)
 {
@@ -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
+}
index 3ce584b347c389cc1f7c81fe1f7de794c333055b..75dcab2d2af84acc47f390e32c6a34b09a065a1a 100644 (file)
@@ -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
index 5f17b89c5c61b938ddb372c131e66ee13f2ad9d1..b7c7a0fff1470f3b8bc375556a6797d3e181219e 100644 (file)
@@ -1132,14 +1132,21 @@ done:
 }
 
 /**
- * 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
@@ -1147,14 +1154,32 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
                             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
@@ -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 */
@@ -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
@@ -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);
 }
 
 /**
index ea3236e80b96915488b50501028a38d8e304f0a3..1c644456314473b7ed7811db4961741de5412ae3 100644 (file)
@@ -173,10 +173,11 @@ struct mgmt_get_data_req {
 
 struct txn_req_get_tree {
        char *xpath;           /* xpath of tree to get */
-       uint8_t result_type;   /* LYD_FORMAT for results */
        uint64_t sent_clients; /* Bitmask of clients sent req to */
        uint64_t recv_clients; /* Bitmask of clients recv reply from */
        int32_t partial_error; /* an error while gather results */
+       uint8_t result_type;   /* LYD_FORMAT for results */
+       uint8_t simple_xpath;  /* if xpath is simple */
        struct lyd_node *client_results; /* result tree from clients */
 };
 
@@ -1268,22 +1269,33 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn,
 {
        struct txn_req_get_tree *get_tree = txn_req->req.get_tree;
        uint64_t req_id = txn_req->req_id;
-       int ret = 0;
+       int ret = NB_OK;
 
        /* cancel timer and send reply onward */
        EVENT_OFF(txn->get_tree_timeout);
 
-       ret = mgmt_fe_adapter_send_tree_data(txn->session_id, txn->txn_id,
-                                            txn_req->req_id,
-                                            get_tree->result_type,
-                                            get_tree->client_results,
-                                            get_tree->partial_error, false);
+       if (!get_tree->simple_xpath && get_tree->client_results) {
+               /*
+                * We have a complex query so Filter results by the xpath query.
+                */
+               ret = yang_trim_tree(get_tree->client_results,
+                                    txn_req->req.get_tree->xpath);
+       }
+
+       if (ret == NB_OK)
+               ret = mgmt_fe_adapter_send_tree_data(txn->session_id,
+                                                    txn->txn_id,
+                                                    txn_req->req_id,
+                                                    get_tree->result_type,
+                                                    get_tree->client_results,
+                                                    get_tree->partial_error,
+                                                    false);
 
        /* we're done with the request */
        mgmt_txn_req_free(&txn_req);
 
        if (ret) {
-               MGMTD_TXN_ERR("Error saving the results of GETTREE for txn-id %" PRIu64
+               MGMTD_TXN_ERR("Error sending the results of GETTREE for txn-id %" PRIu64
                              " req_id %" PRIu64 " to requested type %u",
                              txn->txn_id, req_id, get_tree->result_type);
 
@@ -2352,7 +2364,7 @@ int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id,
  */
 int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
                                uint64_t clients, LYD_FORMAT result_type,
-                               const char *xpath)
+                               bool simple_xpath, const char *xpath)
 {
        struct mgmt_msg_get_tree *msg;
        struct mgmt_txn_ctx *txn;
@@ -2370,6 +2382,7 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
        txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE);
        get_tree = txn_req->req.get_tree;
        get_tree->result_type = result_type;
+       get_tree->simple_xpath = simple_xpath;
        get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath);
 
        msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1,
index 4aa067775558163a4db5edfd8f1794068aa41be4..39d8cde169534d424bf78528266449a925fce97f 100644 (file)
@@ -203,13 +203,15 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id,
  *     req_id: FE client request identifier.
  *     clients: Bitmask of clients to send get-tree to.
  *     result_type: LYD_FORMAT result format.
+ *     simple_xpath: true if xpath is simple (only key predicates).
  *     xpath: The xpath to get the tree from.
+ *
  * Return:
  *     0 on success.
  */
 extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
                                       uint64_t clients, LYD_FORMAT result_type,
-                                      const char *xpath);
+                                      bool simple_xpath, const char *xpath);
 
 /*
  * Notifiy backend adapter on connection.