]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/northbound.c
Merge pull request #4330 from donaldsharp/robo_covo
[mirror_frr.git] / lib / northbound.c
index 12d08310c6ef59bcac49040565fa1912ed3104c5..e8b3e46c19deb2c624f35ee4c9ba654204781a59 100644 (file)
@@ -22,7 +22,9 @@
 #include "libfrr.h"
 #include "log.h"
 #include "lib_errors.h"
+#include "hash.h"
 #include "command.h"
+#include "debug.h"
 #include "db.h"
 #include "northbound.h"
 #include "northbound_cli.h"
 
 DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node")
 DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration")
+DEFINE_MTYPE_STATIC(LIB, NB_CONFIG_ENTRY, "Northbound Configuration Entry")
 
 /* Running configuration - shouldn't be modified directly. */
 struct nb_config *running_config;
 
+/* Hash table of user pointers associated with configuration entries. */
+static struct hash *running_config_entries;
+
+/* Management lock for the running configuration. */
+static struct {
+       /* Mutex protecting this structure. */
+       pthread_mutex_t mtx;
+
+       /* Actual lock. */
+       bool locked;
+
+       /* Northbound client who owns this lock. */
+       enum nb_client owner_client;
+
+       /* Northbound user who owns this lock. */
+       const void *owner_user;
+} running_config_mgmt_lock;
+
 /*
  * Global lock used to prevent multiple configuration transactions from
  * happening concurrently.
  */
 static bool transaction_in_progress;
 
-static int nb_configuration_callback(const enum nb_event event,
+static int nb_callback_configuration(const enum nb_event event,
                                     struct nb_config_change *change);
 static struct nb_transaction *nb_transaction_new(struct nb_config *config,
                                                 struct nb_config_cbs *changes,
                                                 enum nb_client client,
+                                                const void *user,
                                                 const char *comment);
 static void nb_transaction_free(struct nb_transaction *transaction);
 static int nb_transaction_process(enum nb_event event,
                                  struct nb_transaction *transaction);
 static void nb_transaction_apply_finish(struct nb_transaction *transaction);
+static int nb_oper_data_iter_node(const struct lys_node *snode,
+                                 const char *xpath, 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);
+
+static int nb_node_check_config_only(const struct lys_node *snode, void *arg)
+{
+       bool *config_only = arg;
 
-static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
+       if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) {
+               *config_only = false;
+               return YANG_ITER_STOP;
+       }
+
+       return YANG_ITER_CONTINUE;
+}
+
+static int nb_node_new_cb(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node;
        struct lys_node *sparent, *sparent_list;
@@ -67,21 +107,53 @@ static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
        if (sparent_list)
                nb_node->parent_list = sparent_list->priv;
 
+       /* Set flags. */
+       if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+               bool config_only = true;
+
+               yang_snodes_iterate_subtree(snode, nb_node_check_config_only,
+                                           YANG_ITER_ALLOW_AUGMENTATIONS,
+                                           &config_only);
+               if (config_only)
+                       SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY);
+       }
+       if (CHECK_FLAG(snode->nodetype, LYS_LIST)) {
+               struct lys_node_list *slist;
+
+               slist = (struct lys_node_list *)snode;
+               if (slist->keys_size == 0)
+                       SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST);
+       }
+
        /*
         * Link the northbound node and the libyang schema node with one
         * another.
         */
        nb_node->snode = snode;
        lys_set_private(snode, nb_node);
+
+       return YANG_ITER_CONTINUE;
 }
 
-static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2)
+static int nb_node_del_cb(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node;
 
        nb_node = snode->priv;
        lys_set_private(snode, NULL);
        XFREE(MTYPE_NB_NODE, nb_node);
+
+       return YANG_ITER_CONTINUE;
+}
+
+void nb_nodes_create(void)
+{
+       yang_snodes_iterate_all(nb_node_new_cb, 0, NULL);
+}
+
+void nb_nodes_delete(void)
+{
+       yang_snodes_iterate_all(nb_node_del_cb, 0, NULL);
 }
 
 struct nb_node *nb_node_find(const char *xpath)
@@ -134,8 +206,8 @@ static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node)
                                     !!nb_node->cbs.create, false);
        error += nb_node_validate_cb(nb_node, NB_OP_MODIFY,
                                     !!nb_node->cbs.modify, false);
-       error += nb_node_validate_cb(nb_node, NB_OP_DELETE,
-                                    !!nb_node->cbs.delete, false);
+       error += nb_node_validate_cb(nb_node, NB_OP_DESTROY,
+                                    !!nb_node->cbs.destroy, false);
        error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move,
                                     false);
        error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH,
@@ -170,15 +242,16 @@ static unsigned int nb_node_validate_priority(const struct nb_node *nb_node)
        return 0;
 }
 
-static void nb_node_validate(const struct lys_node *snode, void *arg1,
-                            void *arg2)
+static int nb_node_validate(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node = snode->priv;
-       unsigned int *errors = arg1;
+       unsigned int *errors = arg;
 
        /* Validate callbacks and priority. */
        *errors += nb_node_validate_cbs(nb_node);
        *errors += nb_node_validate_priority(nb_node);
+
+       return YANG_ITER_CONTINUE;
 }
 
 struct nb_config *nb_config_new(struct lyd_node *dnode)
@@ -189,8 +262,9 @@ struct nb_config *nb_config_new(struct lyd_node *dnode)
        if (dnode)
                config->dnode = dnode;
        else
-               config->dnode = yang_dnode_new(ly_native_ctx);
+               config->dnode = yang_dnode_new(ly_native_ctx, true);
        config->version = 0;
+       pthread_rwlock_init(&config->lock, NULL);
 
        return config;
 }
@@ -199,6 +273,7 @@ void nb_config_free(struct nb_config *config)
 {
        if (config->dnode)
                yang_dnode_free(config->dnode);
+       pthread_rwlock_destroy(&config->lock);
        XFREE(MTYPE_NB_CONFIG, config);
 }
 
@@ -209,6 +284,7 @@ struct nb_config *nb_config_dup(const struct nb_config *config)
        dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
        dup->dnode = yang_dnode_dup(config->dnode);
        dup->version = config->version;
+       pthread_rwlock_init(&dup->lock, NULL);
 
        return dup;
 }
@@ -236,7 +312,8 @@ void nb_config_replace(struct nb_config *config_dst,
                config_dst->version = config_src->version;
 
        /* Update dnode. */
-       yang_dnode_free(config_dst->dnode);
+       if (config_dst->dnode)
+               yang_dnode_free(config_dst->dnode);
        if (preserve_source) {
                config_dst->dnode = yang_dnode_dup(config_src->dnode);
        } else {
@@ -296,39 +373,58 @@ static void nb_config_diff_del_changes(struct nb_config_cbs *changes)
  * configurations. Given a new subtree, calculate all new YANG data nodes,
  * excluding default leafs and leaf-lists. This is a recursive function.
  */
-static void nb_config_diff_new_subtree(const struct lyd_node *dnode,
-                                      struct nb_config_cbs *changes)
+static void nb_config_diff_created(const struct lyd_node *dnode,
+                                  struct nb_config_cbs *changes)
 {
+       enum nb_operation operation;
        struct lyd_node *child;
 
-       LY_TREE_FOR (dnode->child, child) {
-               enum nb_operation operation;
+       switch (dnode->schema->nodetype) {
+       case LYS_LEAF:
+       case LYS_LEAFLIST:
+               if (lyd_wd_default((struct lyd_node_leaf_list *)dnode))
+                       break;
 
-               switch (child->schema->nodetype) {
-               case LYS_LEAF:
-               case LYS_LEAFLIST:
-                       if (lyd_wd_default((struct lyd_node_leaf_list *)child))
-                               break;
+               if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
+                       operation = NB_OP_CREATE;
+               else if (nb_operation_is_valid(NB_OP_MODIFY, dnode->schema))
+                       operation = NB_OP_MODIFY;
+               else
+                       return;
 
-                       if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
-                               operation = NB_OP_CREATE;
-                       else if (nb_operation_is_valid(NB_OP_MODIFY,
-                                                      child->schema))
-                               operation = NB_OP_MODIFY;
-                       else
-                               continue;
+               nb_config_diff_add_change(changes, operation, dnode);
+               break;
+       case LYS_CONTAINER:
+       case LYS_LIST:
+               if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
+                       nb_config_diff_add_change(changes, NB_OP_CREATE, dnode);
+
+               /* Process child nodes recursively. */
+               LY_TREE_FOR (dnode->child, child) {
+                       nb_config_diff_created(child, changes);
+               }
+               break;
+       default:
+               break;
+       }
+}
 
-                       nb_config_diff_add_change(changes, operation, child);
-                       break;
-               case LYS_CONTAINER:
-               case LYS_LIST:
-                       if (nb_operation_is_valid(NB_OP_CREATE, child->schema))
-                               nb_config_diff_add_change(changes, NB_OP_CREATE,
-                                                         child);
-                       nb_config_diff_new_subtree(child, changes);
-                       break;
-               default:
-                       break;
+static void nb_config_diff_deleted(const struct lyd_node *dnode,
+                                  struct nb_config_cbs *changes)
+{
+       if (nb_operation_is_valid(NB_OP_DESTROY, dnode->schema))
+               nb_config_diff_add_change(changes, NB_OP_DESTROY, dnode);
+       else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) {
+               struct lyd_node *child;
+
+               /*
+                * Non-presence containers need special handling since they
+                * don't have "destroy" callbacks. In this case, what we need to
+                * do is to call the "destroy" callbacks of their child nodes
+                * when applicable (i.e. optional nodes).
+                */
+               LY_TREE_FOR (dnode->child, child) {
+                       nb_config_diff_deleted(child, changes);
                }
        }
 }
@@ -347,41 +443,27 @@ static void nb_config_diff(const struct nb_config *config1,
        for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) {
                LYD_DIFFTYPE type;
                struct lyd_node *dnode;
-               enum nb_operation operation;
 
                type = diff->type[i];
 
                switch (type) {
                case LYD_DIFF_CREATED:
                        dnode = diff->second[i];
-
-                       if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema))
-                               operation = NB_OP_CREATE;
-                       else if (nb_operation_is_valid(NB_OP_MODIFY,
-                                                      dnode->schema))
-                               operation = NB_OP_MODIFY;
-                       else
-                               continue;
+                       nb_config_diff_created(dnode, changes);
                        break;
                case LYD_DIFF_DELETED:
                        dnode = diff->first[i];
-                       operation = NB_OP_DELETE;
+                       nb_config_diff_deleted(dnode, changes);
                        break;
                case LYD_DIFF_CHANGED:
                        dnode = diff->second[i];
-                       operation = NB_OP_MODIFY;
+                       nb_config_diff_add_change(changes, NB_OP_MODIFY, dnode);
                        break;
                case LYD_DIFF_MOVEDAFTER1:
                case LYD_DIFF_MOVEDAFTER2:
                default:
                        continue;
                }
-
-               nb_config_diff_add_change(changes, operation, dnode);
-
-               if (type == LYD_DIFF_CREATED
-                   && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST)))
-                       nb_config_diff_new_subtree(dnode, changes);
        }
 
        lyd_free_diff(diff);
@@ -396,13 +478,6 @@ int nb_candidate_edit(struct nb_config *candidate,
        struct lyd_node *dnode;
        char xpath_edit[XPATH_MAXLEN];
 
-       if (!nb_operation_is_valid(operation, nb_node->snode)) {
-               flog_warn(EC_LIB_NB_CANDIDATE_EDIT_ERROR,
-                         "%s: %s operation not valid for %s", __func__,
-                         nb_operation_name(operation), xpath);
-               return NB_ERR;
-       }
-
        /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */
        if (nb_node->snode->nodetype == LYS_LEAFLIST)
                snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath,
@@ -432,7 +507,7 @@ int nb_candidate_edit(struct nb_config *candidate,
                        lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx);
                }
                break;
-       case NB_OP_DELETE:
+       case NB_OP_DESTROY:
                dnode = yang_dnode_get(candidate->dnode, xpath_edit);
                if (!dnode)
                        /*
@@ -457,17 +532,28 @@ int nb_candidate_edit(struct nb_config *candidate,
 
 bool nb_candidate_needs_update(const struct nb_config *candidate)
 {
-       if (candidate->version < running_config->version)
-               return true;
+       bool ret = false;
+
+       pthread_rwlock_rdlock(&running_config->lock);
+       {
+               if (candidate->version < running_config->version)
+                       ret = true;
+       }
+       pthread_rwlock_unlock(&running_config->lock);
 
-       return false;
+       return ret;
 }
 
 int nb_candidate_update(struct nb_config *candidate)
 {
        struct nb_config *updated_config;
 
-       updated_config = nb_config_dup(running_config);
+       pthread_rwlock_rdlock(&running_config->lock);
+       {
+               updated_config = nb_config_dup(running_config);
+       }
+       pthread_rwlock_unlock(&running_config->lock);
+
        if (nb_config_merge(updated_config, candidate, true) != NB_OK)
                return NB_ERR;
 
@@ -476,38 +562,6 @@ int nb_candidate_update(struct nb_config *candidate)
        return NB_OK;
 }
 
-/*
- * The northbound configuration callbacks use the 'priv' pointer present in the
- * libyang lyd_node structure to store pointers to FRR internal variables
- * associated to YANG lists and presence containers. Before commiting a
- * candidate configuration, we must restore the 'priv' pointers stored in the
- * running configuration since they might be lost while editing the candidate.
- */
-static void nb_candidate_restore_priv_pointers(struct nb_config *candidate)
-{
-       struct lyd_node *root, *next, *dnode_iter;
-
-       LY_TREE_FOR (running_config->dnode, root) {
-               LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
-                       struct lyd_node *dnode_candidate;
-                       char xpath[XPATH_MAXLEN];
-
-                       if (!dnode_iter->priv)
-                               goto next;
-
-                       yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
-                       dnode_candidate =
-                               yang_dnode_get(candidate->dnode, xpath);
-                       if (dnode_candidate)
-                               yang_dnode_set_entry(dnode_candidate,
-                                                    dnode_iter->priv);
-
-               next:
-                       LY_TREE_DFS_END(root, next, dnode_iter);
-               }
-       }
-}
-
 /*
  * Perform YANG syntactic and semantic validation.
  *
@@ -530,12 +584,11 @@ static int nb_candidate_validate_changes(struct nb_config *candidate,
 {
        struct nb_config_cb *cb;
 
-       nb_candidate_restore_priv_pointers(candidate);
        RB_FOREACH (cb, nb_config_cbs, changes) {
                struct nb_config_change *change = (struct nb_config_change *)cb;
                int ret;
 
-               ret = nb_configuration_callback(NB_EV_VALIDATE, change);
+               ret = nb_callback_configuration(NB_EV_VALIDATE, change);
                if (ret != NB_OK)
                        return NB_ERR_VALIDATION;
        }
@@ -552,15 +605,20 @@ int nb_candidate_validate(struct nb_config *candidate)
                return NB_ERR_VALIDATION;
 
        RB_INIT(nb_config_cbs, &changes);
-       nb_config_diff(running_config, candidate, &changes);
-       ret = nb_candidate_validate_changes(candidate, &changes);
-       nb_config_diff_del_changes(&changes);
+       pthread_rwlock_rdlock(&running_config->lock);
+       {
+               nb_config_diff(running_config, candidate, &changes);
+               ret = nb_candidate_validate_changes(candidate, &changes);
+               nb_config_diff_del_changes(&changes);
+       }
+       pthread_rwlock_unlock(&running_config->lock);
 
        return ret;
 }
 
 int nb_candidate_commit_prepare(struct nb_config *candidate,
-                               enum nb_client client, const char *comment,
+                               enum nb_client client, const void *user,
+                               const char *comment,
                                struct nb_transaction **transaction)
 {
        struct nb_config_cbs changes;
@@ -573,25 +631,36 @@ int nb_candidate_commit_prepare(struct nb_config *candidate,
        }
 
        RB_INIT(nb_config_cbs, &changes);
-       nb_config_diff(running_config, candidate, &changes);
-       if (RB_EMPTY(nb_config_cbs, &changes))
-               return NB_ERR_NO_CHANGES;
+       pthread_rwlock_rdlock(&running_config->lock);
+       {
+               nb_config_diff(running_config, candidate, &changes);
+               if (RB_EMPTY(nb_config_cbs, &changes)) {
+                       pthread_rwlock_unlock(&running_config->lock);
+                       return NB_ERR_NO_CHANGES;
+               }
 
-       if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
-               flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
-                         "%s: failed to validate candidate configuration",
-                         __func__);
-               nb_config_diff_del_changes(&changes);
-               return NB_ERR_VALIDATION;
-       }
+               if (nb_candidate_validate_changes(candidate, &changes)
+                   != NB_OK) {
+                       flog_warn(
+                               EC_LIB_NB_CANDIDATE_INVALID,
+                               "%s: failed to validate candidate configuration",
+                               __func__);
+                       nb_config_diff_del_changes(&changes);
+                       pthread_rwlock_unlock(&running_config->lock);
+                       return NB_ERR_VALIDATION;
+               }
 
-       *transaction = nb_transaction_new(candidate, &changes, client, comment);
-       if (*transaction == NULL) {
-               flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
-                         "%s: failed to create transaction", __func__);
-               nb_config_diff_del_changes(&changes);
-               return NB_ERR_LOCKED;
+               *transaction = nb_transaction_new(candidate, &changes, client,
+                                                 user, comment);
+               if (*transaction == NULL) {
+                       flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+                                 "%s: failed to create transaction", __func__);
+                       nb_config_diff_del_changes(&changes);
+                       pthread_rwlock_unlock(&running_config->lock);
+                       return NB_ERR_LOCKED;
+               }
        }
+       pthread_rwlock_unlock(&running_config->lock);
 
        return nb_transaction_process(NB_EV_PREPARE, *transaction);
 }
@@ -610,7 +679,11 @@ void nb_candidate_commit_apply(struct nb_transaction *transaction,
 
        /* Replace running by candidate. */
        transaction->config->version++;
-       nb_config_replace(running_config, transaction->config, true);
+       pthread_rwlock_wrlock(&running_config->lock);
+       {
+               nb_config_replace(running_config, transaction->config, true);
+       }
+       pthread_rwlock_unlock(&running_config->lock);
 
        /* Record transaction. */
        if (save_transaction
@@ -622,13 +695,13 @@ void nb_candidate_commit_apply(struct nb_transaction *transaction,
 }
 
 int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
-                       bool save_transaction, const char *comment,
-                       uint32_t *transaction_id)
+                       const void *user, bool save_transaction,
+                       const char *comment, uint32_t *transaction_id)
 {
        struct nb_transaction *transaction = NULL;
        int ret;
 
-       ret = nb_candidate_commit_prepare(candidate, client, comment,
+       ret = nb_candidate_commit_prepare(candidate, client, user, comment,
                                          &transaction);
        /*
         * Apply the changes if the preparation phase succeeded. Otherwise abort
@@ -643,6 +716,60 @@ int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
        return ret;
 }
 
+int nb_running_lock(enum nb_client client, const void *user)
+{
+       int ret = -1;
+
+       pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+       {
+               if (!running_config_mgmt_lock.locked) {
+                       running_config_mgmt_lock.locked = true;
+                       running_config_mgmt_lock.owner_client = client;
+                       running_config_mgmt_lock.owner_user = user;
+                       ret = 0;
+               }
+       }
+       pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+       return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+       int ret = -1;
+
+       pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+       {
+               if (running_config_mgmt_lock.locked
+                   && running_config_mgmt_lock.owner_client == client
+                   && running_config_mgmt_lock.owner_user == user) {
+                       running_config_mgmt_lock.locked = false;
+                       running_config_mgmt_lock.owner_client = NB_CLIENT_NONE;
+                       running_config_mgmt_lock.owner_user = NULL;
+                       ret = 0;
+               }
+       }
+       pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+       return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+       int ret = -1;
+
+       pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+       {
+               if (!running_config_mgmt_lock.locked
+                   || (running_config_mgmt_lock.owner_client == client
+                       && running_config_mgmt_lock.owner_user == user))
+                       ret = 0;
+       }
+       pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+       return ret;
+}
+
 static void nb_log_callback(const enum nb_event event,
                            enum nb_operation operation, const char *xpath,
                            const char *value)
@@ -657,7 +784,7 @@ static void nb_log_callback(const enum nb_event event,
  * Call the northbound configuration callback associated to a given
  * configuration change.
  */
-static int nb_configuration_callback(const enum nb_event event,
+static int nb_callback_configuration(const enum nb_event event,
                                     struct nb_config_change *change)
 {
        enum nb_operation operation = change->cb.operation;
@@ -667,7 +794,7 @@ static int nb_configuration_callback(const enum nb_event event,
        union nb_resource *resource;
        int ret = NB_ERR;
 
-       if (debug_northbound) {
+       if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) {
                const char *value = "(none)";
 
                if (dnode && !yang_snode_is_typeless_data(dnode->schema))
@@ -688,33 +815,121 @@ static int nb_configuration_callback(const enum nb_event event,
        case NB_OP_MODIFY:
                ret = (*nb_node->cbs.modify)(event, dnode, resource);
                break;
-       case NB_OP_DELETE:
-               ret = (*nb_node->cbs.delete)(event, dnode);
+       case NB_OP_DESTROY:
+               ret = (*nb_node->cbs.destroy)(event, dnode);
                break;
        case NB_OP_MOVE:
                ret = (*nb_node->cbs.move)(event, dnode);
                break;
        default:
-               break;
+               flog_err(EC_LIB_DEVELOPMENT,
+                        "%s: unknown operation (%u) [xpath %s]", __func__,
+                        operation, xpath);
+               exit(1);
        }
 
-       if (ret != NB_OK)
-               flog_warn(
-                       EC_LIB_NB_CB_CONFIG,
-                       "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]",
-                       __func__, nb_err_name(ret), nb_event_name(event),
-                       nb_operation_name(operation), xpath);
+       if (ret != NB_OK) {
+               int priority;
+               enum lib_log_refs ref;
+
+               switch (event) {
+               case NB_EV_VALIDATE:
+                       priority = LOG_WARNING;
+                       ref = EC_LIB_NB_CB_CONFIG_VALIDATE;
+                       break;
+               case NB_EV_PREPARE:
+                       priority = LOG_WARNING;
+                       ref = EC_LIB_NB_CB_CONFIG_PREPARE;
+                       break;
+               case NB_EV_ABORT:
+                       priority = LOG_WARNING;
+                       ref = EC_LIB_NB_CB_CONFIG_ABORT;
+                       break;
+               case NB_EV_APPLY:
+                       priority = LOG_ERR;
+                       ref = EC_LIB_NB_CB_CONFIG_APPLY;
+                       break;
+               default:
+                       flog_err(EC_LIB_DEVELOPMENT,
+                                "%s: unknown event (%u) [xpath %s]",
+                                __func__, event, xpath);
+                       exit(1);
+               }
+
+               flog(priority, ref,
+                    "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]",
+                    __func__, nb_err_name(ret), nb_event_name(event),
+                    nb_operation_name(operation), xpath);
+       }
 
        return ret;
 }
 
-static struct nb_transaction *nb_transaction_new(struct nb_config *config,
-                                                struct nb_config_cbs *changes,
-                                                enum nb_client client,
-                                                const char *comment)
+struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node,
+                                      const char *xpath,
+                                      const void *list_entry)
+{
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (get_elem): xpath [%s] list_entry [%p]",
+              xpath, list_entry);
+
+       return nb_node->cbs.get_elem(xpath, list_entry);
+}
+
+const void *nb_callback_get_next(const struct nb_node *nb_node,
+                                const void *parent_list_entry,
+                                const void *list_entry)
+{
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (get_next): node [%s] parent_list_entry [%p] list_entry [%p]",
+              nb_node->xpath, parent_list_entry, list_entry);
+
+       return nb_node->cbs.get_next(parent_list_entry, list_entry);
+}
+
+int nb_callback_get_keys(const struct nb_node *nb_node, const void *list_entry,
+                        struct yang_list_keys *keys)
+{
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (get_keys): node [%s] list_entry [%p]",
+              nb_node->xpath, list_entry);
+
+       return nb_node->cbs.get_keys(list_entry, keys);
+}
+
+const void *nb_callback_lookup_entry(const struct nb_node *nb_node,
+                                    const void *parent_list_entry,
+                                    const struct yang_list_keys *keys)
+{
+       DEBUGD(&nb_dbg_cbs_state,
+              "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]",
+              nb_node->xpath, parent_list_entry);
+
+       return nb_node->cbs.lookup_entry(parent_list_entry, keys);
+}
+
+int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
+                   const struct list *input, struct list *output)
+{
+       DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath);
+
+       return nb_node->cbs.rpc(xpath, input, output);
+}
+
+static struct nb_transaction *
+nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes,
+                  enum nb_client client, const void *user, const char *comment)
 {
        struct nb_transaction *transaction;
 
+       if (nb_running_lock_check(client, user)) {
+               flog_warn(
+                       EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+                       "%s: running configuration is locked by another client",
+                       __func__);
+               return NULL;
+       }
+
        if (transaction_in_progress) {
                flog_warn(
                        EC_LIB_NB_TRANSACTION_CREATION_FAILED,
@@ -748,40 +963,52 @@ static int nb_transaction_process(enum nb_event event,
 {
        struct nb_config_cb *cb;
 
-       RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
-               struct nb_config_change *change = (struct nb_config_change *)cb;
-               int ret;
-
-               /*
-                * Only try to release resources that were allocated
-                * successfully.
-                */
-               if (event == NB_EV_ABORT && change->prepare_ok == false)
-                       break;
+       /*
+        * Need to lock the running configuration since transaction->changes
+        * can contain pointers to data nodes from the running configuration.
+        */
+       pthread_rwlock_rdlock(&running_config->lock);
+       {
+               RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+                       struct nb_config_change *change =
+                               (struct nb_config_change *)cb;
+                       int ret;
 
-               /* Call the appropriate callback. */
-               ret = nb_configuration_callback(event, change);
-               switch (event) {
-               case NB_EV_PREPARE:
-                       if (ret != NB_OK)
-                               return ret;
-                       change->prepare_ok = true;
-                       break;
-               case NB_EV_ABORT:
-               case NB_EV_APPLY:
                        /*
-                        * At this point it's not possible to reject the
-                        * transaction anymore, so any failure here can lead to
-                        * inconsistencies and should be treated as a bug.
-                        * Operations prone to errors, like validations and
-                        * resource allocations, should be performed during the
-                        * 'prepare' phase.
+                        * Only try to release resources that were allocated
+                        * successfully.
                         */
-                       break;
-               default:
-                       break;
+                       if (event == NB_EV_ABORT && change->prepare_ok == false)
+                               break;
+
+                       /* Call the appropriate callback. */
+                       ret = nb_callback_configuration(event, change);
+                       switch (event) {
+                       case NB_EV_PREPARE:
+                               if (ret != NB_OK) {
+                                       pthread_rwlock_unlock(
+                                               &running_config->lock);
+                                       return ret;
+                               }
+                               change->prepare_ok = true;
+                               break;
+                       case NB_EV_ABORT:
+                       case NB_EV_APPLY:
+                               /*
+                                * At this point it's not possible to reject the
+                                * transaction anymore, so any failure here can
+                                * lead to inconsistencies and should be treated
+                                * as a bug. Operations prone to errors, like
+                                * validations and resource allocations, should
+                                * be performed during the 'prepare' phase.
+                                */
+                               break;
+                       default:
+                               break;
+                       }
                }
        }
+       pthread_rwlock_unlock(&running_config->lock);
 
        return NB_OK;
 }
@@ -833,7 +1060,7 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
                 * (the 'apply_finish' callbacks from the node ancestors should
                 * be called though).
                 */
-               if (change->cb.operation == NB_OP_DELETE) {
+               if (change->cb.operation == NB_OP_DESTROY) {
                        char xpath[XPATH_MAXLEN];
 
                        dnode = dnode->parent;
@@ -875,7 +1102,7 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
 
        /* Call the 'apply_finish' callbacks, sorted by their priorities. */
        RB_FOREACH (cb, nb_config_cbs, &cbs) {
-               if (debug_northbound)
+               if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL))
                        nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH,
                                        cb->xpath, NULL);
 
@@ -890,15 +1117,360 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
        }
 }
 
+static int nb_oper_data_iter_children(const struct lys_node *snode,
+                                     const char *xpath, 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 lys_node *child;
+
+       LY_TREE_FOR (snode->child, child) {
+               int ret;
+
+               ret = nb_oper_data_iter_node(child, xpath, list_entry,
+                                            list_keys, translator, false,
+                                            flags, cb, arg);
+               if (ret != NB_OK)
+                       return ret;
+       }
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_leaf(const struct nb_node *nb_node,
+                                 const char *xpath, const void *list_entry,
+                                 const struct yang_list_keys *list_keys,
+                                 struct yang_translator *translator,
+                                 uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+       struct yang_data *data;
+
+       if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       /* Ignore list keys. */
+       if (lys_is_key((struct lys_node_leaf *)nb_node->snode, NULL))
+               return NB_OK;
+
+       data = nb_callback_get_elem(nb_node, xpath, list_entry);
+       if (data == NULL)
+               /* Leaf of type "empty" is not present. */
+               return NB_OK;
+
+       return (*cb)(nb_node->snode, translator, data, arg);
+}
+
+static int nb_oper_data_iter_container(const struct nb_node *nb_node,
+                                      const char *xpath,
+                                      const void *list_entry,
+                                      const struct yang_list_keys *list_keys,
+                                      struct yang_translator *translator,
+                                      uint32_t flags, nb_oper_data_cb cb,
+                                      void *arg)
+{
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+               return NB_OK;
+
+       /* Presence containers. */
+       if (nb_node->cbs.get_elem) {
+               struct yang_data *data;
+               int ret;
+
+               data = nb_callback_get_elem(nb_node, xpath, list_entry);
+               if (data == NULL)
+                       /* Presence container is not present. */
+                       return NB_OK;
+
+               ret = (*cb)(nb_node->snode, translator, data, arg);
+               if (ret != NB_OK)
+                       return ret;
+       }
+
+       /* Iterate over the child nodes. */
+       return nb_oper_data_iter_children(nb_node->snode, xpath, list_entry,
+                                         list_keys, translator, false, flags,
+                                         cb, arg);
+}
+
+static int
+nb_oper_data_iter_leaflist(const struct nb_node *nb_node, const char *xpath,
+                          const void *parent_list_entry,
+                          const struct yang_list_keys *parent_list_keys,
+                          struct yang_translator *translator, uint32_t flags,
+                          nb_oper_data_cb cb, void *arg)
+{
+       const void *list_entry = NULL;
+
+       if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       do {
+               struct yang_data *data;
+               int ret;
+
+               list_entry = nb_callback_get_next(nb_node, parent_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;
+
+               ret = (*cb)(nb_node->snode, translator, data, arg);
+               if (ret != NB_OK)
+                       return ret;
+       } while (list_entry);
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_list(const struct nb_node *nb_node,
+                                 const char *xpath_list,
+                                 const void *parent_list_entry,
+                                 const struct yang_list_keys *parent_list_keys,
+                                 struct yang_translator *translator,
+                                 uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+       struct lys_node_list *slist = (struct lys_node_list *)nb_node->snode;
+       const void *list_entry = NULL;
+       uint32_t position = 1;
+
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+               return NB_OK;
+
+       /* Iterate over all list entries. */
+       do {
+               struct yang_list_keys list_keys;
+               char xpath[XPATH_MAXLEN * 2];
+               int ret;
+
+               /* Obtain list entry. */
+               list_entry = nb_callback_get_next(nb_node, parent_list_entry,
+                                                 list_entry);
+               if (!list_entry)
+                       /* End of the list. */
+                       break;
+
+               if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
+                       /* Obtain the list entry keys. */
+                       if (nb_callback_get_keys(nb_node, list_entry,
+                                                &list_keys)
+                           != NB_OK) {
+                               flog_warn(EC_LIB_NB_CB_STATE,
+                                         "%s: failed to get list keys",
+                                         __func__);
+                               return NB_ERR;
+                       }
+
+                       /* Build XPath of the list entry. */
+                       strlcpy(xpath, xpath_list, sizeof(xpath));
+                       for (unsigned int i = 0; i < list_keys.num; i++) {
+                               snprintf(xpath + strlen(xpath),
+                                        sizeof(xpath) - strlen(xpath),
+                                        "[%s='%s']", slist->keys[i]->name,
+                                        list_keys.key[i]);
+                       }
+               } else {
+                       /*
+                        * Keyless list - build XPath using a positional index.
+                        */
+                       snprintf(xpath, sizeof(xpath), "%s[%u]", xpath_list,
+                                position);
+                       position++;
+               }
+
+               /* Iterate over the child nodes. */
+               ret = nb_oper_data_iter_children(
+                       nb_node->snode, xpath, list_entry, &list_keys,
+                       translator, false, flags, cb, arg);
+               if (ret != NB_OK)
+                       return ret;
+       } while (list_entry);
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_node(const struct lys_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 nb_node *nb_node;
+       char xpath[XPATH_MAXLEN];
+       int ret = NB_OK;
+
+       if (!first && CHECK_FLAG(flags, NB_OPER_DATA_ITER_NORECURSE)
+           && CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST))
+               return NB_OK;
+
+       /* Update XPath. */
+       strlcpy(xpath, xpath_parent, sizeof(xpath));
+       if (!first && snode->nodetype != LYS_USES)
+               snprintf(xpath + strlen(xpath), sizeof(xpath) - strlen(xpath),
+                        "/%s", snode->name);
+
+       nb_node = snode->priv;
+       switch (snode->nodetype) {
+       case LYS_CONTAINER:
+               ret = nb_oper_data_iter_container(nb_node, xpath, list_entry,
+                                                 list_keys, translator, flags,
+                                                 cb, arg);
+               break;
+       case LYS_LEAF:
+               ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry,
+                                            list_keys, translator, flags, cb,
+                                            arg);
+               break;
+       case LYS_LEAFLIST:
+               ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry,
+                                                list_keys, translator, flags,
+                                                cb, arg);
+               break;
+       case LYS_LIST:
+               ret = nb_oper_data_iter_list(nb_node, xpath, list_entry,
+                                            list_keys, translator, flags, cb,
+                                            arg);
+               break;
+       case LYS_USES:
+               ret = nb_oper_data_iter_children(snode, xpath, list_entry,
+                                                list_keys, translator, false,
+                                                flags, cb, arg);
+               break;
+       default:
+               break;
+       }
+
+       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 nb_node *nb_node;
+       const void *list_entry = NULL;
+       struct yang_list_keys list_keys;
+       struct list *list_dnodes;
+       struct lyd_node *dnode, *dn;
+       struct listnode *ln;
+       int ret;
+
+       nb_node = nb_node_find(xpath);
+       if (!nb_node) {
+               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                         "%s: unknown data path: %s", __func__, xpath);
+               return NB_ERR;
+       }
+
+       /* For now this function works only with containers and lists. */
+       if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+               flog_warn(
+                       EC_LIB_NB_OPERATIONAL_DATA,
+                       "%s: can't iterate over YANG leaf or leaf-list [xpath %s]",
+                       __func__, xpath);
+               return NB_ERR;
+       }
+
+       /*
+        * Create a data tree from the XPath so that we can parse the keys of
+        * all YANG lists (if any).
+        */
+       ly_errno = 0;
+       dnode = lyd_new_path(NULL, ly_native_ctx, xpath, NULL, 0,
+                            LYD_PATH_OPT_UPDATE);
+       if (!dnode && ly_errno) {
+               flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+                         __func__);
+               return NB_ERR;
+       }
+       /*
+        * We can remove the following two lines once we depend on
+        * libyang-v0.16-r2, which has the LYD_PATH_OPT_NOPARENTRET flag for
+        * lyd_new_path().
+        */
+       dnode = yang_dnode_get(dnode, xpath);
+       assert(dnode);
+
+       /*
+        * Create a linked list to sort the data nodes starting from the root.
+        */
+       list_dnodes = list_new();
+       for (dn = dnode; dn; dn = dn->parent) {
+               if (dn->schema->nodetype != LYS_LIST || !dn->child)
+                       continue;
+               listnode_add_head(list_dnodes, dn);
+       }
+       /*
+        * Use the northbound callbacks to find list entry pointer corresponding
+        * to the given XPath.
+        */
+       for (ALL_LIST_ELEMENTS_RO(list_dnodes, ln, dn)) {
+               struct lyd_node *child;
+               struct nb_node *nn;
+               unsigned int n = 0;
+
+               /* Obtain the list entry keys. */
+               memset(&list_keys, 0, sizeof(list_keys));
+               LY_TREE_FOR (dn->child, child) {
+                       if (!lys_is_key((struct lys_node_leaf *)child->schema,
+                                       NULL))
+                               continue;
+                       strlcpy(list_keys.key[n],
+                               yang_dnode_get_string(child, NULL),
+                               sizeof(list_keys.key[n]));
+                       n++;
+               }
+               list_keys.num = n;
+               if (list_keys.num
+                   != ((struct lys_node_list *)dn->schema)->keys_size) {
+                       list_delete(&list_dnodes);
+                       yang_dnode_free(dnode);
+                       return NB_ERR_NOT_FOUND;
+               }
+
+               /* Find the list entry pointer. */
+               nn = dn->schema->priv;
+               list_entry =
+                       nb_callback_lookup_entry(nn, list_entry, &list_keys);
+               if (list_entry == NULL) {
+                       list_delete(&list_dnodes);
+                       yang_dnode_free(dnode);
+                       return NB_ERR_NOT_FOUND;
+               }
+       }
+
+       /* If a list entry was given, iterate over that list entry only. */
+       if (dnode->schema->nodetype == LYS_LIST && dnode->child)
+               ret = nb_oper_data_iter_children(
+                       nb_node->snode, xpath, list_entry, &list_keys,
+                       translator, true, flags, cb, arg);
+       else
+               ret = nb_oper_data_iter_node(nb_node->snode, xpath, list_entry,
+                                            &list_keys, translator, true,
+                                            flags, cb, arg);
+
+       list_delete(&list_dnodes);
+       yang_dnode_free(dnode);
+
+       return ret;
+}
+
 bool nb_operation_is_valid(enum nb_operation operation,
                           const struct lys_node *snode)
 {
+       struct nb_node *nb_node = snode->priv;
        struct lys_node_container *scontainer;
        struct lys_node_leaf *sleaf;
 
        switch (operation) {
        case NB_OP_CREATE:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -920,7 +1492,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_MODIFY:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -937,8 +1509,8 @@ bool nb_operation_is_valid(enum nb_operation operation,
                        return false;
                }
                return true;
-       case NB_OP_DELETE:
-               if (!(snode->flags & LYS_CONFIG_W))
+       case NB_OP_DESTROY:
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -957,7 +1529,8 @@ bool nb_operation_is_valid(enum nb_operation operation,
                                return true;
                        if (sleaf->when)
                                return true;
-                       if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt)
+                       if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE)
+                           || sleaf->dflt)
                                return false;
                        break;
                case LYS_CONTAINER:
@@ -973,13 +1546,13 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_MOVE:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
                case LYS_LIST:
                case LYS_LEAFLIST:
-                       if (!(snode->flags & LYS_USERORDERED))
+                       if (!CHECK_FLAG(snode->flags, LYS_USERORDERED))
                                return false;
                        break;
                default:
@@ -987,15 +1560,16 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_APPLY_FINISH:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
                return true;
        case NB_OP_GET_ELEM:
-               if (!(snode->flags & LYS_CONFIG_R))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
                        return false;
 
                switch (snode->nodetype) {
                case LYS_LEAF:
+               case LYS_LEAFLIST:
                        break;
                case LYS_CONTAINER:
                        scontainer = (struct lys_node_container *)snode;
@@ -1007,20 +1581,34 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_GET_NEXT:
+               switch (snode->nodetype) {
+               case LYS_LIST:
+                       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+                               return false;
+                       break;
+               case LYS_LEAFLIST:
+                       if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+                               return false;
+                       break;
+               default:
+                       return false;
+               }
+               return true;
        case NB_OP_GET_KEYS:
        case NB_OP_LOOKUP_ENTRY:
-               if (!(snode->flags & LYS_CONFIG_R))
-                       return false;
-
                switch (snode->nodetype) {
                case LYS_LIST:
+                       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+                               return false;
+                       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST))
+                               return false;
                        break;
                default:
                        return false;
                }
                return true;
        case NB_OP_RPC:
-               if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R))
+               if (CHECK_FLAG(snode->flags, LYS_CONFIG_W | LYS_CONFIG_R))
                        return false;
 
                switch (snode->nodetype) {
@@ -1043,6 +1631,8 @@ int nb_notification_send(const char *xpath, struct list *arguments)
 {
        int ret;
 
+       DEBUGD(&nb_dbg_notif, "northbound notification: %s", xpath);
+
        ret = hook_call(nb_notification_send, xpath, arguments);
        if (arguments)
                list_delete(&arguments);
@@ -1050,6 +1640,116 @@ int nb_notification_send(const char *xpath, struct list *arguments)
        return ret;
 }
 
+/* Running configuration user pointers management. */
+struct nb_config_entry {
+       char xpath[XPATH_MAXLEN];
+       void *entry;
+};
+
+static bool running_config_entry_cmp(const void *value1, const void *value2)
+{
+       const struct nb_config_entry *c1 = value1;
+       const struct nb_config_entry *c2 = value2;
+
+       return strmatch(c1->xpath, c2->xpath);
+}
+
+static unsigned int running_config_entry_key_make(void *value)
+{
+       return string_hash_make(value);
+}
+
+static void *running_config_entry_alloc(void *p)
+{
+       struct nb_config_entry *new, *key = p;
+
+       new = XCALLOC(MTYPE_NB_CONFIG_ENTRY, sizeof(*new));
+       strlcpy(new->xpath, key->xpath, sizeof(new->xpath));
+
+       return new;
+}
+
+static void running_config_entry_free(void *arg)
+{
+       XFREE(MTYPE_NB_CONFIG_ENTRY, arg);
+}
+
+void nb_running_set_entry(const struct lyd_node *dnode, void *entry)
+{
+       struct nb_config_entry *config, s;
+
+       yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+       config = hash_get(running_config_entries, &s,
+                         running_config_entry_alloc);
+       config->entry = entry;
+}
+
+static void *nb_running_unset_entry_helper(const struct lyd_node *dnode)
+{
+       struct nb_config_entry *config, s;
+       struct lyd_node *child;
+       void *entry = NULL;
+
+       yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+       config = hash_release(running_config_entries, &s);
+       if (config) {
+               entry = config->entry;
+               running_config_entry_free(config);
+       }
+
+       /* Unset user pointers from the child nodes. */
+       if (CHECK_FLAG(dnode->schema->nodetype, LYS_LIST | LYS_CONTAINER)) {
+               LY_TREE_FOR (dnode->child, child) {
+                       (void)nb_running_unset_entry_helper(child);
+               }
+       }
+
+       return entry;
+}
+
+void *nb_running_unset_entry(const struct lyd_node *dnode)
+{
+       void *entry;
+
+       entry = nb_running_unset_entry_helper(dnode);
+       assert(entry);
+
+       return entry;
+}
+
+void *nb_running_get_entry(const struct lyd_node *dnode, const char *xpath,
+                          bool abort_if_not_found)
+{
+       const struct lyd_node *orig_dnode = dnode;
+       char xpath_buf[XPATH_MAXLEN];
+
+       assert(dnode || xpath);
+
+       if (!dnode)
+               dnode = yang_dnode_get(running_config->dnode, xpath);
+
+       while (dnode) {
+               struct nb_config_entry *config, s;
+
+               yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath));
+               config = hash_lookup(running_config_entries, &s);
+               if (config)
+                       return config->entry;
+
+               dnode = dnode->parent;
+       }
+
+       if (!abort_if_not_found)
+               return NULL;
+
+       yang_dnode_get_path(orig_dnode, xpath_buf, sizeof(xpath_buf));
+       flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+                "%s: failed to find entry [xpath %s]", __func__, xpath_buf);
+       zlog_backtrace(LOG_ERR);
+       abort();
+}
+
+/* Logging functions. */
 const char *nb_event_name(enum nb_event event)
 {
        switch (event) {
@@ -1073,8 +1773,8 @@ const char *nb_operation_name(enum nb_operation operation)
                return "create";
        case NB_OP_MODIFY:
                return "modify";
-       case NB_OP_DELETE:
-               return "delete";
+       case NB_OP_DESTROY:
+               return "destroy";
        case NB_OP_MOVE:
                return "move";
        case NB_OP_APPLY_FINISH:
@@ -1127,6 +1827,8 @@ const char *nb_client_name(enum nb_client client)
                return "ConfD";
        case NB_CLIENT_SYSREPO:
                return "Sysrepo";
+       case NB_CLIENT_GRPC:
+               return "gRPC";
        default:
                return "unknown";
        }
@@ -1153,7 +1855,8 @@ static void nb_load_callbacks(const struct frr_yang_module_info *module)
        }
 }
 
-void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
+void nb_init(struct thread_master *tm,
+            const struct frr_yang_module_info *modules[], size_t nmodules)
 {
        unsigned int errors = 0;
 
@@ -1162,14 +1865,14 @@ void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
                yang_module_load(modules[i]->name);
 
        /* Create a nb_node for all YANG schema nodes. */
-       yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL);
+       nb_nodes_create();
 
        /* Load northbound callbacks. */
        for (size_t i = 0; i < nmodules; i++)
                nb_load_callbacks(modules[i]);
 
        /* Validate northbound callbacks. */
-       yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL);
+       yang_snodes_iterate_all(nb_node_validate, 0, &errors);
        if (errors > 0) {
                flog_err(
                        EC_LIB_NB_CBS_VALIDATION,
@@ -1178,17 +1881,15 @@ void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
                exit(1);
        }
 
-       /* Initialize the northbound database (used for the rollback log). */
-       if (nb_db_init() != NB_OK)
-               flog_warn(EC_LIB_NB_DATABASE,
-                         "%s: failed to initialize northbound database",
-                         __func__);
-
        /* Create an empty running configuration. */
        running_config = nb_config_new(NULL);
+       running_config_entries = hash_create(running_config_entry_key_make,
+                                            running_config_entry_cmp,
+                                            "Running Configuration Entries");
+       pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL);
 
        /* Initialize the northbound CLI. */
-       nb_cli_init();
+       nb_cli_init(tm);
 }
 
 void nb_terminate(void)
@@ -1197,8 +1898,11 @@ void nb_terminate(void)
        nb_cli_terminate();
 
        /* Delete all nb_node's from all YANG modules. */
-       yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL);
+       nb_nodes_delete();
 
        /* Delete the running configuration. */
+       hash_clean(running_config_entries, running_config_entry_free);
+       hash_free(running_config_entries);
        nb_config_free(running_config);
+       pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
 }