]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/northbound.c
Merge pull request #5104 from opensourcerouting/route-map-nbv2
[mirror_frr.git] / lib / northbound.c
index 5e031ac2cea8ba4fdc9cbcb88f3ea0fbf5ef1572..206a88d9800650d4de56b753f73fe90c4d93771e 100644 (file)
@@ -26,6 +26,7 @@
 #include "command.h"
 #include "debug.h"
 #include "db.h"
+#include "frr_pthread.h"
 #include "northbound.h"
 #include "northbound_cli.h"
 #include "northbound_db.h"
@@ -40,6 +41,21 @@ 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.
@@ -48,9 +64,13 @@ static bool transaction_in_progress;
 
 static int nb_callback_configuration(const enum nb_event event,
                                     struct nb_config_change *change);
+static void nb_log_callback(const enum nb_event event,
+                           enum nb_operation operation, const char *xpath,
+                           const char *value);
 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,
@@ -163,7 +183,18 @@ static int nb_node_validate_cb(const struct nb_node *nb_node,
 
        valid = nb_operation_is_valid(operation, nb_node->snode);
 
-       if (!valid && callback_implemented)
+       /*
+        * Add an exception for operational data callbacks. A rw list usually
+        * doesn't need any associated operational data callbacks. But if this
+        * rw list is augmented by another module which adds state nodes under
+        * it, then this list will need to have the 'get_next()', 'get_keys()'
+        * and 'lookup_entry()' callbacks. As such, never log a warning when
+        * these callbacks are implemented when they are not needed, since this
+        * depends on context (e.g. some daemons might augment "frr-interface"
+        * while others don't).
+        */
+       if (!valid && callback_implemented && operation != NB_OP_GET_NEXT
+           && operation != NB_OP_GET_KEYS && operation != NB_OP_LOOKUP_ENTRY)
                flog_warn(EC_LIB_NB_CB_UNNEEDED,
                          "unneeded '%s' callback for '%s'",
                          nb_operation_name(operation), nb_node->xpath);
@@ -194,6 +225,8 @@ static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node)
                                     !!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_PRE_VALIDATE,
+                                    !!nb_node->cbs.pre_validate, true);
        error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH,
                                     !!nb_node->cbs.apply_finish, true);
        error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM,
@@ -315,23 +348,39 @@ static inline int nb_config_cb_compare(const struct nb_config_cb *a,
                return 1;
 
        /*
-        * Use XPath as a tie-breaker. This will naturally sort parent nodes
-        * before their children.
+        * Preserve the order of the configuration changes as told by libyang.
         */
-       return strcmp(a->xpath, b->xpath);
+       if (a->seq < b->seq)
+               return -1;
+       if (a->seq > b->seq)
+               return 1;
+
+       /*
+        * All 'apply_finish' callbacks have their sequence number set to zero.
+        * In this case, compare them using their dnode pointers (the order
+        * doesn't matter for callbacks that have the same priority).
+        */
+       if (a->dnode < b->dnode)
+               return -1;
+       if (a->dnode > b->dnode)
+               return 1;
+
+       return 0;
 }
 RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
 
 static void nb_config_diff_add_change(struct nb_config_cbs *changes,
                                      enum nb_operation operation,
+                                     uint32_t *seq,
                                      const struct lyd_node *dnode)
 {
        struct nb_config_change *change;
 
        change = XCALLOC(MTYPE_TMP, sizeof(*change));
        change->cb.operation = operation;
+       change->cb.seq = *seq;
+       *seq = *seq + 1;
        change->cb.nb_node = dnode->schema->priv;
-       yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath));
        change->cb.dnode = dnode;
 
        RB_INSERT(nb_config_cbs, changes, &change->cb);
@@ -354,7 +403,7 @@ 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_created(const struct lyd_node *dnode,
+static void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq,
                                   struct nb_config_cbs *changes)
 {
        enum nb_operation operation;
@@ -373,16 +422,17 @@ static void nb_config_diff_created(const struct lyd_node *dnode,
                else
                        return;
 
-               nb_config_diff_add_change(changes, operation, dnode);
+               nb_config_diff_add_change(changes, operation, seq, 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);
+                       nb_config_diff_add_change(changes, NB_OP_CREATE, seq,
+                                                 dnode);
 
                /* Process child nodes recursively. */
                LY_TREE_FOR (dnode->child, child) {
-                       nb_config_diff_created(child, changes);
+                       nb_config_diff_created(child, seq, changes);
                }
                break;
        default:
@@ -390,11 +440,11 @@ static void nb_config_diff_created(const struct lyd_node *dnode,
        }
 }
 
-static void nb_config_diff_deleted(const struct lyd_node *dnode,
+static void nb_config_diff_deleted(const struct lyd_node *dnode, uint32_t *seq,
                                   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);
+               nb_config_diff_add_change(changes, NB_OP_DESTROY, seq, dnode);
        else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) {
                struct lyd_node *child;
 
@@ -405,7 +455,7 @@ static void nb_config_diff_deleted(const struct lyd_node *dnode,
                 * when applicable (i.e. optional nodes).
                 */
                LY_TREE_FOR (dnode->child, child) {
-                       nb_config_diff_deleted(child, changes);
+                       nb_config_diff_deleted(child, seq, changes);
                }
        }
 }
@@ -416,6 +466,7 @@ static void nb_config_diff(const struct nb_config *config1,
                           struct nb_config_cbs *changes)
 {
        struct lyd_difflist *diff;
+       uint32_t seq = 0;
 
        diff = lyd_diff(config1->dnode, config2->dnode,
                        LYD_DIFFOPT_WITHDEFAULTS);
@@ -430,15 +481,16 @@ static void nb_config_diff(const struct nb_config *config1,
                switch (type) {
                case LYD_DIFF_CREATED:
                        dnode = diff->second[i];
-                       nb_config_diff_created(dnode, changes);
+                       nb_config_diff_created(dnode, &seq, changes);
                        break;
                case LYD_DIFF_DELETED:
                        dnode = diff->first[i];
-                       nb_config_diff_deleted(dnode, changes);
+                       nb_config_diff_deleted(dnode, &seq, changes);
                        break;
                case LYD_DIFF_CHANGED:
                        dnode = diff->second[i];
-                       nb_config_diff_add_change(changes, NB_OP_MODIFY, dnode);
+                       nb_config_diff_add_change(changes, NB_OP_MODIFY, &seq,
+                                                 dnode);
                        break;
                case LYD_DIFF_MOVEDAFTER1:
                case LYD_DIFF_MOVEDAFTER2:
@@ -478,15 +530,6 @@ int nb_candidate_edit(struct nb_config *candidate,
                                  __func__);
                        return NB_ERR;
                }
-
-               /*
-                * If a new node was created, call lyd_validate() only to create
-                * default child nodes.
-                */
-               if (dnode) {
-                       lyd_schema_sort(dnode, 0);
-                       lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx);
-               }
                break;
        case NB_OP_DESTROY:
                dnode = yang_dnode_get(candidate->dnode, xpath_edit);
@@ -540,7 +583,8 @@ int nb_candidate_update(struct nb_config *candidate)
  */
 static int nb_candidate_validate_yang(struct nb_config *candidate)
 {
-       if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG,
+       if (lyd_validate(&candidate->dnode,
+                        LYD_OPT_STRICT | LYD_OPT_CONFIG | LYD_OPT_WHENAUTODEL,
                         ly_native_ctx)
            != 0)
                return NB_ERR_VALIDATION;
@@ -549,14 +593,45 @@ static int nb_candidate_validate_yang(struct nb_config *candidate)
 }
 
 /* Perform code-level validation using the northbound callbacks. */
-static int nb_candidate_validate_changes(struct nb_config *candidate,
-                                        struct nb_config_cbs *changes)
+static int nb_candidate_validate_code(struct nb_config *candidate,
+                                     struct nb_config_cbs *changes)
 {
        struct nb_config_cb *cb;
+       struct lyd_node *root, *next, *child;
+       int ret;
+
+       /* First validate the candidate as a whole. */
+       LY_TREE_FOR (candidate->dnode, root) {
+               LY_TREE_DFS_BEGIN (root, next, child) {
+                       struct nb_node *nb_node;
+
+                       nb_node = child->schema->priv;
+                       if (!nb_node->cbs.pre_validate)
+                               goto next;
+
+                       if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config,
+                                            DEBUG_MODE_ALL)) {
+                               char xpath[XPATH_MAXLEN];
+
+                               yang_dnode_get_path(child, xpath,
+                                                   sizeof(xpath));
+                               nb_log_callback(NB_EV_VALIDATE,
+                                               NB_OP_PRE_VALIDATE, xpath,
+                                               NULL);
+                       }
+
+                       ret = (*nb_node->cbs.pre_validate)(child);
+                       if (ret != NB_OK)
+                               return NB_ERR_VALIDATION;
 
+               next:
+                       LY_TREE_DFS_END(root, next, child);
+               }
+       }
+
+       /* Now validate the configuration changes. */
        RB_FOREACH (cb, nb_config_cbs, changes) {
                struct nb_config_change *change = (struct nb_config_change *)cb;
-               int ret;
 
                ret = nb_callback_configuration(NB_EV_VALIDATE, change);
                if (ret != NB_OK)
@@ -576,14 +651,15 @@ int nb_candidate_validate(struct nb_config *candidate)
 
        RB_INIT(nb_config_cbs, &changes);
        nb_config_diff(running_config, candidate, &changes);
-       ret = nb_candidate_validate_changes(candidate, &changes);
+       ret = nb_candidate_validate_code(candidate, &changes);
        nb_config_diff_del_changes(&changes);
 
        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;
@@ -600,7 +676,7 @@ int nb_candidate_commit_prepare(struct nb_config *candidate,
        if (RB_EMPTY(nb_config_cbs, &changes))
                return NB_ERR_NO_CHANGES;
 
-       if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) {
+       if (nb_candidate_validate_code(candidate, &changes) != NB_OK) {
                flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
                          "%s: failed to validate candidate configuration",
                          __func__);
@@ -608,7 +684,8 @@ int nb_candidate_commit_prepare(struct nb_config *candidate,
                return NB_ERR_VALIDATION;
        }
 
-       *transaction = nb_transaction_new(candidate, &changes, client, comment);
+       *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__);
@@ -645,13 +722,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
@@ -666,6 +743,54 @@ 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;
+
+       frr_with_mutex(&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;
+               }
+       }
+
+       return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+       int ret = -1;
+
+       frr_with_mutex(&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;
+               }
+       }
+
+       return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+       int ret = -1;
+
+       frr_with_mutex(&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;
+       }
+
+       return ret;
+}
+
 static void nb_log_callback(const enum nb_event event,
                            enum nb_operation operation, const char *xpath,
                            const char *value)
@@ -673,7 +798,7 @@ static void nb_log_callback(const enum nb_event event,
        zlog_debug(
                "northbound callback: event [%s] op [%s] xpath [%s] value [%s]",
                nb_event_name(event), nb_operation_name(operation), xpath,
-               value);
+               value ? value : "(NULL)");
 }
 
 /*
@@ -684,7 +809,7 @@ static int nb_callback_configuration(const enum nb_event event,
                                     struct nb_config_change *change)
 {
        enum nb_operation operation = change->cb.operation;
-       const char *xpath = change->cb.xpath;
+       char xpath[XPATH_MAXLEN];
        const struct nb_node *nb_node = change->cb.nb_node;
        const struct lyd_node *dnode = change->cb.dnode;
        union nb_resource *resource;
@@ -696,6 +821,7 @@ static int nb_callback_configuration(const enum nb_event event,
                if (dnode && !yang_snode_is_typeless_data(dnode->schema))
                        value = yang_dnode_get_string(dnode, NULL);
 
+               yang_dnode_get_path(dnode, xpath, sizeof(xpath));
                nb_log_callback(event, operation, xpath, value);
        }
 
@@ -718,6 +844,7 @@ static int nb_callback_configuration(const enum nb_event event,
                ret = (*nb_node->cbs.move)(event, dnode);
                break;
        default:
+               yang_dnode_get_path(dnode, xpath, sizeof(xpath));
                flog_err(EC_LIB_DEVELOPMENT,
                         "%s: unknown operation (%u) [xpath %s]", __func__,
                         operation, xpath);
@@ -728,6 +855,8 @@ static int nb_callback_configuration(const enum nb_event event,
                int priority;
                enum lib_log_refs ref;
 
+               yang_dnode_get_path(dnode, xpath, sizeof(xpath));
+
                switch (event) {
                case NB_EV_VALIDATE:
                        priority = LOG_WARNING;
@@ -812,13 +941,20 @@ int nb_callback_rpc(const struct nb_node *nb_node, const char *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 char *comment)
+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,
@@ -891,14 +1027,12 @@ static int nb_transaction_process(enum nb_event event,
 }
 
 static struct nb_config_cb *
-nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath,
-                      const struct nb_node *nb_node,
+nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const struct nb_node *nb_node,
                       const struct lyd_node *dnode)
 {
        struct nb_config_cb *cb;
 
        cb = XCALLOC(MTYPE_TMP, sizeof(*cb));
-       strlcpy(cb->xpath, xpath, sizeof(cb->xpath));
        cb->nb_node = nb_node;
        cb->dnode = dnode;
        RB_INSERT(nb_config_cbs, cbs, cb);
@@ -907,13 +1041,15 @@ nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath,
 }
 
 static struct nb_config_cb *
-nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath,
-                       const struct nb_node *nb_node)
+nb_apply_finish_cb_find(struct nb_config_cbs *cbs,
+                       const struct nb_node *nb_node,
+                       const struct lyd_node *dnode)
 {
        struct nb_config_cb s;
 
-       strlcpy(s.xpath, xpath, sizeof(s.xpath));
+       s.seq = 0;
        s.nb_node = nb_node;
+       s.dnode = dnode;
        return RB_FIND(nb_config_cbs, cbs, &s);
 }
 
@@ -922,6 +1058,7 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
 {
        struct nb_config_cbs cbs;
        struct nb_config_cb *cb;
+       char xpath[XPATH_MAXLEN];
 
        /* Initialize tree of 'apply_finish' callbacks. */
        RB_INIT(nb_config_cbs, &cbs);
@@ -938,8 +1075,6 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
                 * be called though).
                 */
                if (change->cb.operation == NB_OP_DESTROY) {
-                       char xpath[XPATH_MAXLEN];
-
                        dnode = dnode->parent;
                        if (!dnode)
                                break;
@@ -955,7 +1090,6 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
                                               xpath);
                }
                while (dnode) {
-                       char xpath[XPATH_MAXLEN];
                        struct nb_node *nb_node;
 
                        nb_node = dnode->schema->priv;
@@ -966,11 +1100,10 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
                         * Don't call the callback more than once for the same
                         * data node.
                         */
-                       yang_dnode_get_path(dnode, xpath, sizeof(xpath));
-                       if (nb_apply_finish_cb_find(&cbs, xpath, nb_node))
+                       if (nb_apply_finish_cb_find(&cbs, nb_node, dnode))
                                goto next;
 
-                       nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode);
+                       nb_apply_finish_cb_new(&cbs, nb_node, dnode);
 
                next:
                        dnode = dnode->parent;
@@ -979,9 +1112,11 @@ 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_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL))
-                       nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH,
-                                       cb->xpath, NULL);
+               if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) {
+                       yang_dnode_get_path(cb->dnode, xpath, sizeof(xpath));
+                       nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH, xpath,
+                                       NULL);
+               }
 
                (*cb->nb_node->cbs.apply_finish)(cb->dnode);
        }
@@ -1189,9 +1324,27 @@ static int nb_oper_data_iter_node(const struct lys_node *snode,
 
        /* 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);
+       if (!first && snode->nodetype != LYS_USES) {
+               struct lys_node *parent;
+
+               /* Get the real parent. */
+               parent = snode->parent;
+               while (parent && parent->nodetype == LYS_USES)
+                       parent = parent->parent;
+
+               /*
+                * When necessary, include the namespace of the augmenting
+                * module.
+                */
+               if (parent && parent->nodetype == LYS_AUGMENT)
+                       snprintf(xpath + strlen(xpath),
+                                sizeof(xpath) - strlen(xpath), "/%s:%s",
+                                snode->module->name, snode->name);
+               else
+                       snprintf(xpath + strlen(xpath),
+                                sizeof(xpath) - strlen(xpath), "/%s",
+                                snode->name);
+       }
 
        nb_node = snode->priv;
        switch (snode->nodetype) {
@@ -1260,19 +1413,12 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
         */
        ly_errno = 0;
        dnode = lyd_new_path(NULL, ly_native_ctx, xpath, NULL, 0,
-                            LYD_PATH_OPT_UPDATE);
-       if (!dnode && ly_errno) {
+                            LYD_PATH_OPT_UPDATE | LYD_PATH_OPT_NOPARENTRET);
+       if (!dnode) {
                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.
@@ -1436,6 +1582,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
                        return false;
                }
                return true;
+       case NB_OP_PRE_VALIDATE:
        case NB_OP_APPLY_FINISH:
                if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
@@ -1531,7 +1678,7 @@ static bool running_config_entry_cmp(const void *value1, const void *value2)
        return strmatch(c1->xpath, c2->xpath);
 }
 
-static unsigned int running_config_entry_key_make(void *value)
+static unsigned int running_config_entry_key_make(const void *value)
 {
        return string_hash_make(value);
 }
@@ -1654,6 +1801,8 @@ const char *nb_operation_name(enum nb_operation operation)
                return "destroy";
        case NB_OP_MOVE:
                return "move";
+       case NB_OP_PRE_VALIDATE:
+               return "pre_validate";
        case NB_OP_APPLY_FINISH:
                return "apply_finish";
        case NB_OP_GET_ELEM:
@@ -1704,6 +1853,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";
        }
@@ -1731,7 +1882,8 @@ static void nb_load_callbacks(const struct frr_yang_module_info *module)
 }
 
 void nb_init(struct thread_master *tm,
-            const struct frr_yang_module_info *modules[], size_t nmodules)
+            const struct frr_yang_module_info *const modules[],
+            size_t nmodules)
 {
        unsigned int errors = 0;
 
@@ -1761,6 +1913,7 @@ void nb_init(struct thread_master *tm,
        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(tm);
@@ -1778,4 +1931,5 @@ void nb_terminate(void)
        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);
 }