]> git.puffer.fish Git - matthieu/frr.git/commitdiff
lib: northbound: add yielding and batching to oper-state queries
authorChristian Hopps <chopps@labn.net>
Mon, 30 Oct 2023 09:09:19 +0000 (05:09 -0400)
committerChristian Hopps <chopps@labn.net>
Thu, 28 Dec 2023 17:52:57 +0000 (17:52 +0000)
Signed-off-by: Christian Hopps <chopps@labn.net>
lib/mgmt_be_client.c
lib/mgmt_msg_native.h
lib/northbound.c
lib/northbound.h
lib/northbound_cli.c
lib/northbound_grpc.cpp
lib/northbound_oper.c [new file with mode: 0644]
lib/northbound_sysrepo.c
lib/subdir.am
mgmtd/mgmt_txn.c

index b0d55d2015ff91149922eb82941d2f7a2f91101a..020096666360b3c5c1d6bd6377492284d0278a12 100644 (file)
@@ -26,6 +26,7 @@ DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT, "backend client");
 DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT_NAME, "backend client name");
 DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_BATCH, "backend transaction batch data");
 DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_TXN, "backend transaction data");
+DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_GT_CB_ARGS, "backend get-tree cb args");
 
 enum mgmt_be_txn_event {
        MGMTD_BE_TXN_PROC_SETCFG = 1,
@@ -770,64 +771,89 @@ static int mgmt_be_client_handle_msg(struct mgmt_be_client *client_ctx,
        return 0;
 }
 
+struct be_client_tree_data_batch_args {
+       struct mgmt_be_client *client;
+       uint64_t txn_id;
+       uint64_t req_id;
+       LYD_FORMAT result_type;
+};
+
 /*
  * Process the get-tree request on our local oper state
  */
-static void be_client_handle_get_tree(struct mgmt_be_client *client,
-                                     uint64_t txn_id, void *msgbuf,
-                                     size_t msg_len)
+static enum nb_error be_client_send_tree_data_batch(const struct lyd_node *tree,
+                                                   void *arg, enum nb_error ret)
 {
-       struct mgmt_msg_get_tree *get_tree_msg = msgbuf;
+       struct be_client_tree_data_batch_args *args = arg;
+       struct mgmt_be_client *client = args->client;
        struct mgmt_msg_tree_data *tree_msg = NULL;
-       struct lyd_node *dnode = NULL;
        uint8_t *buf = NULL;
-       int ret;
+       bool more = false;
+       LY_ERR err;
 
-       MGMTD_BE_CLIENT_DBG("Received get-tree request for client %s txn-id %" PRIu64
-                           " req-id %" PRIu64,
-                           client->name, txn_id, get_tree_msg->req_id);
-
-       /* NOTE: removed the translator, if put back merge with northbound_cli
-        * code
-        */
-
-       /* Obtain data. */
-       ret = nb_oper_data_iterate(get_tree_msg->xpath, NULL, 0,
-                                  NULL, NULL, &dnode);
-       if (ret != NB_OK) {
-fail:
-               if (dnode)
-                       yang_dnode_free(dnode);
-               darr_free(buf);
-               be_client_send_error(client, get_tree_msg->txn_id,
-                                    get_tree_msg->req_id, false, -EINVAL,
-                                    "FE cilent %s txn-id %" PRIu64
-                                    " error fetching oper state %d",
-                                    client->name, get_tree_msg->txn_id, ret);
-               return;
+       if (ret == NB_YIELD) {
+               more = true;
+               ret = NB_OK;
        }
-
-       // (void)lyd_validate_all(&dnode, ly_native_ctx, 0, NULL);
+       if (ret != NB_OK)
+               goto done;
 
        darr_append_nz(buf, offsetof(typeof(*tree_msg), result));
        tree_msg = (typeof(tree_msg))buf;
-       tree_msg->session_id = get_tree_msg->session_id;
-       tree_msg->req_id = get_tree_msg->req_id;
+       tree_msg->txn_id = args->txn_id;
+       tree_msg->req_id = args->req_id;
        tree_msg->code = MGMT_MSG_CODE_TREE_DATA;
-       tree_msg->result_type = get_tree_msg->result_type;
-       ret = yang_print_tree_append(&buf, dnode, get_tree_msg->result_type,
+       tree_msg->result_type = args->result_type;
+       tree_msg->more = more;
+       err = yang_print_tree_append(&buf, tree, args->result_type,
                                     (LYD_PRINT_WD_EXPLICIT |
                                      LYD_PRINT_WITHSIBLINGS));
        /* buf may have been reallocated and moved */
        tree_msg = (typeof(tree_msg))buf;
 
-       if (ret != LY_SUCCESS)
-               goto fail;
-
+       if (err) {
+               ret = NB_ERR;
+               goto done;
+       }
        (void)be_client_send_native_msg(client, buf, darr_len(buf), false);
-
+done:
        darr_free(buf);
-       yang_dnode_free(dnode);
+       if (ret)
+               be_client_send_error(client, args->txn_id, args->req_id, false,
+                                    -EINVAL,
+                                    "FE cilent %s txn-id %" PRIu64
+                                    " error fetching oper state %d",
+                                    client->name, args->txn_id, ret);
+       if (ret != NB_OK || !more)
+               XFREE(MTYPE_MGMTD_BE_GT_CB_ARGS, args);
+       return ret;
+}
+
+/*
+ * Process the get-tree request on our local oper state
+ */
+static void be_client_handle_get_tree(struct mgmt_be_client *client,
+                                     uint64_t txn_id, void *msgbuf,
+                                     size_t msg_len)
+{
+       struct mgmt_msg_get_tree *get_tree_msg = msgbuf;
+       struct be_client_tree_data_batch_args *args;
+
+       MGMTD_BE_CLIENT_DBG("Received get-tree request for client %s txn-id %" PRIu64
+                           " req-id %" PRIu64,
+                           client->name, txn_id, get_tree_msg->req_id);
+
+       /* NOTE: removed the translator, if put back merge with northbound_cli
+        * code
+        */
+
+       args = XMALLOC(MTYPE_MGMTD_BE_GT_CB_ARGS, sizeof(*args));
+       args->client = client;
+       args->txn_id = get_tree_msg->txn_id;
+       args->req_id = get_tree_msg->req_id;
+       args->result_type = get_tree_msg->result_type;
+       nb_oper_walk(get_tree_msg->xpath, NULL, 0, true, NULL, NULL,
+                  be_client_send_tree_data_batch, args);
 }
 
 /*
@@ -1057,6 +1083,7 @@ void mgmt_be_client_destroy(struct mgmt_be_client *client)
        MGMTD_BE_CLIENT_DBG("Destroying MGMTD Backend Client '%s'",
                            client->name);
 
+       nb_oper_cancel_all_walks();
        msg_client_cleanup(&client->client);
        mgmt_be_cleanup_all_txns(client);
        mgmt_be_txns_fini(&client->txn_head);
index 3a2bf104a801e0b6e4df4b2676e81a4e5b557e9b..a3b456efaac1862ccdaeca966e12c43d47067e42 100644 (file)
@@ -77,6 +77,7 @@ struct mgmt_msg_tree_data {
        struct mgmt_msg_header;
        int8_t partial_error;
        uint8_t result_type;
+       uint8_t more;
 
        alignas(8) uint8_t result[];
 };
index d55255a0460b3a2b510060fcd3c1c17f33280465..3b02c08bbfbc8a20ec1362d12087b585c4323c22 100644 (file)
@@ -1465,6 +1465,50 @@ const void *nb_callback_lookup_entry(const struct nb_node *nb_node,
        return nb_node->cbs.lookup_entry(&args);
 }
 
+const void *nb_callback_lookup_node_entry(struct lyd_node *node,
+                                         const void *parent_list_entry)
+{
+       struct yang_list_keys keys;
+       struct nb_cb_lookup_entry_args args = {};
+       const struct nb_node *nb_node = node->schema->priv;
+
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+               return NULL;
+
+       if (yang_get_node_keys(node, &keys)) {
+               flog_warn(EC_LIB_LIBYANG,
+                         "%s: can't get keys for lookup from existing data node %s",
+                         __func__, node->schema->name);
+               return NULL;
+       }
+
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (lookup_node_entry): node [%s] parent_list_entry [%p]",
+              nb_node->xpath, parent_list_entry);
+
+       args.parent_list_entry = parent_list_entry;
+       args.keys = &keys;
+       return nb_node->cbs.lookup_entry(&args);
+}
+
+const void *nb_callback_lookup_next(const struct nb_node *nb_node,
+                                   const void *parent_list_entry,
+                                   const struct yang_list_keys *keys)
+{
+       struct nb_cb_lookup_entry_args args = {};
+
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+               return NULL;
+
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]",
+              nb_node->xpath, parent_list_entry);
+
+       args.parent_list_entry = parent_list_entry;
+       args.keys = keys;
+       return nb_node->cbs.lookup_next(&args);
+}
+
 int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
                    const struct list *input, struct list *output, char *errmsg,
                    size_t errmsg_len)
@@ -1967,9 +2011,8 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node,
 
        /* Iterate over all list entries. */
        do {
-               const struct lysc_node_leaf *skey;
                struct yang_list_keys list_keys = {};
-               int len, len2, ret;
+               int len, ret;
 
                /* Obtain list entry. */
                list_entry = nb_callback_get_next(nb_node, parent_list_entry,
@@ -1994,19 +2037,10 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node,
                        len = strlen(xpath);
                        key_preds = &xpath[len];
 
-                       unsigned int i = 0;
-                       LY_FOR_KEYS (snode, skey) {
-                               assert(i < list_keys.num);
-                               len2 = snprintf(xpath + len, sizeof(xpath) - len,
-                                               "[%s='%s']", skey->name,
-                                               list_keys.key[i]);
-                               if (len2 > (ssize_t)sizeof(xpath) - len)
-                                       len = sizeof(xpath);
-                               else
-                                       len += len2;
-                               i++;
-                       }
-                       assert(i == list_keys.num);
+                       uint n = yang_get_key_preds(xpath + len, snode,
+                                                   &list_keys,
+                                                   sizeof(xpath) - len);
+                       assert(n == list_keys.num);
 
                } else {
                        /*
@@ -2038,13 +2072,12 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node,
        return NB_OK;
 }
 
-static int nb_oper_data_iter_node(const struct lysc_node *snode,
-                                 const char *xpath_parent,
-                                 const void *list_entry,
-                                 const struct yang_list_keys *list_keys,
-                                 struct yang_translator *translator, bool first,
-                                 uint32_t flags, nb_oper_data_cb cb, void *arg,
-                                 struct lyd_node *pdnode)
+int nb_oper_data_iter_node(const struct lysc_node *snode,
+                          const char *xpath_parent, const void *list_entry,
+                          const struct yang_list_keys *list_keys,
+                          struct yang_translator *translator, bool first,
+                          uint32_t flags, nb_oper_data_cb cb, void *arg,
+                          struct lyd_node *pdnode)
 {
        struct nb_node *nb_node;
        char xpath[XPATH_MAXLEN];
@@ -2083,6 +2116,7 @@ static int nb_oper_data_iter_node(const struct lysc_node *snode,
        nb_node = snode->priv;
        switch (snode->nodetype) {
        case LYS_CONTAINER:
+               /* does something, then walks children */
                ret = nb_oper_data_iter_container(nb_node, xpath, first,
                                                  list_entry, list_keys,
                                                  translator, flags, cb, arg,
@@ -2090,21 +2124,25 @@ static int nb_oper_data_iter_node(const struct lysc_node *snode,
 
                break;
        case LYS_LEAF:
+               /* does something then returns */
                ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry,
                                             list_keys, translator, flags, cb,
                                             arg, pdnode);
                break;
        case LYS_LEAFLIST:
+               /* walks leaf list doing things and returns */
                ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry,
                                                 list_keys, translator, flags,
                                                 cb, arg, pdnode);
                break;
        case LYS_LIST:
+               /* walks children */
                ret = nb_oper_data_iter_list(nb_node, xpath, list_entry,
                                             list_keys, translator, flags, cb,
                                             arg, pdnode);
                break;
        case LYS_USES:
+               /* walks children */
                ret = nb_oper_data_iter_children(snode, xpath, list_entry,
                                                 list_keys, translator, false,
                                                 flags, cb, arg, pdnode);
@@ -2171,7 +2209,6 @@ done:
        return ret;
 }
 
-
 int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
                         uint32_t flags, nb_oper_data_cb cb, void *arg,
                         struct lyd_node **tree)
@@ -2214,6 +2251,7 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
                return ret;
        }
 
+
        /*
         * Create a linked list to sort the data nodes starting from the root.
         */
@@ -2296,10 +2334,14 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
        if (dnode) {
                while (lyd_parent(dnode))
                        dnode = lyd_parent(dnode);
-               if (!tree)
-                       lyd_free_all(dnode);
-               else
+
+               if (tree && ret == NB_OK)
                        *tree = dnode;
+               else {
+                       lyd_free_all(dnode);
+                       if (tree)
+                               *tree = NULL;
+               }
        }
 
        return ret;
@@ -2703,6 +2745,8 @@ const char *nb_err_name(enum nb_error error)
                return "failed to allocate resource";
        case NB_ERR_INCONSISTENCY:
                return "internal inconsistency";
+       case NB_YIELD:
+               return "should yield";
        }
 
        assert(!"Reached end of function we should never hit");
@@ -2824,10 +2868,15 @@ void nb_init(struct event_loop *tm,
 
        /* Initialize the northbound CLI. */
        nb_cli_init(tm);
+
+       /* Initialize oper-state */
+       nb_oper_init(tm);
 }
 
 void nb_terminate(void)
 {
+       nb_oper_terminate();
+
        /* Terminate the northbound CLI. */
        nb_cli_terminate();
 
index 2a338a082c32bbd163ef42ca5b11e3196194fb84..850397d221d63868543f9186b9eb33e32c46df0c 100644 (file)
@@ -484,6 +484,22 @@ struct nb_callbacks {
         */
        const void *(*lookup_entry)(struct nb_cb_lookup_entry_args *args);
 
+       /*
+        * Operational data callback for YANG lists.
+        *
+        * The callback function should return the next list entry that would
+        * follow a list entry with the keys given as a parameter. Keyless
+        * lists don't need to implement this  callback.
+        *
+        * args
+        *    Refer to the documentation comments of nb_cb_lookup_entry_args for
+        *    details.
+        *
+        * Returns:
+        *    Pointer to the list entry if found, or NULL if not found.
+        */
+       const void *(*lookup_next)(struct nb_cb_lookup_entry_args *args);
+
        /*
         * RPC and action callback.
         *
@@ -644,6 +660,7 @@ enum nb_error {
        NB_ERR_VALIDATION,
        NB_ERR_RESOURCE,
        NB_ERR_INCONSISTENCY,
+       NB_YIELD,
 };
 
 /* Default priority. */
@@ -710,6 +727,29 @@ typedef int (*nb_oper_data_cb)(const struct lysc_node *snode,
                               struct yang_translator *translator,
                               struct yang_data *data, void *arg);
 
+/**
+ * nb_oper_data_finish_cb() - finish a portion or all of a oper data walk.
+ * @tree - r/o copy of the tree created during this portion of the walk.
+ * @arg - finish arg passed to nb_op_iterate_yielding.
+ * @ret - NB_OK if done with walk, NB_YIELD if done with portion, otherwise an
+ *        error.
+ *
+ * If nb_op_iterate_yielding() was passed with @should_batch set then this
+ * callback will be invoked during each portion (batch) of the walk.
+ *
+ * The @tree is read-only and should not be modified or freed.
+ *
+ * If this function returns anything but NB_OK then the walk will be terminated.
+ * and this function will not be called again regardless of if @ret was
+ * `NB_YIELD` or not.
+ *
+ * Return: NB_OK to continue or complete the walk normally, otherwise an error
+ * to immediately terminate the walk.
+ */
+/* Callback function used by nb_oper_data_iter_yielding(). */
+typedef enum nb_error (*nb_oper_data_finish_cb)(const struct lyd_node *tree,
+                                               void *arg, enum nb_error ret);
+
 /* Iterate over direct child nodes only. */
 #define NB_OPER_DATA_ITER_NORECURSE 0x0001
 
@@ -743,6 +783,11 @@ extern int nb_callback_get_keys(const struct nb_node *nb_node,
 extern const void *nb_callback_lookup_entry(const struct nb_node *nb_node,
                                            const void *parent_list_entry,
                                            const struct yang_list_keys *keys);
+extern const void *nb_callback_lookup_node_entry(struct lyd_node *node,
+                                                const void *parent_list_entry);
+extern const void *nb_callback_lookup_next(const struct nb_node *nb_node,
+                                          const void *parent_list_entry,
+                                          const struct yang_list_keys *keys);
 extern int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
                           const struct list *input, struct list *output,
                           char *errmsg, size_t errmsg_len);
@@ -1250,8 +1295,13 @@ extern int nb_running_unlock(enum nb_client client, const void *user);
  */
 extern int nb_running_lock_check(enum nb_client client, const void *user);
 
+extern int nb_oper_data_iterate(const char *xpath,
+                               struct yang_translator *translator,
+                               uint32_t flags, nb_oper_data_cb cb, void *arg,
+                               struct lyd_node **tree);
+
 /*
- * Iterate over operational data.
+ * Iterate over operational data -- deprecated.
  *
  * xpath
  *    Data path of the YANG data we want to iterate over.
@@ -1262,6 +1312,9 @@ extern int nb_running_lock_check(enum nb_client client, const void *user);
  * flags
  *    NB_OPER_DATA_ITER_ flags to control how the iteration is performed.
  *
+ * should_batch
+ *    Should call finish cb with partial results (i.e., creating batches)
+ *
  * cb
  *    Function to call with each data node.
  *
@@ -1274,10 +1327,42 @@ extern int nb_running_lock_check(enum nb_client client, const void *user);
  * Returns:
  *    NB_OK on success, NB_ERR otherwise.
  */
-extern int nb_oper_data_iterate(const char *xpath,
-                               struct yang_translator *translator,
-                               uint32_t flags, nb_oper_data_cb cb, void *arg,
-                               struct lyd_node **tree);
+extern enum nb_error nb_oper_iterate_legacy(const char *xpath,
+                                           struct yang_translator *translator,
+                                           uint32_t flags, nb_oper_data_cb cb,
+                                           void *arg, struct lyd_node **tree);
+
+/**
+ * nb_op_walk() - walk the schema building operational state.
+ * @xpath -
+ * @translator -
+ * @flags -
+ * @should_batch - should allow yielding and processing portions of the tree.
+ * @cb - callback invoked for each non-list, non-container node.
+ * @arg - arg to pass to @cb.
+ * @finish - function to call when done with portion or all of walk.
+ * @finish_arg - arg to pass to @finish.
+ *
+ * Return: walk - a cookie that can be used to cancel the walk.
+ */
+extern void *nb_oper_walk(const char *xpath, struct yang_translator *translator,
+                         uint32_t flags, bool should_batch, nb_oper_data_cb cb,
+                         void *arg, nb_oper_data_finish_cb finish,
+                         void *finish_arg);
+
+/**
+ * nb_op_iterate_yielding_cancel() - cancel the in progress walk.
+ * @walk - value returned from nb_op_iterate_yielding()
+ *
+ * Should only be called on an in-progress walk. It is invalid to cancel and
+ * already finished walk. The walks `finish` callback will not be called.
+ */
+extern void nb_oper_cancel_walk(void *walk);
+
+/**
+ * nb_op_cancel_all_walks() - cancel all in progress walks.
+ */
+extern void nb_oper_cancel_all_walks(void);
 
 /*
  * Validate if the northbound operation is valid for the given node.
@@ -1485,6 +1570,9 @@ extern void nb_init(struct event_loop *tm,
  */
 extern void nb_terminate(void);
 
+extern void nb_oper_init(struct event_loop *loop);
+extern void nb_oper_terminate(void);
+
 #ifdef __cplusplus
 }
 #endif
index cba40035a37d8a8b93a4d1571dddc49690c4738a..20f030e28097b1a500c53c3a5abad1c645d4134a 100644 (file)
@@ -1437,11 +1437,9 @@ static int nb_cli_oper_data_cb(const struct lysc_node *snode,
        }
 
 exit:
-       yang_data_free(data);
        return NB_OK;
 
 error:
-       yang_data_free(data);
        return NB_ERR;
 }
 
@@ -1490,9 +1488,14 @@ DEFPY (show_yang_operational_data,
                ly_ctx = ly_native_ctx;
 
        /* Obtain data. */
-       dnode = yang_dnode_new(ly_ctx, false);
-       ret = nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
-                                  dnode, NULL);
+       if (translator) {
+               dnode = yang_dnode_new(ly_ctx, false);
+               ret = nb_oper_iterate_legacy(xpath, translator, 0,
+                                          nb_cli_oper_data_cb, dnode, NULL);
+       } else {
+               dnode = NULL;
+               ret = nb_oper_iterate_legacy(xpath, NULL, 0, NULL, NULL, &dnode);
+       }
        if (ret != NB_OK) {
                if (format == LYD_JSON)
                        vty_out(vty, "{}\n");
@@ -1500,7 +1503,8 @@ DEFPY (show_yang_operational_data,
                        /* embed ly_last_errmsg() when we get newer libyang */
                        vty_out(vty, "<!-- Not found -->\n");
                }
-               yang_dnode_free(dnode);
+               if (dnode)
+                       yang_dnode_free(dnode);
                return CMD_WARNING;
        }
 
index 6c33351cef0439b6b20318391f6468a49c5a6e4f..7957752589c49ecfd1a8e46ba9c8ac10de21ac97 100644 (file)
@@ -427,25 +427,11 @@ static struct lyd_node *get_dnode_config(const std::string &path)
        return dnode;
 }
 
-static int get_oper_data_cb(const struct lysc_node *snode,
-                           struct yang_translator *translator,
-                           struct yang_data *data, void *arg)
-{
-       struct lyd_node *dnode = static_cast<struct lyd_node *>(arg);
-       int ret = yang_dnode_edit(dnode, data->xpath, data->value);
-       yang_data_free(data);
-
-       return (ret == 0) ? NB_OK : NB_ERR;
-}
-
 static struct lyd_node *get_dnode_state(const std::string &path)
 {
-       struct lyd_node *dnode = yang_dnode_new(ly_native_ctx, false);
-       if (nb_oper_data_iterate(path.c_str(), NULL, 0, get_oper_data_cb, dnode)
-           != NB_OK) {
-               yang_dnode_free(dnode);
-               return NULL;
-       }
+       struct lyd_node *dnode = NULL;
+
+       (void)nb_oper_iterate_legacy(path.c_str(), NULL, 0, NULL, NULL, &dnode);
 
        return dnode;
 }
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
new file mode 100644 (file)
index 0000000..4a7a0bb
--- /dev/null
@@ -0,0 +1,1769 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * October 14 2023, Christian Hopps <chopps@labn.net>
+ *
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ * Copyright (c) 2023, LabN Consulting, L.L.C.
+ *
+ */
+
+#include <zebra.h>
+#include "darr.h"
+#include "debug.h"
+#include "frrevent.h"
+#include "frrstr.h"
+#include "lib_errors.h"
+#include "monotime.h"
+#include "northbound.h"
+
+/*
+ * YANG model yielding design restrictions:
+ *
+ * In order to be able to yield and guarantee we have a valid data tree at the
+ * point of yielding we must know that each parent has all it's siblings
+ * collected to represent a complete element.
+ *
+ * Basically, there should be a only single branch in the schema tree that
+ * supports yielding. In practice this means:
+ *
+ * list node schema with lookup next:
+ * - must not have any lookup-next list node sibling schema
+ * - must not have any list or container node siblings with lookup-next descendants.
+ * - any parent list nodes must also be lookup-next list nodes
+ *
+ * We must also process containers with lookup-next descendants last.
+ */
+
+DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State");
+DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos");
+
+/* Amount of time allowed to spend constructing oper-state prior to yielding */
+#define NB_OP_WALK_INTERVAL_MS 50
+#define NB_OP_WALK_INTERVAL_US (NB_OP_WALK_INTERVAL_MS * 1000)
+
+/* ---------- */
+/* Data Types */
+/* ---------- */
+PREDECL_LIST(nb_op_walks);
+
+/*
+ * This is our information about a node on the branch we are looking at
+ */
+struct nb_op_node_info {
+       struct lyd_node_inner *inner;
+       const struct lysc_node *schema; /* inner schema in case we rm inner */
+       struct yang_list_keys keys;     /* if list, keys to locate element */
+       const void *list_entry;         /* opaque entry from user or NULL */
+       uint xpath_len;           /* length of the xpath string for this node */
+       uint niters;              /* # list elems create this iteration */
+       uint nents;               /* # list elems create so far */
+       bool query_specific_entry : 1; /* this info is specific specified */
+       bool has_lookup_next : 1; /* if this node support lookup next */
+       bool lookup_next_ok : 1;  /* if this and all previous support */
+};
+
+/**
+ * struct nb_op_yield_state - tracking required state for yielding.
+ *
+ * @xpath: current xpath representing the node_info stack.
+ * @xpath_orig: the original query string from the user
+ * @node_infos: the container stack for the walk from root to current
+ * @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).
+ * @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
+ *                    commence below that.
+ */
+struct nb_op_yield_state {
+       /* Walking state */
+       char *xpath;
+       char *xpath_orig;
+       struct nb_op_node_info *node_infos;
+       const struct lysc_node **schema_path;
+       char *query_tokstr;
+       char **query_tokens;
+       int walk_root_level;
+       int walk_start_level;
+       int query_base_level;
+       bool query_list_entry; /* XXX query was for a specific list entry */
+
+       /* Yielding state */
+       bool query_did_entry;  /* currently processing the entry */
+       bool should_batch;
+       struct timeval start_time;
+       struct yang_translator *translator;
+       uint32_t flags;
+       nb_oper_data_cb cb;
+       void *cb_arg;
+       nb_oper_data_finish_cb finish;
+       void *finish_arg;
+       struct event *walk_ev;
+       struct nb_op_walks_item link;
+};
+
+DECLARE_LIST(nb_op_walks, struct nb_op_yield_state, link);
+
+/* ---------------- */
+/* Global Variables */
+/* ---------------- */
+
+static struct event_loop *event_loop;
+static struct nb_op_walks_head nb_op_walks;
+
+/* --------------------- */
+/* Function Declarations */
+/* --------------------- */
+
+static enum nb_error nb_op_yield(struct nb_op_yield_state *ys);
+static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys);
+
+/* -------------------- */
+/* Function Definitions */
+/* -------------------- */
+
+static inline struct nb_op_yield_state *
+nb_op_create_yield_state(const char *xpath, struct yang_translator *translator,
+                        uint32_t flags, bool should_batch, nb_oper_data_cb cb,
+                        void *cb_arg, nb_oper_data_finish_cb finish,
+                        void *finish_arg)
+{
+       struct nb_op_yield_state *ys;
+
+       ys = XCALLOC(MTYPE_NB_YIELD_STATE, sizeof(*ys));
+       ys->xpath = darr_strdup_cap(xpath, (size_t)XPATH_MAXLEN);
+       ys->xpath_orig = darr_strdup(xpath);
+       ys->translator = translator;
+       ys->flags = flags;
+       ys->should_batch = should_batch;
+       ys->cb = cb;
+       ys->cb_arg = cb_arg;
+       ys->finish = finish;
+       ys->finish_arg = finish_arg;
+
+       nb_op_walks_add_tail(&nb_op_walks, ys);
+
+       return ys;
+}
+
+static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys,
+                                         bool nofree_tree)
+{
+       if (ys) {
+               EVENT_OFF(ys->walk_ev);
+               nb_op_walks_del(&nb_op_walks, ys);
+               /* if we have a branch then free up it's libyang tree */
+               if (!nofree_tree && ys_root_node(ys))
+                       lyd_free_all(ys_root_node(ys));
+               darr_free(ys->query_tokens);
+               darr_free(ys->query_tokstr);
+               darr_free(ys->schema_path);
+               darr_free(ys->node_infos);
+               darr_free(ys->xpath_orig);
+               darr_free(ys->xpath);
+               XFREE(MTYPE_NB_YIELD_STATE, ys);
+       }
+}
+
+static const struct lysc_node *ys_get_walk_stem_tip(struct nb_op_yield_state *ys)
+{
+       if (ys->walk_start_level <= 0)
+               return NULL;
+       return ys->node_infos[ys->walk_start_level - 1].schema;
+}
+
+static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys)
+{
+       if (!darr_len(ys->node_infos))
+               return NULL;
+       return &ys->node_infos[0].inner->node;
+}
+
+static void ys_trim_xpath(struct nb_op_yield_state *ys)
+{
+       uint len = darr_len(ys->node_infos);
+
+       if (len == 0)
+               darr_setlen(ys->xpath, 1);
+       else
+               darr_setlen(ys->xpath, darr_last(ys->node_infos)->xpath_len + 1);
+       ys->xpath[darr_len(ys->xpath) - 1] = 0;
+}
+
+static void ys_pop_inner(struct nb_op_yield_state *ys)
+{
+       uint len = darr_len(ys->node_infos);
+
+       assert(len);
+       darr_setlen(ys->node_infos, len - 1);
+       ys_trim_xpath(ys);
+}
+
+static void nb_op_get_keys(struct lyd_node_inner *list_node,
+                          struct yang_list_keys *keys)
+{
+       struct lyd_node *child;
+       uint n = 0;
+
+       keys->num = 0;
+       LY_LIST_FOR (list_node->child, child) {
+               if (!lysc_is_key(child->schema))
+                       break;
+               strlcpy(keys->key[n], yang_dnode_get_string(child, NULL),
+                       sizeof(keys->key[n]));
+               n++;
+       }
+
+       keys->num = n;
+}
+
+/**
+ * __move_back_to_next() - move back to the next lookup-next schema
+ */
+static bool __move_back_to_next(struct nb_op_yield_state *ys, int i)
+{
+       struct nb_op_node_info *ni;
+       int j;
+
+       /*
+        * We will free the subtree we are trimming back to, or we will be done
+        * with the walk and will free the root on cleanup.
+        */
+
+       /* pop any node_info we dropped below on entry */
+       for (j = darr_ilen(ys->node_infos) - 1; j > i; j--)
+               ys_pop_inner(ys);
+
+       for (; i >= ys->walk_root_level; i--) {
+               if (ys->node_infos[i].has_lookup_next)
+                       break;
+               ys_pop_inner(ys);
+       }
+
+       if (i < ys->walk_root_level)
+               return false;
+
+       ni = &ys->node_infos[i];
+
+       /*
+        * The i'th node has been lost after a yield so trim it from the tree
+        * now.
+        */
+       lyd_free_tree(&ni->inner->node);
+       ni->inner = NULL;
+       ni->list_entry = NULL;
+
+       /*
+        * Leave the empty-of-data node_info on top, __walk will deal with
+        * this, by doing a lookup-next with the keys which we still have.
+        */
+
+       return true;
+}
+
+static void nb_op_resume_data_tree(struct nb_op_yield_state *ys)
+{
+       struct nb_op_node_info *ni;
+       struct nb_node *nn;
+       const void *parent_entry;
+       const void *list_entry;
+       uint i;
+
+       /*
+        * IMPORTANT: On yielding: we always yield during list iteration and
+        * after the initial list element has been created and handled, so the
+        * top of the yield stack will always point at a list node.
+        *
+        * Additionally, that list node has been processed and was in the
+        * process of being "get_next"d when we yielded. We process the
+        * lookup-next list node last so all the rest of the data (to the left)
+        * has been gotten. NOTE: To keep this simple we will require only a
+        * single lookup-next sibling in any parents list of children.
+        *
+        * Walk the rightmost branch (the node info stack) from base to tip
+        * verifying all list nodes are still present. If not we backup to the
+        * node which has a lookup next, and we prune the branch to this node.
+        * If the list node that went away is the topmost we will be using
+        * lookup_next, but if it's a parent then the list_entry will have been
+        * restored.
+        */
+       darr_foreach_i (ys->node_infos, i) {
+               ni = &ys->node_infos[i];
+               nn = ni->schema->priv;
+
+               if (CHECK_FLAG(ni->schema->nodetype, LYS_CONTAINER))
+                       continue;
+
+               assert(ni->list_entry != NULL ||
+                      ni == darr_last(ys->node_infos));
+
+               /* Verify the entry is still present */
+               parent_entry = (i == 0 ? NULL : ni[-1].list_entry);
+               list_entry = nb_callback_lookup_entry(nn, parent_entry,
+                                                     &ni->keys);
+               if (!list_entry || list_entry != ni->list_entry) {
+                       /* May be NULL or a different pointer
+                        * move back to first of
+                        * container with last lookup_next list node
+                        * (which may be this one) and get next.
+                        */
+                       if (!__move_back_to_next(ys, i))
+                               DEBUGD(&nb_dbg_events,
+                                      "%s: Nothing to resume after delete during walk (yield)",
+                                      __func__);
+                       return;
+               }
+       }
+}
+
+/*
+ * Can only yield if all list nodes to root have lookup_next() callbacks
+ *
+ * In order to support lookup_next() the list_node get_next() callback
+ * needs to return ordered (i.e., sorted) results.
+ */
+
+/* ======================= */
+/* Start of walk init code */
+/* ======================= */
+
+/**
+ * __xpath_pop_node() - remove the last node from xpath string
+ * @xpath: an xpath string
+ *
+ * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop.
+ */
+static int __xpath_pop_node(char *xpath)
+{
+       int len = strlen(xpath);
+       bool abs = xpath[0] == '/';
+       char *slash;
+
+       /* "//" or "/" => NULL */
+       if (abs && (len == 1 || (len == 2 && xpath[1] == '/')))
+               return NB_ERR_NOT_FOUND;
+
+       slash = (char *)frrstr_back_to_char(xpath, '/');
+       /* "/foo/bar/" or "/foo/bar//" => "/foo " */
+       if (slash && slash == &xpath[len - 1]) {
+               xpath[--len] = 0;
+               slash = (char *)frrstr_back_to_char(xpath, '/');
+               if (slash && slash == &xpath[len - 1]) {
+                       xpath[--len] = 0;
+                       slash = (char *)frrstr_back_to_char(xpath, '/');
+               }
+       }
+       if (!slash)
+               return NB_ERR_NOT_FOUND;
+       *slash = 0;
+       return NB_OK;
+}
+
+/**
+ * nb_op_xpath_to_trunk() - generate a lyd_node tree (trunk) using an xpath.
+ * @xpath_in: xpath query string to build trunk from.
+ * @dnode: resulting tree (trunk)
+ *
+ * Use the longest prefix of @xpath_in as possible to resolve to a tree (trunk).
+ * This is logically as if we walked along the xpath string resolving each
+ * nodename reference (in particular list nodes) until we could not.
+ *
+ * Return: error if any, if no error then @dnode contains the tree (trunk).
+ */
+static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in,
+                                         struct lyd_node **trunk)
+{
+       char *xpath = NULL;
+       enum nb_error ret = NB_OK;
+       LY_ERR err;
+
+       darr_in_strdup(xpath, xpath_in);
+       for (;;) {
+               err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0,
+                                   LYD_NEW_PATH_UPDATE, NULL, trunk);
+               if (err == LY_SUCCESS)
+                       break;
+
+               ret = __xpath_pop_node(xpath);
+               if (ret != NB_OK)
+                       break;
+       }
+       darr_free(xpath);
+       return ret;
+}
+
+/*
+ * Finish initializing the node info based on the xpath string, and previous
+ * node_infos on the stack. If this node is a list node, obtain the specific
+ * list-entry object.
+ */
+static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys,
+                                                uint index)
+{
+       struct nb_op_node_info *ni = &ys->node_infos[index];
+       struct lyd_node_inner *inner = ni->inner;
+       struct nb_node *nn = ni->schema->priv;
+       bool yield_ok = ys->finish != NULL;
+
+       ni->has_lookup_next = nn->cbs.lookup_next != NULL;
+
+       /* track the last list_entry until updated by new list node */
+       ni->list_entry = index == 0 ? NULL : ni[-1].list_entry;
+
+       /* Assert that we are walking the rightmost branch */
+       assert(!inner->parent || &inner->node == inner->parent->child->prev);
+
+       if (CHECK_FLAG(inner->schema->nodetype, LYS_CONTAINER)) {
+               /* containers have only zero or one child on a branch of a tree */
+               inner = (struct lyd_node_inner *)inner->child;
+               assert(!inner || inner->prev == &inner->node);
+               ni->lookup_next_ok = yield_ok &&
+                                    (index == 0 || ni[-1].lookup_next_ok);
+               return NB_OK;
+       }
+
+       assert(CHECK_FLAG(inner->schema->nodetype, LYS_LIST));
+
+       ni->lookup_next_ok = yield_ok && ni->has_lookup_next &&
+                            (index == 0 || ni[-1].lookup_next_ok);
+
+       nb_op_get_keys(inner, &ni->keys);
+
+       /* A list entry cannot be present in a tree w/o it's keys */
+       assert(ni->keys.num == yang_snode_num_keys(inner->schema));
+
+       /*
+        * Get this nodes opaque list_entry object
+        */
+
+       if (!nn->cbs.lookup_entry) {
+               flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                         "%s: data path doesn't support iteration over operational data: %s",
+                         __func__, ys->xpath);
+               return NB_ERR_NOT_FOUND;
+       }
+
+       /* ni->list_entry starts as the parent entry of this node */
+       ni->list_entry = nb_callback_lookup_entry(nn, ni->list_entry, &ni->keys);
+       if (ni->list_entry == NULL) {
+               flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                         "%s: list entry lookup failed", __func__);
+               return NB_ERR_NOT_FOUND;
+       }
+
+       /*
+        * By definition any list element we can get a specific list_entry for
+        * is specific.
+        */
+       ni->query_specific_entry = true;
+
+       return NB_OK;
+}
+
+/**
+ * nb_op_ys_init_node_infos() - initialize the node info stack from the query.
+ * @ys: the yield state for this tree walk.
+ *
+ * On starting a walk we initialize the node_info stack as deeply as possible
+ * based on specific node references in the query string. We will stop at the
+ * point in the query string that is not specific (e.g., a list element without
+ * it's keys predicate)
+ *
+ * Return: northbound return value (enum nb_error)
+ */
+static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys)
+{
+       struct nb_op_node_info *ni;
+       struct lyd_node_inner *inner;
+       struct lyd_node *node;
+       enum nb_error ret;
+       uint i, len;
+       char *tmp;
+
+       /*
+        * Obtain the trunk of the data node tree of the query.
+        *
+        * These are the nodes from the root that could be specifically
+        * identified with the query string. The trunk ends when a no specific
+        * node could be identified (e.g., a list-node name with no keys).
+        */
+
+       ret = nb_op_xpath_to_trunk(ys->xpath, &node);
+       if (ret || !node) {
+               flog_warn(EC_LIB_LIBYANG,
+                         "%s: can't instantiate concrete path using xpath: %s",
+                         __func__, ys->xpath);
+               if (!ret)
+                       ret = NB_ERR_NOT_FOUND;
+               return ret;
+       }
+       assert(CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST));
+
+       inner = (struct lyd_node_inner *)node;
+       for (len = 1; inner->parent; len++)
+               inner = inner->parent;
+
+
+       darr_append_nz_mt(ys->node_infos, len, MTYPE_NB_NODE_INFOS);
+
+       /*
+        * For each node find the prefix of the xpath query that identified it
+        * -- save the prefix length.
+        */
+       inner = (struct lyd_node_inner *)node;
+       for (i = len; i > 0; i--, inner = inner->parent) {
+               ni = &ys->node_infos[i - 1];
+               ni->inner = inner;
+               ni->schema = inner->schema;
+               /*
+                * NOTE: we could build this by hand with a litte more effort,
+                * but this simple implementation works and won't be expensive
+                * since the number of nodes is small and only done once per
+                * query.
+                */
+               tmp = yang_dnode_get_path(&inner->node, NULL, 0);
+               ni->xpath_len = strlen(tmp);
+
+               /* Replace users supplied xpath with the libyang returned value */
+               if (i == len)
+                       darr_in_strdup(ys->xpath, tmp);
+
+               /* The prefix must match the prefix of the stored xpath */
+               assert(!strncmp(tmp, ys->xpath, ni->xpath_len));
+               free(tmp);
+       }
+
+       /*
+        * Obtain the specific list-entry objects for each list node on the
+        * trunk and finish initializing the node_info structs.
+        */
+
+       darr_foreach_i (ys->node_infos, i) {
+               ret = nb_op_ys_finalize_node_info(ys, i);
+               if (ret != NB_OK) {
+                       darr_free(ys->node_infos);
+                       return ret;
+               }
+       }
+
+       ys->walk_start_level = darr_len(ys->node_infos);
+
+       ys->walk_root_level = (int)ys->walk_start_level - 1;
+
+       return NB_OK;
+}
+
+/* ================ */
+/* End of init code */
+/* ================ */
+
+/**
+ * nb_op_add_leaf() - Add leaf data to the get tree results
+ * @ys - the yield state for this tree walk.
+ * @nb_node - the northbound node representing this leaf.
+ * @xpath - the xpath (with key predicates) to this leaf value.
+ *
+ * Return: northbound return value (enum nb_error)
+ */
+static enum nb_error nb_op_iter_leaf(struct nb_op_yield_state *ys,
+                                    const struct nb_node *nb_node,
+                                    const char *xpath)
+{
+       const struct lysc_node *snode = nb_node->snode;
+       struct nb_op_node_info *ni = darr_last(ys->node_infos);
+       struct yang_data *data;
+       enum nb_error ret = NB_OK;
+       LY_ERR err;
+
+       if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       /* Ignore list keys. */
+       if (lysc_is_key(snode))
+               return NB_OK;
+
+       data = nb_callback_get_elem(nb_node, xpath, ni->list_entry);
+       if (data == NULL)
+               return NB_OK;
+
+       /* Add a dnode to our tree */
+       err = lyd_new_term(&ni->inner->node, snode->module, snode->name,
+                          data->value, false, NULL);
+       if (err) {
+               yang_data_free(data);
+               return NB_ERR_RESOURCE;
+       }
+
+       if (ys->cb)
+               ret = (*ys->cb)(nb_node->snode, ys->translator, data,
+                               ys->cb_arg);
+       yang_data_free(data);
+
+       return ret;
+}
+
+static enum nb_error nb_op_iter_leaflist(struct nb_op_yield_state *ys,
+                                        const struct nb_node *nb_node,
+                                        const char *xpath)
+{
+       const struct lysc_node *snode = nb_node->snode;
+       struct nb_op_node_info *ni = darr_last(ys->node_infos);
+       const void *list_entry = NULL;
+       enum nb_error ret = NB_OK;
+       LY_ERR err;
+
+       if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       do {
+               struct yang_data *data;
+
+               list_entry = nb_callback_get_next(nb_node, ni->list_entry,
+                                                 list_entry);
+               if (!list_entry)
+                       /* End of the list. */
+                       break;
+
+               data = nb_callback_get_elem(nb_node, xpath, list_entry);
+               if (data == NULL)
+                       continue;
+
+               /* Add a dnode to our tree */
+               err = lyd_new_term(&ni->inner->node, snode->module, snode->name,
+                                  data->value, false, NULL);
+               if (err) {
+                       yang_data_free(data);
+                       return NB_ERR_RESOURCE;
+               }
+
+               if (ys->cb)
+                       ret = (*ys->cb)(nb_node->snode, ys->translator, data,
+                                       ys->cb_arg);
+               yang_data_free(data);
+       } while (ret == NB_OK && list_entry);
+
+       return ret;
+}
+
+
+static bool nb_op_schema_path_has_predicate(struct nb_op_yield_state *ys,
+                                           int level)
+{
+       if (level > darr_lasti(ys->query_tokens))
+               return false;
+       return strchr(ys->query_tokens[level], '[') != NULL;
+}
+
+/**
+ * nb_op_empty_container_ok() - determine if should keep empty container node.
+ *
+ * Return: true if the empty container should be kept.
+ */
+static bool nb_op_empty_container_ok(const struct lysc_node *snode,
+                                    const char *xpath, const void *list_entry)
+{
+       struct nb_node *nn = snode->priv;
+       struct yang_data *data;
+
+       if (!CHECK_FLAG(snode->flags, LYS_PRESENCE))
+               return false;
+
+       if (!nn->cbs.get_elem)
+               return false;
+
+       data = nb_callback_get_elem(nn, xpath, list_entry);
+       if (data) {
+               yang_data_free(data);
+               return true;
+       }
+       return false;
+}
+
+/**
+ * nb_op_get_child_path() - add child node name to the xpath.
+ * @xpath_parent - a darr string for the parent node.
+ * @schild - the child schema node.
+ * @xpath_child - a previous return value from this function to reuse.
+ */
+static char *nb_op_get_child_path(const char *xpath_parent,
+                                 const struct lysc_node *schild,
+                                 char *xpath_child)
+{
+       /* "/childname" */
+       uint space, extra = strlen(schild->name) + 1;
+       bool new_mod = (!schild->parent ||
+                       schild->parent->module != schild->module);
+       int n;
+
+       if (new_mod)
+               /* "modulename:" */
+               extra += strlen(schild->module->name) + 1;
+       space = darr_len(xpath_parent) + extra;
+
+       if (xpath_parent == xpath_child)
+               darr_ensure_cap(xpath_child, space);
+       else
+               darr_in_strdup_cap(xpath_child, xpath_parent, space);
+       if (new_mod)
+               n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s:%s",
+                            schild->module->name, schild->name);
+       else
+               n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s",
+                            schild->name);
+       assert(n == (int)extra);
+       _darr_len(xpath_child) += extra;
+       return xpath_child;
+}
+
+static bool __is_yielding_node(const struct lysc_node *snode)
+{
+       struct nb_node *nn = snode->priv;
+
+       return nn->cbs.lookup_next != NULL;
+}
+
+static const struct lysc_node *__sib_next(bool yn, const struct lysc_node *sib)
+{
+       for (; sib; sib = sib->next)
+               if (yn == __is_yielding_node(sib))
+                       return sib;
+       return NULL;
+}
+
+/**
+ * nb_op_sib_next() - Return the next sibling to walk to
+ * @ys: the yield state for this tree walk.
+ * @sib: the currently being visited sibling
+ *
+ * Return: the next sibling to walk to, walking non-yielding before yielding.
+ */
+static const struct lysc_node *nb_op_sib_next(struct nb_op_yield_state *ys,
+                                             const struct lysc_node *sib)
+{
+       struct lysc_node *parent = sib->parent;
+       bool yn = __is_yielding_node(sib);
+
+       /*
+        * If the node info stack is shorter than the schema path then we are
+        * doign specific query still on the node from the schema path (should
+        * match) so just return NULL.
+        */
+       if (darr_len(ys->schema_path) > darr_len(ys->node_infos))
+               return NULL;
+       /*
+        * If sib is on top of the node info stack then
+        * 1) it's a container node -or-
+        * 2) it's a list node that we were walking and we've reach the last entry
+        * 3) if sib is a list and the list was empty we never would have
+        * pushed sib on the stack so the top of the stack is the parent
+        *
+        * If the query string included this node then we do not process any
+        * siblings as we are not walking all the parent's children just this
+        * specified one give by the query string.
+        */
+       if (sib == darr_last(ys->node_infos)->schema &&
+           darr_len(ys->schema_path) >= darr_len(ys->node_infos))
+               return NULL;
+       /* case (3) */
+       else if (sib->nodetype == LYS_LIST &&
+                parent == darr_last(ys->node_infos)->schema &&
+                darr_len(ys->schema_path) > darr_len(ys->node_infos))
+               return NULL;
+
+       sib = __sib_next(yn, sib->next);
+       if (sib)
+               return sib;
+       if (yn)
+               return NULL;
+       return __sib_next(true, lysc_node_child(parent));
+}
+/*
+ * sib_walk((struct lyd_node *)ni->inner->node.parent->parent->parent->parent->parent->parent->parent)
+ */
+
+/**
+ * nb_op_sib_first() - obtain the first child to walk to
+ * @ys: the yield state for this tree walk.
+ * @parent: the parent whose child we seek
+ * @skip_keys: if should skip over keys
+ *
+ * Return: the first child to continue the walk to, starting with non-yielding
+ * siblings then yielding ones. There should be no more than 1 yielding sibling.
+ */
+static const struct lysc_node *nb_op_sib_first(struct nb_op_yield_state *ys,
+                                              const struct lysc_node *parent,
+                                              bool skip_keys)
+{
+       const struct lysc_node *sib = lysc_node_child(parent);
+       const struct lysc_node *first_sib;
+
+       /*
+        * The top of the node stack points at @parent.
+        *
+        * If the schema path (original query) is longer than our current node
+        * info stack (current xpath location), we are building back up to the
+        * base of the user query, return the next schema node from the query
+        * string (schema_path).
+        */
+       assert(darr_last(ys->node_infos)->schema == parent);
+       if (darr_lasti(ys->node_infos) < ys->query_base_level)
+               return ys->schema_path[darr_lasti(ys->node_infos) + 1];
+
+       if (skip_keys)
+               while (sib && lysc_is_key(sib))
+                       sib = sib->next;
+       if (!sib)
+               return NULL;
+
+       /* Return non-yielding node's first */
+       first_sib = sib;
+       if (__is_yielding_node(sib)) {
+               sib = __sib_next(false, sib);
+               if (sib)
+                       return sib;
+       }
+       return first_sib;
+}
+
+/*
+ * "3-dimensional" walk from base of the tree to the tip in-order.
+ *
+ * The actual tree is only 2-dimensional as list nodes are organized as adjacent
+ * siblings under a common parent perhaps with other siblings to each side;
+ * however, using 3d view here is easier to diagram.
+ *
+ * - A list node is yielding if it has a lookup_next callback.
+ * - All other node types are not yielding.
+ * - There's only one yielding node in a list of children (i.e., siblings).
+ *
+ * We visit all non-yielding children prior to the yielding child.
+ * That way we have the fullest tree possible even when something is deleted
+ * during a yield.
+ *                             --- child/parent descendant poinilnters
+ *                             ... next/prev sibling pointers
+ *                             o.o list entries pointers
+ *                             ~~~ diagram extension connector
+ *          1
+ *         / \
+ *        /   \         o~~~~12
+ *       /     \      .      / \
+ *      2.......5   o~~~9  13...14
+ *     / \      | .    / \
+ *    3...4     6    10...11      Cont Nodes: 1,2,5
+ *             / \                List Nodes: 6,9,12
+ *            7...8               Leaf Nodes: 3,4,7,8,10,11,13,14
+ *                             Schema Leaf A: 3
+ *                             Schema Leaf B: 4
+ *                             Schema Leaf C: 7,10,13
+ *                             Schema Leaf D: 8,11,14
+ */
+static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume)
+{
+       const struct lysc_node *walk_stem_tip = ys_get_walk_stem_tip(ys);
+       const struct lysc_node *sib;
+       const void *parent_list_entry = NULL;
+       const void *list_entry = NULL;
+       struct nb_op_node_info *ni, *pni;
+       struct lyd_node *node;
+       struct nb_node *nn;
+       char *xpath_child = NULL;
+       // bool at_query_base;
+       bool at_root_level, list_start, is_specific_node;
+       enum nb_error ret = NB_OK;
+       LY_ERR err;
+       int at_clevel;
+       uint len;
+
+
+       monotime(&ys->start_time);
+
+       /* Don't currently support walking all root nodes */
+       if (!walk_stem_tip)
+               return NB_ERR_NOT_FOUND;
+
+       /*
+        * If we are resuming then start with the list container on top.
+        * Otherwise get the first child of the container we are walking,
+        * starting with non-yielding children.
+        */
+       if (is_resume)
+               sib = darr_last(ys->node_infos)->schema;
+       else {
+               /*
+                * Start with non-yielding children first.
+                *
+                * When adding root level walks, the sibling list are the root
+                * level nodes of all modules
+                */
+               sib = nb_op_sib_first(ys, walk_stem_tip, true);
+               if (!sib)
+                       return NB_ERR_NOT_FOUND;
+       }
+
+
+       while (true) {
+               /* Grab the top container/list node info on the stack */
+               at_clevel = darr_lasti(ys->node_infos);
+               ni = &ys->node_infos[at_clevel];
+
+               /*
+                * This is the level of the last specific node at init
+                * time. +1 would be the first non-specific list or
+                * non-container if present in the container node.
+                */
+               at_root_level = at_clevel == ys->walk_root_level;
+
+               if (!sib) {
+                       /*
+                        * We've reached the end of the siblings inside a
+                        * containing node; either a container or a specific
+                        * list node entry.
+                        *
+                        * We handle container node inline; however, for lists
+                        * we are only done with a specific entry and need to
+                        * move to the next element on the list so we drop down
+                        * into the switch for that case.
+                        */
+
+                       /* Grab the containing node. */
+                       sib = ni->schema;
+
+                       if (sib->nodetype == LYS_CONTAINER) {
+                               /* If we added an empty container node (no
+                                * children) and it's not a presence container
+                                * or it's not backed by the get_elem callback,
+                                * remove the node from the tree.
+                                */
+                               if (!lyd_child(&ni->inner->node) &&
+                                   !nb_op_empty_container_ok(sib, ys->xpath,
+                                                             ni->list_entry))
+                                       lyd_free_tree(&ni->inner->node);
+
+                               /* If we have returned to our original walk base,
+                                * then we are done with the walk.
+                                */
+                               if (at_root_level) {
+                                       ret = NB_OK;
+                                       goto done;
+                               }
+                               /*
+                                * Grab the sibling of the container we are
+                                * about to pop, so we will be mid-walk on the
+                                * parent containers children.
+                                */
+                               sib = nb_op_sib_next(ys, sib);
+
+                               /* Pop container node to the parent container */
+                               ys_pop_inner(ys);
+
+                               /*
+                                * If are were working on a user narrowed path
+                                * then we are done with these siblings.
+                                */
+                               if (darr_len(ys->schema_path) >
+                                   darr_len(ys->node_infos))
+                                       sib = NULL;
+
+                               /* Start over */
+                               continue;
+                       }
+                       /*
+                        * If we are here we have reached the end of the
+                        * children of a list entry node. sib points
+                        * at the list node info.
+                        */
+               }
+
+               /* TODO: old code checked for "first" here and skipped if set */
+               if (CHECK_FLAG(sib->nodetype,
+                              LYS_LEAF | LYS_LEAFLIST | LYS_CONTAINER))
+                       xpath_child = nb_op_get_child_path(ys->xpath, sib,
+                                                          xpath_child);
+               nn = sib->priv;
+
+               switch (sib->nodetype) {
+               case LYS_LEAF:
+                       /*
+                        * If we have a non-specific walk to a specific leaf
+                        * (e.g., "..../route-entry/metric") and the leaf value
+                        * is not present, then we are left with the data nodes
+                        * of the stem of the branch to the missing leaf data.
+                        * For containers this will get cleaned up by the
+                        * container code above that looks for no children;
+                        * however, this doesn't work for lists.
+                        *
+                        * (FN:A) We need a similar check for empty list
+                        * elements. Empty list elements below the
+                        * query_base_level (i.e., the schema path length)
+                        * should be cleaned up as they don't support anything
+                        * the user is querying for, if they are above the
+                        * query_base_level then they are part of the walk and
+                        * should be kept.
+                        */
+                       ret = nb_op_iter_leaf(ys, nn, xpath_child);
+                       sib = nb_op_sib_next(ys, sib);
+                       continue;
+               case LYS_LEAFLIST:
+                       ret = nb_op_iter_leaflist(ys, nn, xpath_child);
+                       sib = nb_op_sib_next(ys, sib);
+                       continue;
+               case LYS_CONTAINER:
+                       if (CHECK_FLAG(nn->flags, F_NB_NODE_CONFIG_ONLY)) {
+                               sib = nb_op_sib_next(ys, sib);
+                               continue;
+                       }
+
+                       node = NULL;
+                       err = lyd_new_inner(&ni->inner->node, sib->module,
+                                           sib->name, false, &node);
+                       if (err) {
+                               ret = NB_ERR_RESOURCE;
+                               goto done;
+                       }
+
+                       /* push this container node on top of the stack */
+                       ni = darr_appendz(ys->node_infos);
+                       ni->inner = (struct lyd_node_inner *)node;
+                       ni->schema = node->schema;
+                       ni->niters = 0;
+                       ni->nents = 0;
+                       ni->has_lookup_next = false;
+                       ni->lookup_next_ok = ni[-1].lookup_next_ok;
+                       ni->list_entry = ni[-1].list_entry;
+
+                       darr_in_strdup(ys->xpath, xpath_child);
+                       ni->xpath_len = darr_strlen(ys->xpath);
+
+                       sib = nb_op_sib_first(ys, sib, false);
+                       continue;
+               case LYS_LIST:
+
+                       /*
+                        * Notes:
+                        *
+                        * NOTE: ni->inner may be NULL here if we resumed and it
+                        * was gone. ni->schema and ni->keys will still be
+                        * valid.
+                        *
+                        * NOTE: At this point sib is never NULL; however, if it
+                        * was NULL at the top of the loop, then we were done
+                        * working on a list element's children and will be
+                        * attempting to get the next list element here so sib
+                        * == ni->schema (i.e., !list_start).
+                        *
+                        * (FN:A): Before doing this let's remove empty list
+                        * elements that are "inside" the query string as they
+                        * represent a stem which didn't lead to actual data
+                        * being requested by the user -- for example,
+                        * ".../route-entry/metric" if metric is not present we
+                        * don't want to return an empty route-entry to the
+                        * user.
+                        */
+
+                       node = NULL;
+                       list_start = ni->schema != sib;
+                       if (list_start) {
+                               /*
+                                * List iteration: First Element
+                                * -----------------------------
+                                *
+                                * Our node info wasn't on top (wasn't an entry
+                                * for sib) so this is a new list iteration, we
+                                * will push our node info below. The top is our
+                                * parent.
+                                */
+                               if (CHECK_FLAG(nn->flags,
+                                              F_NB_NODE_CONFIG_ONLY)) {
+                                       sib = nb_op_sib_next(ys, sib);
+                                       continue;
+                               }
+                               /* we are now at one level higher */
+                               at_clevel += 1;
+                               pni = ni;
+                               ni = NULL;
+                       } else {
+                               /*
+                                * List iteration: Next Element
+                                * ----------------------------
+                                *
+                                * This is the case where `sib == NULL` at the
+                                * top of the loop, so, we just completed the
+                                * walking the children of a list entry, i.e.,
+                                * we are done with that list entry.
+                                *
+                                * `sib` was reset to point at the our list node
+                                * at the top of node_infos.
+                                *
+                                * Within this node_info, `ys->xpath`, `inner`,
+                                * `list_entry`, and `xpath_len` are for the
+                                * previous list entry, and need to be updated.
+                                */
+                               pni = darr_len(ys->node_infos) > 1 ? &ni[-1]
+                                                                  : NULL;
+                       }
+
+                       parent_list_entry = pni ? pni->list_entry : NULL;
+                       list_entry = ni ? ni->list_entry : NULL;
+
+                       /*
+                        * Before yielding we check to see if we are doing a
+                        * specific list entry instead of a full list iteration.
+                        * We do not want to yield during specific list entry
+                        * processing.
+                        */
+
+                       /*
+                        * If we are at a list start check to see if the node
+                        * has a predicate. If so we will try and fetch the data
+                        * node now that we've built part of the tree, if the
+                        * predicates are keys or only depend on the tree already
+                        * built, it should create the element for us.
+                        */
+                       is_specific_node = false;
+                       if (list_start &&
+                           at_clevel <= darr_lasti(ys->query_tokens) &&
+                           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 {
+                                       flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                                                 "%s: unable to create node for specific query string: %s",
+                                                 __func__,
+                                                 ys->query_tokens[at_clevel]);
+                               }
+                       }
+
+                       if (list_entry && ni->query_specific_entry) {
+                               /*
+                                * Ending specific list entry processing.
+                                */
+                               assert(!list_start);
+                               is_specific_node = true;
+                               list_entry = NULL;
+                       }
+
+                       /*
+                        * Should we yield?
+                        *
+                        * Don't yield if we have a specific entry.
+                        */
+                       if (!is_specific_node && ni && ni->lookup_next_ok &&
+                           // make sure we advance, if the interval is
+                           // fast and we are very slow.
+                           ((monotime_since(&ys->start_time, NULL) >
+                                     NB_OP_WALK_INTERVAL_US &&
+                             ni->niters) ||
+                            (ni->niters + 1) % 10000 == 0)) {
+                               /* This is a yield supporting list node and
+                                * we've been running at least our yield
+                                * interval, so yield.
+                                *
+                                * NOTE: we never yield on list_start, and we
+                                * are always about to be doing a get_next.
+                                */
+                               DEBUGD(&nb_dbg_events,
+                                      "%s: yielding after %u iterations",
+                                      __func__, ni->niters);
+
+                               ni->niters = 0;
+                               ret = NB_YIELD;
+                               goto done;
+                       }
+
+                       /*
+                        * Now get the backend list_entry opaque object for
+                        * this list entry from the backend.
+                        */
+
+                       if (is_specific_node) {
+                               /*
+                                * Specific List Entry:
+                                * --------------------
+                                */
+                               if (list_start) {
+                                       list_entry =
+                                               nb_callback_lookup_node_entry(
+                                                       node, parent_list_entry);
+                                       /*
+                                        * If the node we created from a
+                                        * specific predicate entry is not
+                                        * actually there we need to delete the
+                                        * node from our data tree
+                                        */
+                                       if (!list_entry) {
+                                               lyd_free_tree(node);
+                                               node = NULL;
+                                       }
+                               }
+                       } else if (!list_start && !list_entry &&
+                                  ni->has_lookup_next) {
+                               /*
+                                * After Yield:
+                                * ------------
+                                * After a yield the list_entry may have become
+                                * invalid, so use lookup_next callback with
+                                * parent and keys instead to find next element.
+                                */
+                               list_entry =
+                                       nb_callback_lookup_next(nn,
+                                                               parent_list_entry,
+                                                               &ni->keys);
+                       } else {
+                               /*
+                                * Normal List Iteration:
+                                * ----------------------
+                                * Start (list_entry == NULL) or continue
+                                * (list_entry != NULL) the list iteration.
+                                */
+                               /* Obtain [next] list entry. */
+                               list_entry =
+                                       nb_callback_get_next(nn,
+                                                            parent_list_entry,
+                                                            list_entry);
+                       }
+
+                       /*
+                        * (FN:A) Reap empty list element? Check to see if we
+                        * should reap an empty list element. We do this if the
+                        * empty list element exists at or below the query base
+                        * (i.e., it's not part of the walk, but a failed find
+                        * on a more specific query e.g., for below the
+                        * `route-entry` element for a query
+                        * `.../route-entry/metric` where the list element had
+                        * no metric value.
+                        */
+                       if (!list_start && ni->inner &&
+                           !lyd_child_no_keys(&ni->inner->node) &&
+                           /* is this at or below the base? */
+                           darr_ilen(ys->node_infos) <= ys->query_base_level)
+                               lyd_free_tree(&ni->inner->node);
+
+
+                       if (!list_entry) {
+                               /*
+                                * List Iteration Done
+                                * -------------------
+                                */
+
+                               /*
+                                * Grab next sibling of the list node
+                                */
+                               if (is_specific_node)
+                                       sib = NULL;
+                               else
+                                       sib = nb_op_sib_next(ys, sib);
+
+                               /*
+                                * If we are at the walk root (base) level then
+                                * that specifies a list and we are done iterating
+                                * the list, so we are done with the walk entirely.
+                                */
+                               if (!sib && at_clevel == ys->walk_root_level) {
+                                       ret = NB_OK;
+                                       goto done;
+                               }
+
+                               /*
+                                * Pop the our list node info back to our
+                                * parent.
+                                *
+                                * We only do this if we've already pushed a
+                                * node for the current list schema. For
+                                * `list_start` this hasn't happened yet, as
+                                * would have happened below. So when list_start
+                                * is true but list_entry if NULL we
+                                * are processing an empty list.
+                                */
+                               if (!list_start)
+                                       ys_pop_inner(ys);
+
+                               /*
+                                * We should never be below the walk root
+                                */
+                               assert(darr_lasti(ys->node_infos) >=
+                                      ys->walk_root_level);
+
+                               /* Move on to the sibling of the list node */
+                               continue;
+                       }
+
+                       /*
+                        * From here on, we have selected a new top node_info
+                        * list entry (either newly pushed or replacing the
+                        * previous entry in the walk), and we are filling in
+                        * the details.
+                        */
+
+                       if (list_start) {
+                               /*
+                                * Starting iteration of a list type or
+                                * processing a specific entry, push the list
+                                * node_info on stack.
+                                */
+                               ni = darr_appendz(ys->node_infos);
+                               pni = &ni[-1]; /* memory may have moved */
+                               ni->has_lookup_next = nn->cbs.lookup_next !=
+                                                     NULL;
+                               ni->lookup_next_ok = ((!pni && ys->finish) ||
+                                                     pni->lookup_next_ok) &&
+                                                    ni->has_lookup_next;
+                               ni->query_specific_entry = is_specific_node;
+                               ni->niters = 0;
+                               ni->nents = 0;
+
+                               /* this will be our predicate-less xpath */
+                               ys->xpath = nb_op_get_child_path(ys->xpath, sib,
+                                                                ys->xpath);
+                       } else {
+                               /*
+                                * Reset our xpath to the list node (i.e.,
+                                * remove the entry predicates)
+                                */
+                               if (ni->query_specific_entry) {
+                                       flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                                                 "%s: unexpected state",
+                                                 __func__);
+                               }
+                               assert(!ni->query_specific_entry);
+                               len = strlen(sib->name) + 1; /* "/sibname" */
+                               if (pni)
+                                       len += pni->xpath_len;
+                               darr_setlen(ys->xpath, len + 1);
+                               ys->xpath[len] = 0;
+                               ni->xpath_len = len;
+                       }
+
+                       /* Need to get keys. */
+
+                       if (!CHECK_FLAG(nn->flags, F_NB_NODE_KEYLESS_LIST)) {
+                               ret = nb_callback_get_keys(nn, list_entry,
+                                                          &ni->keys);
+                               if (ret) {
+                                       darr_pop(ys->node_infos);
+                                       ret = NB_ERR_RESOURCE;
+                                       goto done;
+                               }
+                       }
+                       /*
+                        * Append predicates to xpath.
+                        */
+                       len = darr_strlen(ys->xpath);
+                       if (ni->keys.num) {
+                               yang_get_key_preds(ys->xpath + len, sib,
+                                                  &ni->keys,
+                                                  darr_cap(ys->xpath) - len);
+                       } else {
+                               /* add a position predicate (1s based?) */
+                               darr_ensure_avail(ys->xpath, 10);
+                               snprintf(ys->xpath + len,
+                                        darr_cap(ys->xpath) - len + 1, "[%u]",
+                                        ni->nents + 1);
+                       }
+                       darr_setlen(ys->xpath,
+                                   strlen(ys->xpath + len) + len + 1);
+                       ni->xpath_len = darr_strlen(ys->xpath);
+
+                       /*
+                        * Create the new list entry node.
+                        */
+
+                       if (!node) {
+                               /* NOTE: can also use lyd_new_list2 here when available */
+                               err = yang_lyd_new_list(ni[-1].inner, sib,
+                                                       &ni->keys,
+                                                       (struct lyd_node_inner *
+                                                                *)&node);
+                               if (err) {
+                                       darr_pop(ys->node_infos);
+                                       ret = NB_ERR_RESOURCE;
+                                       goto done;
+                               }
+                       }
+
+                       /*
+                        * Save the new list entry with the list node info
+                        */
+                       ni->inner = (struct lyd_node_inner *)node;
+                       ni->schema = node->schema;
+                       ni->list_entry = list_entry;
+                       ni->niters += 1;
+                       ni->nents += 1;
+
+                       /* Skip over the key children, they've been created. */
+                       sib = nb_op_sib_first(ys, sib, true);
+                       continue;
+
+               case LYS_CHOICE:
+                       /* Container type with no data */
+                       /*FALLTHROUGH*/
+               case LYS_CASE:
+                       /* Container type with no data */
+                       /*FALLTHROUGH*/
+               default:
+                       /*FALLTHROUGH*/
+               case LYS_ANYXML:
+               case LYS_ANYDATA:
+                       /* These schema types are not currently handled */
+                       flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                                 "%s: unsupported schema node type: %s",
+                                 __func__, lys_nodetype2str(sib->nodetype));
+                       sib = nb_op_sib_next(ys, sib);
+                       continue;
+               }
+       }
+
+done:
+       darr_free(xpath_child);
+       return ret;
+}
+
+static void nb_op_walk_continue(struct event *thread)
+{
+       struct nb_op_yield_state *ys = EVENT_ARG(thread);
+       enum nb_error ret = NB_OK;
+
+       DEBUGD(&nb_dbg_cbs_state, "northbound oper-state: resuming %s",
+              ys->xpath);
+
+       nb_op_resume_data_tree(ys);
+
+       /* if we've popped past the walk start level we're done */
+       if (darr_lasti(ys->node_infos) < ys->walk_root_level)
+               goto finish;
+
+       /* otherwise we are at a resumable node */
+       assert(darr_last(ys->node_infos)->has_lookup_next);
+
+       ret = __walk(ys, true);
+       if (ret == NB_YIELD) {
+               if (nb_op_yield(ys) != NB_OK) {
+                       if (ys->should_batch)
+                               goto stopped;
+                       else
+                               goto finish;
+               }
+               return;
+       }
+finish:
+       (*ys->finish)(ys_root_node(ys), ys->finish_arg, ret);
+stopped:
+       nb_op_free_yield_state(ys, false);
+}
+
+static void __free_siblings(struct lyd_node *this)
+{
+       struct lyd_node *next, *sib;
+       uint count = 0;
+
+       LY_LIST_FOR_SAFE(lyd_first_sibling(this), next, sib)
+       {
+               if (lysc_is_key(sib->schema))
+                       continue;
+               if (sib == this)
+                       continue;
+               lyd_free_tree(sib);
+               count++;
+       }
+       DEBUGD(&nb_dbg_events, "NB oper-state: deleted %u siblings", count);
+}
+
+/*
+ * Trim Algorithm:
+ *
+ * Delete final lookup-next list node and subtree, leave stack slot with keys.
+ *
+ * Then walking up the stack, delete all siblings except:
+ * 1. right-most container or list node (must be lookup-next by design)
+ * 2. keys supporting existing parent list node.
+ *
+ * NOTE the topmost node on the stack will be the final lookup-nexxt list node,
+ * as we only yield on lookup-next list nodes.
+ *
+ */
+static void nb_op_trim_yield_state(struct nb_op_yield_state *ys)
+{
+       struct nb_op_node_info *ni;
+       int i = darr_lasti(ys->node_infos);
+
+       assert(i >= 0);
+
+       DEBUGD(&nb_dbg_events, "NB oper-state: start trimming: top: %d", i);
+
+       ni = &ys->node_infos[i];
+       assert(ni->has_lookup_next);
+
+       DEBUGD(&nb_dbg_events, "NB oper-state: deleting tree at level %d", i);
+       __free_siblings(&ni->inner->node);
+       lyd_free_tree(&ni->inner->node);
+       ni->inner = NULL;
+
+       while (--i > 0) {
+               DEBUGD(&nb_dbg_events,
+                      "NB oper-state: deleting siblings at level: %d", i);
+               __free_siblings(&ys->node_infos[i].inner->node);
+       }
+       DEBUGD(&nb_dbg_events, "NB oper-state: stop trimming: new top: %d",
+              (int)darr_lasti(ys->node_infos));
+}
+
+static enum nb_error nb_op_yield(struct nb_op_yield_state *ys)
+{
+       enum nb_error ret;
+       unsigned long min_us = MAX(1, NB_OP_WALK_INTERVAL_US / 50000);
+       struct timeval tv = { .tv_sec = 0, .tv_usec = min_us };
+
+       DEBUGD(&nb_dbg_events, "NB oper-state: yielding %s for %lus (should_batch %d)",
+              ys->xpath, tv.tv_usec, ys->should_batch);
+
+       if (ys->should_batch) {
+               /*
+                * TODO: add ability of finish to influence the timer.
+                * This will allow, for example, flow control based on how long
+                * it takes finish to process the batch.
+                */
+               ret = (*ys->finish)(ys_root_node(ys), ys->finish_arg, NB_YIELD);
+               if (ret != NB_OK)
+                       return ret;
+               /* now trim out that data we just "finished" */
+               nb_op_trim_yield_state(ys);
+
+       }
+
+       event_add_timer_tv(event_loop, nb_op_walk_continue, ys, &tv,
+                          &ys->walk_ev);
+       return NB_OK;
+}
+
+static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys,
+                                              struct nb_node **last)
+{
+       const struct lysc_node *sn;
+       struct nb_node *nblast;
+       char *s, *s2;
+       int count;
+       uint i;
+
+       /*
+        * Get the schema node stack for the entire query string
+        *
+        * The user might pass in something like "//metric" which may resolve to
+        * more than one schema node ("trunks"). nb_node_find() returns a single
+        * node though. We should expand the functionality to get the set of
+        * nodes that matches the xpath (not path) query and save that set in
+        * the yield state. Then we should do a walk using the users query
+        * string over each schema trunk in the set.
+        */
+       nblast = nb_node_find(ys->xpath);
+       if (!nblast) {
+               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                         "%s: unknown data path: %s", __func__, ys->xpath);
+               return NB_ERR;
+       }
+       *last = nblast;
+
+       /*
+        * Create a stack of schema nodes one element per node in the query
+        * path, only the top (last) element may be a non-container type.
+        *
+        * NOTE: appears to be a bug in nb_node linkage where parent can be NULL,
+        * or I'm misunderstanding the code, in any case we use the libyang
+        * linkage to walk which works fine.
+        *
+        * XXX: we don't actually support choice/case yet, they are container
+        * types in the libyang schema, but won't be in data so our length
+        * checking gets messed up.
+        */
+       for (sn = nblast->snode, count = 0; sn; count++, sn = sn->parent)
+               if (sn != nblast->snode)
+                       assert(CHECK_FLAG(sn->nodetype,
+                                         LYS_CONTAINER | LYS_LIST |
+                                                 LYS_CHOICE | LYS_CASE));
+       /* create our arrays */
+       darr_append_n(ys->schema_path, count);
+       darr_append_n(ys->query_tokens, count);
+       for (sn = nblast->snode; sn; sn = sn->parent)
+               ys->schema_path[--count] = sn;
+
+       /*
+        * Now tokenize the query string and get pointers to each token
+        */
+
+       /* Get copy of query string start after initial '/'s */
+       s = ys->xpath;
+       while (*s && *s == '/')
+               s++;
+       ys->query_tokstr = darr_strdup(s);
+       s = ys->query_tokstr;
+
+       darr_foreach_i (ys->schema_path, i) {
+               const char *modname = ys->schema_path[i]->module->name;
+               const char *name = ys->schema_path[i]->name;
+               int nlen = strlen(name);
+               int mnlen = 0;
+
+               while (true) {
+                       s2 = strstr(s, name);
+                       if (!s2)
+                               goto error;
+
+                       if (s2[-1] == ':') {
+                               mnlen = strlen(modname) + 1;
+                               if (ys->query_tokstr > s2 - mnlen ||
+                                   strncmp(s2 - mnlen, modname, mnlen - 1))
+                                       goto error;
+                               s2 -= mnlen;
+                               nlen += mnlen;
+                       }
+
+                       s = s2;
+                       if ((i == 0 || s[-1] == '/') &&
+                           (s[nlen] == 0 || s[nlen] == '[' || s[nlen] == '/'))
+                               break;
+                       /*
+                        * Advance past the incorrect match, must have been
+                        * part of previous predicate.
+                        */
+                       s += nlen;
+               }
+
+               /* NUL terminate previous token and save this one */
+               if (i > 0)
+                       s[-1] = 0;
+               ys->query_tokens[i] = s;
+               s += nlen;
+       }
+
+       /* NOTE: need to subtract choice/case nodes when these are supported */
+       ys->query_base_level = darr_lasti(ys->schema_path);
+
+       return NB_OK;
+
+error:
+       darr_free(ys->query_tokstr);
+       darr_free(ys->schema_path);
+       darr_free(ys->query_tokens);
+       return NB_ERR;
+}
+
+
+/**
+ * nb_op_walk_start() - Start walking oper-state directed by query string.
+ * @ys: partially initialized yield state for this walk.
+ *
+ */
+static enum nb_error nb_op_walk_start(struct nb_op_yield_state *ys)
+{
+       struct nb_node *nblast;
+       enum nb_error ret;
+
+       /*
+        * Get nb_node path (stack) corresponding to the xpath query
+        */
+       ret = nb_op_ys_init_schema_path(ys, &nblast);
+       if (ret != NB_OK)
+               return ret;
+
+
+       /*
+        * Get the node_info path (stack) corresponding to the uniquely
+        * resolvable data nodes from the beginning of the xpath query.
+        */
+       // I think this moves
+       ret = nb_op_ys_init_node_infos(ys);
+       if (ret != NB_OK)
+               return ret;
+
+       return __walk(ys, false);
+}
+
+
+void *nb_oper_walk(const char *xpath, struct yang_translator *translator,
+                  uint32_t flags, bool should_batch, nb_oper_data_cb cb,
+                  void *cb_arg, nb_oper_data_finish_cb finish, void *finish_arg)
+{
+       struct nb_op_yield_state *ys;
+       enum nb_error ret;
+
+       ys = nb_op_create_yield_state(xpath, translator, flags, should_batch,
+                                     cb, cb_arg, finish, finish_arg);
+
+       ret = nb_op_walk_start(ys);
+       if (ret == NB_YIELD) {
+               if (nb_op_yield(ys) != NB_OK) {
+                       if (ys->should_batch)
+                               goto stopped;
+                       else
+                               goto finish;
+               }
+               return ys;
+       }
+finish:
+       (void)(*ys->finish)(ys_root_node(ys), ys->finish_arg, ret);
+stopped:
+       nb_op_free_yield_state(ys, false);
+       return NULL;
+}
+
+
+void nb_oper_cancel_walk(void *walk)
+{
+       if (walk)
+               nb_op_free_yield_state(walk, false);
+}
+
+
+void nb_oper_cancel_all_walks(void)
+{
+       struct nb_op_yield_state *ys;
+
+       frr_each_safe (nb_op_walks, &nb_op_walks, ys)
+               nb_oper_cancel_walk(ys);
+}
+
+
+/*
+ * The old API -- remove when we've update the users to yielding.
+ */
+enum nb_error nb_oper_iterate_legacy(const char *xpath,
+                                    struct yang_translator *translator,
+                                    uint32_t flags, nb_oper_data_cb cb,
+                                    void *cb_arg, struct lyd_node **tree)
+{
+       struct nb_op_yield_state *ys;
+       enum nb_error ret;
+
+       ys = nb_op_create_yield_state(xpath, translator, flags, false, cb,
+                                     cb_arg, NULL, NULL);
+
+       ret = nb_op_walk_start(ys);
+       assert(ret != NB_YIELD);
+
+       if (tree && ret == NB_OK)
+               *tree = ys_root_node(ys);
+       else {
+               if (ys_root_node(ys))
+                       yang_dnode_free(ys_root_node(ys));
+               if (tree)
+                       *tree = NULL;
+       }
+
+       nb_op_free_yield_state(ys, true);
+       return ret;
+}
+
+void nb_oper_init(struct event_loop *loop)
+{
+       event_loop = loop;
+       nb_op_walks_init(&nb_op_walks);
+}
+
+void nb_oper_terminate(void)
+{
+       nb_oper_cancel_all_walks();
+}
index ea5aa22b144bfdf0be4259f95f6caa7a3ba87123..535c8b637eb7dcc9c0a53a222d920186e9d58289 100644 (file)
@@ -118,6 +118,9 @@ static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data)
                sr_data->type = SR_INT64_T;
                sr_data->data.int64_val = yang_str2int64(frr_data->value);
                break;
+       case LY_TYPE_LEAFREF:
+               sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value);
+               break;
        case LY_TYPE_STRING:
                sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value);
                break;
@@ -137,6 +140,11 @@ static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data)
                sr_data->type = SR_UINT64_T;
                sr_data->data.uint64_val = yang_str2uint64(frr_data->value);
                break;
+       case LY_TYPE_UNION:
+               /* No way to deal with this using un-typed yang_data object */
+               sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value);
+               break;
+       case LY_TYPE_UNKNOWN:
        default:
                return -1;
        }
@@ -340,6 +348,8 @@ static int frr_sr_config_change_cb(sr_session_ctx_t *session, uint32_t sub_id,
                return frr_sr_config_change_cb_apply(session, module_name);
        case SR_EV_ABORT:
                return frr_sr_config_change_cb_abort(session, module_name);
+       case SR_EV_RPC:
+       case SR_EV_UPDATE:
        default:
                flog_err(EC_LIB_LIBSYSREPO, "%s: unexpected sysrepo event: %u",
                         __func__, sr_ev);
@@ -347,39 +357,16 @@ static int frr_sr_config_change_cb(sr_session_ctx_t *session, uint32_t sub_id,
        }
 }
 
-static int frr_sr_state_data_iter_cb(const struct lysc_node *snode,
-                                    struct yang_translator *translator,
-                                    struct yang_data *data, void *arg)
-{
-       struct lyd_node *dnode = arg;
-       LY_ERR ly_errno;
-
-       ly_errno = 0;
-       ly_errno = lyd_new_path(NULL, ly_native_ctx, data->xpath, data->value,
-                               0, &dnode);
-       if (ly_errno) {
-               flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
-                         __func__);
-               yang_data_free(data);
-               return NB_ERR;
-       }
-
-       yang_data_free(data);
-       return NB_OK;
-}
-
 /* Callback for state retrieval. */
 static int frr_sr_state_cb(sr_session_ctx_t *session, uint32_t sub_id,
                           const char *module_name, const char *xpath,
                           const char *request_xpath, uint32_t request_id,
                           struct lyd_node **parent, void *private_ctx)
 {
-       struct lyd_node *dnode;
+       struct lyd_node *dnode = NULL;
 
        dnode = *parent;
-       if (nb_oper_data_iterate(request_xpath, NULL, 0,
-                                frr_sr_state_data_iter_cb, dnode, NULL)
-           != NB_OK) {
+       if (nb_oper_iterate_legacy(request_xpath, NULL, 0, NULL, NULL, &dnode)) {
                flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
                          "%s: failed to obtain operational data [xpath %s]",
                          __func__, xpath);
index 1dde70646620e6798dce27a02299829c77c64ca0..977fd9f9aa4969e988b0453aab68e4802518e3ed 100644 (file)
@@ -81,6 +81,7 @@ lib_libfrr_la_SOURCES = \
        lib/northbound.c \
        lib/northbound_cli.c \
        lib/northbound_db.c \
+       lib/northbound_oper.c \
        lib/ntop.c \
        lib/openbsd-tree.c \
        lib/pid_output.c \
index a46b50dcb693c803e0f4127526e219b8e699f1b1..b94fd3bad47b29b93c62570673e1a8c0013132b9 100644 (file)
@@ -2539,7 +2539,8 @@ int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter,
                                                   ? data_msg->partial_error
                                                   : (int)err);
 
-       get_tree->recv_clients |= (1u << id);
+       if (!data_msg->more)
+               get_tree->recv_clients |= (1u << id);
 
        /* check if done yet */
        if (get_tree->recv_clients != get_tree->sent_clients)