]> git.puffer.fish Git - matthieu/frr.git/commitdiff
mgmtd: Add MGMT Transaction Framework
authorYash Ranjan <ranjany@vmware.com>
Thu, 28 Oct 2021 07:07:11 +0000 (00:07 -0700)
committerChristian Hopps <chopps@labn.net>
Wed, 22 Mar 2023 02:08:32 +0000 (22:08 -0400)
This commit introduces the MGMT Transaction framework that takes
management requests from one (or more) frontend client sessions,
translates them into transactions and drives them to completion
in co-oridination with one (or more) backend client daemons
involved in the request.

This commit includes the following functionalities in the changeset:
1. Introduces the actual Transaction module. Commands added related to
   transaction are:
   a. show mgmt transaction all
2. Adds support for commit rollback feature which stores upto the 10
   commit buffers. Each commit has a commit-id which can be used to
   rollback to the exact configuration state.
   Commands supported for this feature are:
   a. show mgmt commit-history
   b. mgmt rollback commit-id COMMIT_ID
3. Add hidden commands to enable record various performance metrics:
   a. mgmt performance-measurement
   b. mgmt reset-statistic

Co-authored-by: Pushpasis Sarkar <pushpasis@gmail.com>
Co-authored-by: Abhinay Ramesh <rabhinay@vmware.com>
Co-authored-by: Ujwal P <ujwalp@vmware.com>
Signed-off-by: Yash Ranjan <ranjany@vmware.com>
21 files changed:
.clang-format
configure.ac
lib/northbound.c
lib/northbound.h
lib/northbound_cli.c
lib/yang.c
mgmtd/mgmt.c
mgmtd/mgmt.h
mgmtd/mgmt_be_adapter.c
mgmtd/mgmt_defines.h
mgmtd/mgmt_ds.c
mgmtd/mgmt_ds.h
mgmtd/mgmt_fe_adapter.c
mgmtd/mgmt_history.c [new file with mode: 0644]
mgmtd/mgmt_history.h [new file with mode: 0644]
mgmtd/mgmt_memory.c
mgmtd/mgmt_memory.h
mgmtd/mgmt_txn.c [new file with mode: 0644]
mgmtd/mgmt_txn.h [new file with mode: 0644]
mgmtd/mgmt_vty.c
mgmtd/subdir.am

index 01e909d930cb089ae12d8a3bb848e35362fc92b4..1b18323348976628c99ae7494f4fa3b8e3b756f0 100644 (file)
@@ -82,6 +82,10 @@ ForEachMacros:
   # ospfd
   - LSDB_LOOP
   # mgmtd
+  - FOREACH_CMT_REC
+  - FOREACH_TXN_CFG_BATCH_IN_LIST
+  - FOREACH_TXN_REQ_IN_LIST
+  - FOREACH_TXN_IN_LIST
   - FOREACH_MGMTD_DB_ID
   - FOREACH_ADAPTER_IN_LIST
   - FOREACH_SESSION_IN_LIST
index 7d8b73ddd637a35abab82e8a05a6d104d0609d04..4b07536337395f479c32497cd27affb0d1157515 100644 (file)
@@ -618,6 +618,8 @@ AC_ARG_ENABLE([bgpd],
   AS_HELP_STRING([--disable-bgpd], [do not build bgpd]))
 AC_ARG_ENABLE([mgmtd],
   AS_HELP_STRING([--disable-mgmtd], [do not build mgmtd]))
+AC_ARG_ENABLE([mgmtd_local_validations],
+  AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation]))
 AC_ARG_ENABLE([ripd],
   AS_HELP_STRING([--disable-ripd], [do not build ripd]))
 AC_ARG_ENABLE([ripngd],
@@ -1732,6 +1734,11 @@ AS_IF([test "$enable_bgpd" != "no"], [
 AS_IF([test "$enable_mgmtd" != "no"], [
 
   AC_DEFINE([HAVE_MGMTD], [1], [mgmtd])
+
+  # Enable MGMTD local validations
+  AS_IF([test "$enable_mgmtd_local_validations" == "yes"], [
+    AC_DEFINE([MGMTD_LOCAL_VALIDATIONS_ENABLED], [1], [Enable mgmtd local validations.])
+  ])
 ])
 
 AS_IF([test "$enable_ripd" != "no"], [
index b9b840a60d3ae8227ae533fa0bae70a44ca52977..b91308fb1a8e6b789ee1cb1923332287fc01eaa2 100644 (file)
@@ -943,11 +943,12 @@ int nb_candidate_update(struct nb_config *candidate)
  * WARNING: lyd_validate() can change the configuration as part of the
  * validation process.
  */
-int nb_candidate_validate_yang(struct nb_config *candidate, char *errmsg,
-                              size_t errmsg_len)
+int nb_candidate_validate_yang(struct nb_config *candidate, bool no_state,
+                              char *errmsg, size_t errmsg_len)
 {
        if (lyd_validate_all(&candidate->dnode, ly_native_ctx,
-                            LYD_VALIDATE_NO_STATE, NULL)
+                            no_state ? LYD_VALIDATE_NO_STATE :
+                            LYD_VALIDATE_PRESENT, NULL)
            != 0) {
                yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
                return NB_ERR_VALIDATION;
@@ -1003,7 +1004,8 @@ int nb_candidate_diff_and_validate_yang(struct nb_context *context,
                                        struct nb_config_cbs *changes,
                                        char *errmsg, size_t errmsg_len)
 {
-       if (nb_candidate_validate_yang(candidate, errmsg, sizeof(errmsg_len))
+       if (nb_candidate_validate_yang(candidate, true, errmsg,
+                                      sizeof(errmsg_len))
            != NB_OK)
                return NB_ERR_VALIDATION;
 
@@ -1042,7 +1044,7 @@ int nb_candidate_commit_prepare(struct nb_context context,
        struct nb_config_cbs changes;
 
        if (!skip_validate
-           && nb_candidate_validate_yang(candidate, errmsg, errmsg_len)
+           && nb_candidate_validate_yang(candidate, true, errmsg, errmsg_len)
                       != NB_OK) {
                flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
                          "%s: failed to validate candidate configuration",
index b63b216b0d6604785d0e04f852a9d249d82b1f11..96f257c8d9aaf47acca8e894ed2978ac03487dcf 100644 (file)
@@ -875,6 +875,19 @@ extern int nb_candidate_edit(struct nb_config *candidate,
                             const struct yang_data *previous,
                             const struct yang_data *data);
 
+/*
+ * Create diff for configuration.
+ *
+ * dnode
+ *    Pointer to a libyang data node containing the configuration data. If NULL
+ *    is given, an empty configuration will be created.
+ *
+ * seq
+ *    Returns sequence number assigned to the specific change.
+ *
+ * changes
+ *    Northbound config callback head.
+ */
 extern void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq,
                                   struct nb_config_cbs *changes);
 
@@ -889,25 +902,134 @@ extern void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq,
  */
 extern bool nb_candidate_needs_update(const struct nb_config *candidate);
 
+/*
+ * Edit candidate configuration changes.
+ *
+ * candidate_config
+ *    Candidate configuration to edit.
+ *
+ * cfg_changes
+ *    Northbound config changes.
+ *
+ * num_cfg_changes
+ *    Number of config changes.
+ *
+ * xpath_base
+ *    Base xpath for config.
+ *
+ * curr_xpath
+ *    Current xpath for config.
+ *
+ * xpath_index
+ *    Index of xpath being processed.
+ *
+ * err_buf
+ *    Buffer to store human-readable error message in case of error.
+ *
+ * err_bufsize
+ *    Size of err_buf.
+ *
+ * error
+ *    TRUE on error, FALSE on success
+ */
 extern void nb_candidate_edit_config_changes(
        struct nb_config *candidate_config, struct nb_cfg_change cfg_changes[],
        size_t num_cfg_changes, const char *xpath_base, const char *curr_xpath,
        int xpath_index, char *err_buf, int err_bufsize, bool *error);
 
+/*
+ * Delete candidate configuration changes.
+ *
+ * changes
+ *    Northbound config changes.
+ */
 extern void nb_config_diff_del_changes(struct nb_config_cbs *changes);
 
+/*
+ * Create candidate diff and validate on yang tree
+ *
+ * context
+ *    Context of the northbound transaction.
+ *
+ * candidate
+ *    Candidate DB configuration.
+ *
+ * changes
+ *    Northbound config changes.
+ *
+ * errmsg
+ *    Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ *    Size of errmsg.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR_VALIDATION otherwise
+ */
 extern int nb_candidate_diff_and_validate_yang(struct nb_context *context,
                                               struct nb_config *candidate,
                                               struct nb_config_cbs *changes,
                                               char *errmsg, size_t errmsg_len);
 
+/*
+ * Calculate the delta between two different configurations.
+ *
+ * reference
+ *    Running DB config changes to be compared against.
+ *
+ * incremental
+ *    Candidate DB config changes that will be compared against reference.
+ *
+ * changes
+ *    Will hold the final diff generated.
+ *
+ */
 extern void nb_config_diff(const struct nb_config *reference,
                           const struct nb_config *incremental,
                           struct nb_config_cbs *changes);
 
-extern int nb_candidate_validate_yang(struct nb_config *candidate, char *errmsg,
-                                     size_t errmsg_len);
+/*
+ * Perform YANG syntactic and semantic validation.
+ *
+ * WARNING: lyd_validate() can change the configuration as part of the
+ * validation process.
+ *
+ * candidate
+ *    Candidate DB configuration.
+ *
+ * errmsg
+ *    Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ *    Size of errmsg.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR_VALIDATION otherwise
+ */
+extern int nb_candidate_validate_yang(struct nb_config *candidate, bool no_state,
+                                     char *errmsg, size_t errmsg_len);
 
+/*
+ * Perform code-level validation using the northbound callbacks.
+ *
+ * context
+ *    Context of the northbound transaction.
+ *
+ * candidate
+ *    Candidate DB configuration.
+ *
+ * changes
+ *    Northbound config changes.
+ *
+ * errmsg
+ *    Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ *    Size of errmsg.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR_VALIDATION otherwise
+ */
 extern int nb_candidate_validate_code(struct nb_context *context,
                                      struct nb_config *candidate,
                                      struct nb_config_cbs *changes,
@@ -972,6 +1094,12 @@ extern int nb_candidate_validate(struct nb_context *context,
  *    nb_candidate_commit_abort() or committed using
  *    nb_candidate_commit_apply().
  *
+ * skip_validate
+ *    TRUE to skip commit validation, FALSE otherwise.
+ *
+ * ignore_zero_change
+ *    TRUE to ignore if zero changes, FALSE otherwise.
+ *
  * errmsg
  *    Buffer to store human-readable error message in case of error.
  *
index 2ead14cc39ecf41ece1713c5cbd3bb3982a69108..523b383c629bfb5102304ff02319a47d4995e423 100644 (file)
@@ -192,6 +192,12 @@ int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
                vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
                va_end(ap);
        }
+
+       if (vty_mgmt_fe_enabled()) {
+               VTY_CHECK_XPATH;
+               return vty_mgmt_send_config_data(vty);
+       }
+
        return nb_cli_apply_changes_internal(vty, xpath_base, false);
 }
 
@@ -208,6 +214,12 @@ int nb_cli_apply_changes_clear_pending(struct vty *vty,
                vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
                va_end(ap);
        }
+
+       if (vty_mgmt_fe_enabled()) {
+               VTY_CHECK_XPATH;
+               return vty_mgmt_send_config_data(vty);
+       }
+
        return nb_cli_apply_changes_internal(vty, xpath_base, true);
 }
 
index 78738f7d4d5d404172df4c78d7248d3ceb650487..70a3251ab39db4280c8c3a904fac85d7febee71b 100644 (file)
@@ -395,7 +395,12 @@ struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, const char *xpath)
                xpath += 2;
 
        if (lyd_find_xpath(dnode, xpath, &set)) {
-               assert(0); /* XXX replicates old libyang1 base code */
+               /*
+                * Commenting out the below assert failure as it crashes mgmtd
+                * when bad xpath is passed.
+                *
+                * assert(0);  XXX replicates old libyang1 base code
+                */
                goto exit;
        }
        if (set->count == 0)
index 34db900afa5666268a5581d6f647cef9d0ae038e..e1acfde3903136bb5096bf74ca2ae45e473b429f 100644 (file)
@@ -13,6 +13,7 @@
 #include "mgmtd/mgmt_fe_server.h"
 #include "mgmtd/mgmt_fe_adapter.h"
 #include "mgmtd/mgmt_ds.h"
+#include "mgmtd/mgmt_history.h"
 #include "mgmtd/mgmt_memory.h"
 
 bool mgmt_debug_be;
@@ -49,6 +50,12 @@ void mgmt_init(void)
        /* Initialize datastores */
        mgmt_ds_init(mm);
 
+       /* Initialize history */
+       mgmt_history_init();
+
+       /* Initialize MGMTD Transaction module */
+       mgmt_txn_init(mm, mm->master);
+
        /* Initialize the MGMTD Backend Adapter Module */
        mgmt_be_adapter_init(mm->master);
 
@@ -61,7 +68,7 @@ void mgmt_init(void)
        /* Start the MGMTD Frontend Server for clients to connect */
        mgmt_fe_server_init(mm->master);
 
-       /* MGMTD VTY commands installation.  */
+       /* MGMTD VTY commands installation. */
        mgmt_vty_init();
 }
 
@@ -71,5 +78,7 @@ void mgmt_terminate(void)
        mgmt_fe_adapter_destroy();
        mgmt_be_server_destroy();
        mgmt_be_adapter_destroy();
+       mgmt_txn_destroy();
+       mgmt_history_destroy();
        mgmt_ds_destroy();
 }
index ad9f03c4222347d4381b5398e11bf9a38ab0ce09..2a9d947517071b86c65f0ec91226c6c5f9bdd9c5 100644 (file)
 #define _FRR_MGMTD_H
 
 #include "vrf.h"
-
 #include "defaults.h"
 #include "stream.h"
 
 #include "mgmtd/mgmt_memory.h"
+#include "mgmtd/mgmt_defines.h"
+#include "mgmtd/mgmt_history.h"
+#include "mgmtd/mgmt_txn.h"
 #include "mgmtd/mgmt_ds.h"
 
 #define MGMTD_VTY_PORT 2622
 #define MGMTD_SOCKET_BUF_SIZE 65535
+#define MGMTD_MAX_COMMIT_LIST 10
 
 extern bool mgmt_debug_be;
 extern bool mgmt_debug_fe;
 extern bool mgmt_debug_ds;
 extern bool mgmt_debug_txn;
 
+struct mgmt_txn_ctx;
+
 /*
  * MGMTD master for system wide configurations and variables.
  */
@@ -34,6 +39,16 @@ struct mgmt_master {
        /* How big should we set the socket buffer size */
        uint32_t socket_buffer;
 
+       /* The single instance of config transaction allowed at any time */
+       struct mgmt_txns_head txn_list;
+
+       /* Map of Transactions and its ID */
+       struct hash *txn_hash;
+       uint64_t next_txn_id;
+
+       /* The single instance of config transaction allowed at any time */
+       struct mgmt_txn_ctx *cfg_txn;
+
        /* Datastores */
        struct mgmt_ds_ctx *running_ds;
        struct mgmt_ds_ctx *candidate_ds;
@@ -41,6 +56,9 @@ struct mgmt_master {
 
        bool terminating;   /* global flag that sigint terminate seen */
        bool perf_stats_en; /* to enable performance stats measurement */
+
+       /* List of commit infos */
+       struct mgmt_cmt_infos_head cmts; /* List of last 10 commits executed. */
 };
 
 extern struct mgmt_master *mm;
@@ -86,16 +104,12 @@ extern void mgmt_vty_init(void);
 static inline char *mgmt_realtime_to_string(struct timeval *tv, char *buf,
                                            size_t sz)
 {
-       char tmp[50];
-       struct tm *lm;
-
-       lm = localtime((const time_t *)&tv->tv_sec);
-       if (lm) {
-               strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", lm);
-               snprintf(buf, sz, "%s.%06lu", tmp,
-                        (unsigned long int)tv->tv_usec);
-       }
+       struct tm tm;
+       size_t n;
 
+       localtime_r((const time_t *)&tv->tv_sec, &tm);
+       n = strftime(buf, sz, "%Y-%m-%dT%H:%M:%S", &tm);
+       snprintf(&buf[n], sz - n, ",%06u000", (unsigned int)tv->tv_usec);
        return buf;
 }
 
index f5385d218eecaf6bbf9486f65516b66b48feb45c..b47ed3afe18895414627c6086b500bff176f93eb 100644 (file)
@@ -332,9 +332,9 @@ static void mgmt_be_adapter_disconnect(struct mgmt_be_client_adapter *adapter)
        }
 
        /*
-        * TODO: Notify about client disconnect for appropriate cleanup
-        * mgmt_txn_notify_be_adapter_conn(adapter, false);
+        * Notify about client disconnect for appropriate cleanup
         */
+       mgmt_txn_notify_be_adapter_conn(adapter, false);
 
        if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) {
                mgmt_be_adapters_by_id[adapter->id] = NULL;
@@ -402,12 +402,12 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter,
                        adapter->name,
                        be_msg->txn_reply->success ? "success" : "failure");
                /*
-                * TODO: Forward the TXN_REPLY to txn module.
-                * mgmt_txn_notify_be_txn_reply(
-                *      be_msg->txn_reply->txn_id,
-                *      be_msg->txn_reply->create,
-                *      be_msg->txn_reply->success, adapter);
+                * Forward the TXN_REPLY to txn module.
                 */
+               mgmt_txn_notify_be_txn_reply(
+                       be_msg->txn_reply->txn_id,
+                       be_msg->txn_reply->create,
+                       be_msg->txn_reply->success, adapter);
                break;
        case MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REPLY:
                MGMTD_BE_ADAPTER_DBG(
@@ -419,13 +419,13 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter,
                                ? be_msg->cfg_data_reply->error_if_any
                                : "None");
                /*
-                * TODO: Forward the CGFData-create reply to txn module.
-                * mgmt_txn_notify_be_cfgdata_reply(
-                *      be_msg->cfg_data_reply->txn_id,
-                *      be_msg->cfg_data_reply->batch_id,
-                *      be_msg->cfg_data_reply->success,
-                *      be_msg->cfg_data_reply->error_if_any, adapter);
+                * Forward the CGFData-create reply to txn module.
                 */
+               mgmt_txn_notify_be_cfgdata_reply(
+                       be_msg->cfg_data_reply->txn_id,
+                       be_msg->cfg_data_reply->batch_id,
+                       be_msg->cfg_data_reply->success,
+                       be_msg->cfg_data_reply->error_if_any, adapter);
                break;
        case MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REPLY:
                MGMTD_BE_ADAPTER_DBG(
@@ -445,14 +445,15 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter,
                        be_msg->cfg_apply_reply->error_if_any
                                ? be_msg->cfg_apply_reply->error_if_any
                                : "None");
-               /* TODO: Forward the CGFData-apply reply to txn module.
-                * mgmt_txn_notify_be_cfg_apply_reply(
-                *      be_msg->cfg_apply_reply->txn_id,
-                *      be_msg->cfg_apply_reply->success,
-                *      (uint64_t *)be_msg->cfg_apply_reply->batch_ids,
-                *      be_msg->cfg_apply_reply->n_batch_ids,
-                *      be_msg->cfg_apply_reply->error_if_any, adapter);
+               /*
+                * Forward the CGFData-apply reply to txn module.
                 */
+               mgmt_txn_notify_be_cfg_apply_reply(
+                       be_msg->cfg_apply_reply->txn_id,
+                       be_msg->cfg_apply_reply->success,
+                       (uint64_t *)be_msg->cfg_apply_reply->batch_ids,
+                       be_msg->cfg_apply_reply->n_batch_ids,
+                       be_msg->cfg_apply_reply->error_if_any, adapter);
                break;
        case MGMTD__BE_MESSAGE__MESSAGE_GET_REPLY:
        case MGMTD__BE_MESSAGE__MESSAGE_CFG_CMD_REPLY:
@@ -899,27 +900,26 @@ static void mgmt_be_adapter_conn_init(struct thread *thread)
        assert(adapter && adapter->conn_fd >= 0);
 
        /*
-        * TODO: Check first if the current session can run a CONFIG
+        * Check first if the current session can run a CONFIG
         * transaction or not. Reschedule if a CONFIG transaction
         * from another session is already in progress.
+        */
        if (mgmt_config_txn_in_progress() != MGMTD_SESSION_ID_NONE) {
                mgmt_be_adapter_register_event(adapter, MGMTD_BE_CONN_INIT);
-               return 0;
+               return;
        }
-        */
 
-    /*
-     * TODO: Notify TXN module to create a CONFIG transaction and
-     * download the CONFIGs identified for this new client.
-     * If the TXN module fails to initiate the CONFIG transaction
-     * disconnect from the client forcing a reconnect later.
-     * That should also take care of destroying the adapter.
-     *
+       /*
+        * Notify TXN module to create a CONFIG transaction and
+        * download the CONFIGs identified for this new client.
+        * If the TXN module fails to initiate the CONFIG transaction
+        * disconnect from the client forcing a reconnect later.
+        * That should also take care of destroying the adapter.
+        */
        if (mgmt_txn_notify_be_adapter_conn(adapter, true) != 0) {
                mgmt_be_adapter_disconnect(adapter);
                adapter = NULL;
        }
-     */
 }
 
 static void
index 2af8f3f553854a07237482cc1c82bf7a82913fe6..ee2f376f484c669673729126910d6739292bd66b 100644 (file)
@@ -20,6 +20,7 @@
 #define MGMTD_MAX_NUM_XPATH_REG 128
 
 #define MGMTD_MAX_NUM_DATA_REQ_IN_BATCH 32
+#define MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH 8
 
 enum mgmt_result {
        MGMTD_SUCCESS = 0,
@@ -54,4 +55,6 @@ enum mgmt_be_event {
 
 #define MGMTD_TXN_ID_NONE 0
 
+#define MGMTD_TXN_BATCH_ID_NONE 0
+
 #endif /* _FRR_MGMTD_DEFINES_H */
index 2f4e7f8492b7e1c2b2afb07284422b484d8ff1c6..1724afb1829a7617518802fdfd265c24406a4f43 100644 (file)
@@ -11,6 +11,8 @@
 #include "mgmtd/mgmt.h"
 #include "mgmtd/mgmt_memory.h"
 #include "mgmtd/mgmt_ds.h"
+#include "mgmtd/mgmt_history.h"
+#include "mgmtd/mgmt_txn.h"
 #include "libyang/libyang.h"
 
 #ifdef REDIRECT_DEBUG_TO_STDERR
@@ -22,7 +24,7 @@
 #define MGMTD_DS_DBG(fmt, ...)                                                 \
        do {                                                                   \
                if (mgmt_debug_ds)                                             \
-                       zlog_debug("%s: " fmt, __func__, ##__VA_ARGS__);       \
+                       zlog_err("%s: " fmt, __func__, ##__VA_ARGS__);         \
        } while (0)
 #define MGMTD_DS_ERR(fmt, ...)                                                 \
        zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
@@ -107,6 +109,14 @@ static int mgmt_ds_replace_dst_with_src_ds(struct mgmt_ds_ctx *src,
        else
                dst->root.dnode_root = dst_dnode;
 
+       if (src->ds_id == MGMTD_DS_CANDIDATE) {
+               /*
+                * Drop the changes in scratch-buffer.
+                */
+               MGMTD_DS_DBG("Emptying Candidate Scratch buffer!");
+               nb_config_diff_del_changes(&src->root.cfg_root->cfg_chgs);
+       }
+
        if (dst->ds_id == MGMTD_DS_RUNNING) {
                if (ly_out_new_filepath(MGMTD_STARTUP_DS_FILE_PATH, &out)
                    == LY_SUCCESS)
@@ -141,6 +151,14 @@ static int mgmt_ds_merge_src_with_dst_ds(struct mgmt_ds_ctx *src,
                return ret;
        }
 
+       if (src->ds_id == MGMTD_DS_CANDIDATE) {
+               /*
+                * Drop the changes in scratch-buffer.
+                */
+               MGMTD_DS_DBG("Emptying Candidate Scratch buffer!");
+               nb_config_diff_del_changes(&src->root.cfg_root->cfg_chgs);
+       }
+
        if (dst->ds_id == MGMTD_DS_RUNNING) {
                if (ly_out_new_filepath(MGMTD_STARTUP_DS_FILE_PATH, &out)
                    == LY_SUCCESS)
@@ -169,6 +187,17 @@ static int mgmt_ds_load_cfg_from_file(const char *filepath,
        return 0;
 }
 
+void mgmt_ds_reset_candidate(void)
+{
+       struct lyd_node *dnode = mm->candidate_ds->root.cfg_root->dnode;
+       if (dnode)
+               yang_dnode_free(dnode);
+
+       dnode = yang_dnode_new(ly_native_ctx, true);
+       mm->candidate_ds->root.cfg_root->dnode = dnode;
+}
+
+
 int mgmt_ds_init(struct mgmt_master *mm)
 {
        struct lyd_node *root;
@@ -194,6 +223,12 @@ int mgmt_ds_init(struct mgmt_master *mm)
        candidate.config_ds = true;
        candidate.ds_id = MGMTD_DS_CANDIDATE;
 
+       /*
+        * Redirect lib/vty candidate-config datastore to the global candidate
+        * config Ds on the MGMTD process.
+        */
+       vty_mgmt_candidate_config = candidate.root.cfg_root;
+
        oper.root.dnode_root = yang_dnode_new(ly_native_ctx, true);
        oper.config_ds = false;
        oper.ds_id = MGMTD_DS_OPERATIONAL;
@@ -208,7 +243,6 @@ int mgmt_ds_init(struct mgmt_master *mm)
 
 void mgmt_ds_destroy(void)
 {
-
        /*
         * TODO: Free the datastores.
         */
@@ -277,21 +311,15 @@ int mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx)
        return 0;
 }
 
-int mgmt_ds_merge_dss(struct mgmt_ds_ctx *src_ds_ctx,
-                     struct mgmt_ds_ctx *dst_ds_ctx, bool updt_cmt_rec)
-{
-       if (mgmt_ds_merge_src_with_dst_ds(src_ds_ctx, dst_ds_ctx) != 0)
-               return -1;
-
-       return 0;
-}
-
 int mgmt_ds_copy_dss(struct mgmt_ds_ctx *src_ds_ctx,
                     struct mgmt_ds_ctx *dst_ds_ctx, bool updt_cmt_rec)
 {
        if (mgmt_ds_replace_dst_with_src_ds(src_ds_ctx, dst_ds_ctx) != 0)
                return -1;
 
+       if (updt_cmt_rec && dst_ds_ctx->ds_id == MGMTD_DS_RUNNING)
+               mgmt_history_new_record(dst_ds_ctx);
+
        return 0;
 }
 
@@ -377,14 +405,16 @@ static int mgmt_walk_ds_nodes(
                num_left--;
        }
 
-       /* If the base_xpath points to leaf node, we can skip the tree walk */
-       if (base_dnode->schema->nodetype & LYD_NODE_TERM)
+       /*
+        * If the base_xpath points to a leaf node, or we don't need to
+        * visit any children we can skip the tree walk.
+        */
+       if (!childs_as_well || base_dnode->schema->nodetype & LYD_NODE_TERM)
                return 0;
 
        indx = 0;
        LY_LIST_FOR (lyd_child(base_dnode), dnode) {
                assert(dnode->schema && dnode->schema->priv);
-               nbnode = (struct nb_node *)dnode->schema->priv;
 
                xpath = NULL;
                if (xpaths) {
@@ -407,9 +437,6 @@ static int mgmt_walk_ds_nodes(
                assert(xpath);
                MGMTD_DS_DBG(" -- XPATH: %s", xpath);
 
-               if (!childs_as_well)
-                       continue;
-
                if (num_nodes)
                        num_found = num_left;
 
index 7a6737cd2feebed2676729260cb754683b70734d..89a2ea94259f796202ce3e1a6179e57c06714f3a 100644 (file)
@@ -13,6 +13,8 @@
 #include "northbound.h"
 
 #include "mgmtd/mgmt_defines.h"
+#include "mgmtd/mgmt_be_adapter.h"
+#include "mgmtd/mgmt_fe_adapter.h"
 
 #define MGMTD_MAX_NUM_DSNODES_PER_BATCH 128
 
 #define MGMTD_COMMIT_INDEX_FILE_NAME DAEMON_DB_DIR "/commit-index.dat"
 #define MGMTD_COMMIT_TIME_STR_LEN 100
 
-struct mgmt_master;
-
 extern struct nb_config *running_config;
 
 struct mgmt_ds_ctx;
 
-typedef void (*mgmt_ds_node_iter_fn)(uint64_t ds_hndl, char *xpath,
-                                    struct lyd_node *node,
-                                    struct nb_node *nb_node, void *ctx);
-
 /***************************************************************
  * Global data exported
  ***************************************************************/
@@ -202,25 +198,6 @@ extern int mgmt_ds_write_lock(struct mgmt_ds_ctx *ds_ctx);
  */
 extern int mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx);
 
-/*
- * Merge two datastores.
- *
- * src_ds
- *    Source datastore handle.
- *
- * dst_ds
- *    Destination datastore handle.
- *
- * update_cmd_rec
- *    TRUE if need to update commit record, FALSE otherwise.
- *
- * Returns:
- *    0 on success, -1 on failure.
- */
-extern int mgmt_ds_merge_dss(struct mgmt_ds_ctx *src_ds_ctx,
-                            struct mgmt_ds_ctx *dst_ds_ctx,
-                            bool update_cmt_rec);
-
 /*
  * Copy from source to destination datastore.
  *
@@ -388,4 +365,10 @@ extern void mgmt_ds_status_write_one(struct vty *vty,
  */
 extern void mgmt_ds_status_write(struct vty *vty);
 
+
+/*
+ * Reset the candidate DS to empty state
+ */
+void mgmt_ds_reset_candidate(void);
+
 #endif /* _FRR_MGMTD_DS_H_ */
index fa1e0826d17b06c204b48be0b9261cd4998bf372..1ea812c1a74a91700d7a0c4b3363236acab476dd 100644 (file)
@@ -180,10 +180,11 @@ mgmt_fe_session_cfg_txn_cleanup(struct mgmt_fe_session_ctx *session)
                }
        }
 
-       /* TODO: Destroy the actual transaction created earlier.
-        * if (session->cfg_txn_id != MGMTD_TXN_ID_NONE)
-        *      mgmt_destroy_txn(&session->cfg_txn_id);
+       /*
+        * Destroy the actual transaction created earlier.
         */
+       if (session->cfg_txn_id != MGMTD_TXN_ID_NONE)
+               mgmt_destroy_txn(&session->cfg_txn_id);
 }
 
 static void
@@ -200,10 +201,11 @@ mgmt_fe_session_show_txn_cleanup(struct mgmt_fe_session_ctx *session)
                }
        }
 
-       /* TODO: Destroy the transaction created recently.
-        * if (session->txn_id != MGMTD_TXN_ID_NONE)
-        *      mgmt_destroy_txn(&session->txn_id);
+       /*
+        * Destroy the transaction created recently.
         */
+       if (session->txn_id != MGMTD_TXN_ID_NONE)
+               mgmt_destroy_txn(&session->txn_id);
 }
 
 static void
@@ -687,9 +689,6 @@ mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
                                    &tv, &session->proc_show_txn_clnp);
                assert(session->proc_show_txn_clnp);
                break;
-       default:
-               assert(!"mgmt_fe_adapter_post_event() called incorrectly");
-               break;
        }
 }
 
@@ -834,7 +833,7 @@ static int
 mgmt_fe_session_handle_setcfg_req_msg(struct mgmt_fe_session_ctx *session,
                                          Mgmtd__FeSetConfigReq *setcfg_req)
 {
-       /* uint64_t cfg_session_id; */
+       uint64_t cfg_session_id;
        struct mgmt_ds_ctx *ds_ctx, *dst_ds_ctx;
 
        if (mm->perf_stats_en)
@@ -867,20 +866,20 @@ mgmt_fe_session_handle_setcfg_req_msg(struct mgmt_fe_session_ctx *session,
 
        if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) {
                /*
-                * TODO: Check first if the current session can run a CONFIG
+                * Check first if the current session can run a CONFIG
                 * transaction or not. Report failure if a CONFIG transaction
                 * from another session is already in progress.
-                * cfg_session_id = mgmt_config_txn_in_progress();
-                * if (cfg_session_id != MGMTD_SESSION_ID_NONE
-                *   && cfg_session_id != session->session_id) {
-                *      mgmt_fe_send_setcfg_reply(
-                *              session, setcfg_req->ds_id, setcfg_req->req_id,
-                *              false,
-                *              "Configuration already in-progress through a
-                *different user session!", setcfg_req->implicit_commit); goto
-                *mgmt_fe_sess_handle_setcfg_req_failed;
-                *}
                 */
+               cfg_session_id = mgmt_config_txn_in_progress();
+               if (cfg_session_id != MGMTD_SESSION_ID_NONE
+                  && cfg_session_id != session->session_id) {
+                       mgmt_fe_send_setcfg_reply(
+                               session, setcfg_req->ds_id, setcfg_req->req_id,
+                               false,
+                               "Configuration already in-progress through a different user session!",
+                               setcfg_req->implicit_commit);
+                       goto mgmt_fe_sess_handle_setcfg_req_failed;
+               }
 
 
                /*
@@ -902,18 +901,18 @@ mgmt_fe_session_handle_setcfg_req_msg(struct mgmt_fe_session_ctx *session,
                }
 
                /*
-                * TODO: Start a CONFIG Transaction (if not started already)
-                * session->cfg_txn_id = mgmt_create_txn(session->session_id,
-                *                                    MGMTD_TXN_TYPE_CONFIG);
-                * if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
-                *      mgmt_fe_send_setcfg_reply(
-                *              session, setcfg_req->ds_id, setcfg_req->req_id,
-                *              false,
-                *              "Failed to create a Configuration session!",
-                *              setcfg_req->implicit_commit);
-                *      goto mgmt_fe_sess_handle_setcfg_req_failed;
-                * }
+                * Start a CONFIG Transaction (if not started already)
                 */
+               session->cfg_txn_id = mgmt_create_txn(session->session_id,
+                                                     MGMTD_TXN_TYPE_CONFIG);
+               if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
+                       mgmt_fe_send_setcfg_reply(
+                               session, setcfg_req->ds_id, setcfg_req->req_id,
+                               false,
+                               "Failed to create a Configuration session!",
+                               setcfg_req->implicit_commit);
+                       goto mgmt_fe_sess_handle_setcfg_req_failed;
+               }
 
                MGMTD_FE_ADAPTER_DBG(
                        "Created new Config Txn 0x%llx for session %p",
@@ -950,36 +949,31 @@ mgmt_fe_session_handle_setcfg_req_msg(struct mgmt_fe_session_ctx *session,
                }
        }
 
-       /* TODO: Create the SETConfig request under the transaction.
-        * if (mgmt_txn_send_set_config_req(
-        *      session->cfg_txn_id, setcfg_req->req_id, setcfg_req->ds_id,
-        *      ds_ctx, setcfg_req->data, setcfg_req->n_data,
-        *      setcfg_req->implicit_commit, setcfg_req->commit_ds_id,
-        *      dst_ds_ctx)
-        *      != 0) {
-        *      mgmt_fe_send_setcfg_reply(
-        *              session, setcfg_req->ds_id, setcfg_req->req_id, false,
-        *              "Request processing for SET-CONFIG failed!",
-        *              setcfg_req->implicit_commit);
-        *      goto mgmt_fe_sess_handle_setcfg_req_failed;
-        * }
-        *
-        * For now send a failure reply.
+       /*
+        * Create the SETConfig request under the transaction.
         */
-       mgmt_fe_send_setcfg_reply(
-               session, setcfg_req->ds_id, setcfg_req->req_id, false,
-               "Request processing for SET-CONFIG failed!",
-               setcfg_req->implicit_commit);
-       goto mgmt_fe_sess_handle_setcfg_req_failed;
+       if (mgmt_txn_send_set_config_req(
+                   session->cfg_txn_id, setcfg_req->req_id, setcfg_req->ds_id,
+                   ds_ctx, setcfg_req->data, setcfg_req->n_data,
+                   setcfg_req->implicit_commit, setcfg_req->commit_ds_id,
+                   dst_ds_ctx)
+           != 0) {
+               mgmt_fe_send_setcfg_reply(
+                       session, setcfg_req->ds_id, setcfg_req->req_id, false,
+                       "Request processing for SET-CONFIG failed!",
+                       setcfg_req->implicit_commit);
+               goto mgmt_fe_sess_handle_setcfg_req_failed;
+       }
 
        return 0;
 
 mgmt_fe_sess_handle_setcfg_req_failed:
 
-       /* TODO: Delete transaction created recently.
-        * if (session->cfg_txn_id != MGMTD_TXN_ID_NONE)
-        *      mgmt_destroy_txn(&session->cfg_txn_id);
+       /*
+        * Delete transaction created recently.
         */
+       if (session->cfg_txn_id != MGMTD_TXN_ID_NONE)
+               mgmt_destroy_txn(&session->cfg_txn_id);
        if (ds_ctx && session->ds_write_locked[setcfg_req->ds_id])
                mgmt_fe_session_unlock_ds(setcfg_req->ds_id, ds_ctx, session,
                                              true, false);
@@ -1042,22 +1036,17 @@ mgmt_fe_session_handle_getcfg_req_msg(struct mgmt_fe_session_ctx *session,
                }
 
                /*
-                * TODO: Start a SHOW Transaction (if not started already)
-                * session->txn_id = mgmt_create_txn(session->session_id,
-                *                              MGMTD_TXN_TYPE_SHOW);
-                * if (session->txn_id == MGMTD_SESSION_ID_NONE) {
-                *      mgmt_fe_send_getcfg_reply(
-                *              session, getcfg_req->ds_id, getcfg_req->req_id,
-                *              false, NULL,
-                *              "Failed to create a Show transaction!");
-                *      goto mgmt_fe_sess_handle_getcfg_req_failed;
-                * }
+                * Start a SHOW Transaction (if not started already)
                 */
-               mgmt_fe_send_getcfg_reply(
-                       session, getcfg_req->ds_id, getcfg_req->req_id, false,
-                       NULL, "Failed to create a Show transaction!");
-               goto mgmt_fe_sess_handle_getcfg_req_failed;
-
+               session->txn_id = mgmt_create_txn(session->session_id,
+                                                 MGMTD_TXN_TYPE_SHOW);
+               if (session->txn_id == MGMTD_SESSION_ID_NONE) {
+                       mgmt_fe_send_getcfg_reply(
+                               session, getcfg_req->ds_id, getcfg_req->req_id,
+                               false, NULL,
+                               "Failed to create a Show transaction!");
+                       goto mgmt_fe_sess_handle_getcfg_req_failed;
+               }
 
                MGMTD_FE_ADAPTER_DBG(
                        "Created new Show Txn 0x%llx for session %p",
@@ -1068,32 +1057,28 @@ mgmt_fe_session_handle_getcfg_req_msg(struct mgmt_fe_session_ctx *session,
                        (unsigned long long)session->txn_id, session);
        }
 
-       /* TODO: Create a GETConfig request under the transaction.
-        * if (mgmt_txn_send_get_config_req(session->txn_id, getcfg_req->req_id,
-        *                              getcfg_req->ds_id, ds_ctx,
-        *                              getcfg_req->data, getcfg_req->n_data)
-        *      != 0) {
-        *      mgmt_fe_send_getcfg_reply(
-        *              session, getcfg_req->ds_id, getcfg_req->req_id, false,
-        *              NULL, "Request processing for GET-CONFIG failed!");
-        *      goto mgmt_fe_sess_handle_getcfg_req_failed;
-        * }
-        *
-        * For now send back a failure reply.
+       /*
+        * Create a GETConfig request under the transaction.
         */
-       mgmt_fe_send_getcfg_reply(
-               session, getcfg_req->ds_id, getcfg_req->req_id, false, NULL,
-               "Request processing for GET-CONFIG failed!");
-       goto mgmt_fe_sess_handle_getcfg_req_failed;
+       if (mgmt_txn_send_get_config_req(session->txn_id, getcfg_req->req_id,
+                                         getcfg_req->ds_id, ds_ctx,
+                                         getcfg_req->data, getcfg_req->n_data)
+           != 0) {
+               mgmt_fe_send_getcfg_reply(
+                       session, getcfg_req->ds_id, getcfg_req->req_id, false,
+                       NULL, "Request processing for GET-CONFIG failed!");
+               goto mgmt_fe_sess_handle_getcfg_req_failed;
+       }
 
        return 0;
 
 mgmt_fe_sess_handle_getcfg_req_failed:
 
-       /* TODO: Destroy the transaction created recently.
-        * if (session->txn_id != MGMTD_TXN_ID_NONE)
-        *      mgmt_destroy_txn(&session->txn_id);
+       /*
+        * Destroy the transaction created recently.
         */
+       if (session->txn_id != MGMTD_TXN_ID_NONE)
+               mgmt_destroy_txn(&session->txn_id);
        if (ds_ctx && session->ds_read_locked[getcfg_req->ds_id])
                mgmt_fe_session_unlock_ds(getcfg_req->ds_id, ds_ctx, session,
                                              false, true);
@@ -1142,23 +1127,17 @@ mgmt_fe_session_handle_getdata_req_msg(struct mgmt_fe_session_ctx *session,
                }
 
                /*
-                * TODO: Start a SHOW Transaction (if not started already)
-                * session->txn_id =
-                *      mgmt_create_txn(session->session_id,
-                *                      MGMTD_TXN_TYPE_SHOW);
-                * if (session->txn_id == MGMTD_SESSION_ID_NONE) {
-                *      mgmt_fe_send_getdata_reply(
-                *              session, getdata_req->ds_id, getdata_req->req_id,
-                *              false, NULL,
-                *              "Failed to create a Show transaction!");
-                *      goto mgmt_fe_sess_handle_getdata_req_failed;
-                * }
+                * Start a SHOW Transaction (if not started already)
                 */
-               mgmt_fe_send_getdata_reply(
-                       session, getdata_req->ds_id, getdata_req->req_id, false,
-                       NULL, "Failed to create a Show transaction!");
-               goto mgmt_fe_sess_handle_getdata_req_failed;
-
+               session->txn_id = mgmt_create_txn(session->session_id,
+                                                 MGMTD_TXN_TYPE_SHOW);
+               if (session->txn_id == MGMTD_SESSION_ID_NONE) {
+                       mgmt_fe_send_getdata_reply(
+                               session, getdata_req->ds_id, getdata_req->req_id,
+                               false, NULL,
+                               "Failed to create a Show transaction!");
+                       goto mgmt_fe_sess_handle_getdata_req_failed;
+               }
 
                MGMTD_FE_ADAPTER_DBG(
                        "Created new Show Txn 0x%llx for session %p",
@@ -1169,32 +1148,28 @@ mgmt_fe_session_handle_getdata_req_msg(struct mgmt_fe_session_ctx *session,
                        (unsigned long long)session->txn_id, session);
        }
 
-       /* TODO: Create a GETData request under the transaction.
-        * if (mgmt_txn_send_get_data_req(session->txn_id, getdata_req->req_id,
-        *                              getdata_req->ds_id, ds_ctx,
-        *                              getdata_req->data, getdata_req->n_data)
-        *      != 0) {
-        *      mgmt_fe_send_getdata_reply(
-        *              session, getdata_req->ds_id, getdata_req->req_id, false,
-        *              NULL, "Request processing for GET-CONFIG failed!");
-        *      goto mgmt_fe_sess_handle_getdata_req_failed;
-        * }
-        *
-        * For now send back a failure reply.
+       /*
+        * Create a GETData request under the transaction.
         */
-       mgmt_fe_send_getdata_reply(
-               session, getdata_req->ds_id, getdata_req->req_id, false, NULL,
-               "Request processing for GET-CONFIG failed!");
-       goto mgmt_fe_sess_handle_getdata_req_failed;
+       if (mgmt_txn_send_get_data_req(session->txn_id, getdata_req->req_id,
+                                       getdata_req->ds_id, ds_ctx,
+                                       getdata_req->data, getdata_req->n_data)
+           != 0) {
+               mgmt_fe_send_getdata_reply(
+                       session, getdata_req->ds_id, getdata_req->req_id, false,
+                       NULL, "Request processing for GET-CONFIG failed!");
+               goto mgmt_fe_sess_handle_getdata_req_failed;
+       }
 
        return 0;
 
 mgmt_fe_sess_handle_getdata_req_failed:
 
-       /* TODO: Destroy the transaction created recently.
-        * if (session->txn_id != MGMTD_TXN_ID_NONE)
-        *      mgmt_destroy_txn(&session->txn_id);
+       /*
+        * Destroy the transaction created recently.
         */
+       if (session->txn_id != MGMTD_TXN_ID_NONE)
+               mgmt_destroy_txn(&session->txn_id);
 
        if (ds_ctx && session->ds_read_locked[getdata_req->ds_id])
                mgmt_fe_session_unlock_ds(getdata_req->ds_id, ds_ctx,
@@ -1256,25 +1231,19 @@ static int mgmt_fe_session_handle_commit_config_req_msg(
 
        if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) {
                /*
-                * TODO: Start a CONFIG Transaction (if not started already)
-                * session->cfg_txn_id = mgmt_create_txn(session->session_id,
-                *                              MGMTD_TXN_TYPE_CONFIG);
-                * if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
-                *      mgmt_fe_send_commitcfg_reply(
-                *              session, commcfg_req->src_ds_id,
-                *              commcfg_req->dst_ds_id, commcfg_req->req_id,
-                *              MGMTD_INTERNAL_ERROR,
-                *              commcfg_req->validate_only,
-                *              "Failed to create a Configuration session!");
-                *      return 0;
-                * }
+                * Start a CONFIG Transaction (if not started already)
                 */
-               mgmt_fe_send_commitcfg_reply(
-                       session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id,
-                       commcfg_req->req_id, MGMTD_INTERNAL_ERROR,
-                       commcfg_req->validate_only,
-                       "Failed to create a Configuration session!");
-               return 0;
+               session->cfg_txn_id = mgmt_create_txn(session->session_id,
+                                               MGMTD_TXN_TYPE_CONFIG);
+               if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
+                       mgmt_fe_send_commitcfg_reply(
+                               session, commcfg_req->src_ds_id,
+                               commcfg_req->dst_ds_id, commcfg_req->req_id,
+                               MGMTD_INTERNAL_ERROR,
+                               commcfg_req->validate_only,
+                               "Failed to create a Configuration session!");
+                       return 0;
+               }
        }
 
 
@@ -1297,28 +1266,22 @@ static int mgmt_fe_session_handle_commit_config_req_msg(
                session->ds_locked_implict[commcfg_req->dst_ds_id] = true;
        }
 
-       /* TODO: Create COMMITConfig request under the transaction
-        * if (mgmt_txn_send_commit_config_req(
-        *      session->cfg_txn_id, commcfg_req->req_id,
-        *      commcfg_req->src_ds_id, src_ds_ctx, commcfg_req->dst_ds_id,
-        *      dst_ds_ctx, commcfg_req->validate_only, commcfg_req->abort,
-        *      false)
-        *      != 0) {
-        *      mgmt_fe_send_commitcfg_reply(
-        *              session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id,
-        *              commcfg_req->req_id, MGMTD_INTERNAL_ERROR,
-        *              commcfg_req->validate_only,
-        *              "Request processing for COMMIT-CONFIG failed!");
-        *      return 0;
-        * }
-        *
-        * For now due to lack of txn modules send a unsuccessfull reply.
+       /*
+        * Create COMMITConfig request under the transaction
         */
-       mgmt_fe_send_commitcfg_reply(
-               session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id,
-               commcfg_req->req_id, MGMTD_INTERNAL_ERROR,
-               commcfg_req->validate_only,
-               "Request processing for COMMIT-CONFIG failed!");
+       if (mgmt_txn_send_commit_config_req(
+                   session->cfg_txn_id, commcfg_req->req_id,
+                   commcfg_req->src_ds_id, src_ds_ctx, commcfg_req->dst_ds_id,
+                   dst_ds_ctx, commcfg_req->validate_only, commcfg_req->abort,
+                   false)
+           != 0) {
+               mgmt_fe_send_commitcfg_reply(
+                       session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id,
+                       commcfg_req->req_id, MGMTD_INTERNAL_ERROR,
+                       commcfg_req->validate_only,
+                       "Request processing for COMMIT-CONFIG failed!");
+               return 0;
+       }
 
        return 0;
 }
diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c
new file mode 100644 (file)
index 0000000..4fecfa9
--- /dev/null
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021  Vmware, Inc.
+ *                    Pushpasis Sarkar <spushpasis@vmware.com>
+ * Copyright (c) 2023, LabN Consulting, L.L.C.
+ */
+
+#include <zebra.h>
+#include "md5.h"
+#include "thread.h"
+#include "xref.h"
+
+#include "mgmt_fe_client.h"
+#include "mgmtd/mgmt.h"
+#include "mgmtd/mgmt_ds.h"
+#include "mgmtd/mgmt_history.h"
+
+struct mgmt_cmt_info_t {
+       struct mgmt_cmt_infos_item cmts;
+
+       char cmtid_str[MGMTD_MD5_HASH_STR_HEX_LEN];
+       char time_str[MGMTD_COMMIT_TIME_STR_LEN];
+       char cmt_json_file[PATH_MAX];
+};
+
+
+DECLARE_DLIST(mgmt_cmt_infos, struct mgmt_cmt_info_t, cmts);
+
+#define FOREACH_CMT_REC(mm, cmt_info)                                          \
+       frr_each_safe (mgmt_cmt_infos, &mm->cmts, cmt_info)
+
+
+
+static bool mgmt_history_record_exists(char *file_path)
+{
+       int exist;
+
+       exist = access(file_path, F_OK);
+       if (exist == 0)
+               return true;
+       else
+               return false;
+}
+
+static void mgmt_history_remove_file(char *name)
+{
+       if (remove(name) == 0)
+               zlog_debug("Old commit info deletion succeeded");
+       else
+               zlog_err("Old commit info deletion failed");
+}
+
+static void mgmt_history_hash(const char *input_str, char *hash)
+{
+       int i;
+       unsigned char digest[MGMTD_MD5_HASH_LEN];
+       MD5_CTX ctx;
+
+       memset(&ctx, 0, sizeof(ctx));
+       MD5Init(&ctx);
+       MD5Update(&ctx, input_str, strlen(input_str));
+       MD5Final(digest, &ctx);
+
+       for (i = 0; i < MGMTD_MD5_HASH_LEN; i++)
+               snprintf(&hash[i * 2], MGMTD_MD5_HASH_STR_HEX_LEN, "%02x",
+                        (unsigned int)digest[i]);
+}
+
+static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
+{
+       struct mgmt_cmt_info_t *new;
+       struct mgmt_cmt_info_t *cmt_info;
+       struct mgmt_cmt_info_t *last_cmt_info = NULL;
+       struct timeval cmt_recd_tv;
+
+       new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t));
+       gettimeofday(&cmt_recd_tv, NULL);
+       mgmt_realtime_to_string(&cmt_recd_tv, new->time_str,
+                               sizeof(new->time_str));
+       mgmt_history_hash(new->time_str, new->cmtid_str);
+       snprintf(new->cmt_json_file, sizeof(new->cmt_json_file),
+                MGMTD_COMMIT_FILE_PATH, new->cmtid_str);
+
+       if (mgmt_cmt_infos_count(&mm->cmts) == MGMTD_MAX_COMMIT_LIST) {
+               FOREACH_CMT_REC (mm, cmt_info)
+                       last_cmt_info = cmt_info;
+
+               if (last_cmt_info) {
+                       mgmt_history_remove_file(last_cmt_info->cmt_json_file);
+                       mgmt_cmt_infos_del(&mm->cmts, last_cmt_info);
+                       XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info);
+               }
+       }
+
+       mgmt_cmt_infos_add_head(&mm->cmts, new);
+       return new;
+}
+
+static struct mgmt_cmt_info_t *mgmt_history_find_cmt_record(const char *cmtid_str)
+{
+       struct mgmt_cmt_info_t *cmt_info;
+
+       FOREACH_CMT_REC (mm, cmt_info) {
+               if (strncmp(cmt_info->cmtid_str, cmtid_str,
+                           MGMTD_MD5_HASH_STR_HEX_LEN) == 0)
+                       return cmt_info;
+       }
+
+       return NULL;
+}
+
+static bool mgmt_history_read_cmt_record_index(void)
+{
+       FILE *fp;
+       struct mgmt_cmt_info_t cmt_info;
+       struct mgmt_cmt_info_t *new;
+       int cnt = 0;
+
+       fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
+       if (!fp) {
+               zlog_err("Failed to open file %s rb mode",
+                        MGMTD_COMMIT_INDEX_FILE_NAME);
+               return false;
+       }
+
+       while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) {
+               if (cnt < MGMTD_MAX_COMMIT_LIST) {
+                       if (!mgmt_history_record_exists(cmt_info.cmt_json_file)) {
+                               zlog_err(
+                                       "Commit record present in index_file, but commit file %s missing",
+                                       cmt_info.cmt_json_file);
+                               continue;
+                       }
+
+                       new = XCALLOC(MTYPE_MGMTD_CMT_INFO,
+                                     sizeof(struct mgmt_cmt_info_t));
+                       memcpy(new, &cmt_info, sizeof(struct mgmt_cmt_info_t));
+                       mgmt_cmt_infos_add_tail(&mm->cmts, new);
+               } else {
+                       zlog_err("More records found in index file %s",
+                                MGMTD_COMMIT_INDEX_FILE_NAME);
+                       return false;
+               }
+
+               cnt++;
+       }
+
+       fclose(fp);
+       return true;
+}
+
+static bool mgmt_history_dump_cmt_record_index(void)
+{
+       FILE *fp;
+       int ret = 0;
+       struct mgmt_cmt_info_t *cmt_info;
+       struct mgmt_cmt_info_t cmt_info_set[10];
+       int cnt = 0;
+
+       mgmt_history_remove_file((char *)MGMTD_COMMIT_INDEX_FILE_NAME);
+       fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "ab");
+       if (!fp) {
+               zlog_err("Failed to open file %s ab mode",
+                        MGMTD_COMMIT_INDEX_FILE_NAME);
+               return false;
+       }
+
+       FOREACH_CMT_REC (mm, cmt_info) {
+               memcpy(&cmt_info_set[cnt], cmt_info,
+                      sizeof(struct mgmt_cmt_info_t));
+               cnt++;
+       }
+
+       if (!cnt) {
+               fclose(fp);
+               return false;
+       }
+
+       ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
+       fclose(fp);
+       if (ret != cnt) {
+               zlog_err("Write record failed");
+               return false;
+       } else {
+               return true;
+       }
+}
+
+static int mgmt_history_rollback_to_cmt(struct vty *vty,
+                                  struct mgmt_cmt_info_t *cmt_info,
+                                  bool skip_file_load)
+{
+       struct mgmt_ds_ctx *src_ds_ctx;
+       struct mgmt_ds_ctx *dst_ds_ctx;
+       int ret = 0;
+
+       src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
+       if (!src_ds_ctx) {
+               vty_out(vty, "ERROR: Couldnot access Candidate datastore!\n");
+               return -1;
+       }
+
+       /*
+        * Note: Write lock on src_ds is not required. This is already
+        * taken in 'conf te'.
+        */
+       dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
+       if (!dst_ds_ctx) {
+               vty_out(vty, "ERROR: Couldnot access Running datastore!\n");
+               return -1;
+       }
+
+       ret = mgmt_ds_write_lock(dst_ds_ctx);
+       if (ret != 0) {
+               vty_out(vty,
+                       "Failed to lock the DS %u for rollback Reason: %s!\n",
+                       MGMTD_DS_RUNNING, strerror(ret));
+               return -1;
+       }
+
+       if (!skip_file_load) {
+               ret = mgmt_ds_load_config_from_file(
+                       src_ds_ctx, cmt_info->cmt_json_file, false);
+               if (ret != 0) {
+                       mgmt_ds_unlock(dst_ds_ctx);
+                       vty_out(vty,
+                               "Error with parsing the file with error code %d\n",
+                               ret);
+                       return ret;
+               }
+       }
+
+       /* Internally trigger a commit-request. */
+       ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx);
+       if (ret != 0) {
+               mgmt_ds_unlock(dst_ds_ctx);
+               vty_out(vty,
+                       "Error with creating commit apply txn with error code %d\n",
+                       ret);
+               return ret;
+       }
+
+       mgmt_history_dump_cmt_record_index();
+       return 0;
+}
+
+int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str)
+{
+       int ret = 0;
+       struct mgmt_cmt_info_t *cmt_info;
+
+       if (!mgmt_cmt_infos_count(&mm->cmts) ||
+           !mgmt_history_find_cmt_record(cmtid_str)) {
+               vty_out(vty, "Invalid commit Id\n");
+               return -1;
+       }
+
+       FOREACH_CMT_REC (mm, cmt_info) {
+               if (strncmp(cmt_info->cmtid_str, cmtid_str,
+                           MGMTD_MD5_HASH_STR_HEX_LEN) == 0) {
+                       ret = mgmt_history_rollback_to_cmt(vty, cmt_info, false);
+                       return ret;
+               }
+
+               mgmt_history_remove_file(cmt_info->cmt_json_file);
+               mgmt_cmt_infos_del(&mm->cmts, cmt_info);
+               XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
+       }
+
+       return 0;
+}
+
+int mgmt_history_rollback_n(struct vty *vty, int num_cmts)
+{
+       int ret = 0;
+       int cnt = 0;
+       struct mgmt_cmt_info_t *cmt_info;
+       size_t cmts;
+
+       if (!num_cmts)
+               num_cmts = 1;
+
+       cmts = mgmt_cmt_infos_count(&mm->cmts);
+       if ((int)cmts < num_cmts) {
+               vty_out(vty,
+                       "Number of commits found (%d) less than required to rollback\n",
+                       (int)cmts);
+               return -1;
+       }
+
+       if ((int)cmts == 1 || (int)cmts == num_cmts) {
+               vty_out(vty,
+                       "Number of commits found (%d), Rollback of last commit is not supported\n",
+                       (int)cmts);
+               return -1;
+       }
+
+       FOREACH_CMT_REC (mm, cmt_info) {
+               if (cnt == num_cmts) {
+                       ret = mgmt_history_rollback_to_cmt(vty, cmt_info, false);
+                       return ret;
+               }
+
+               cnt++;
+               mgmt_history_remove_file(cmt_info->cmt_json_file);
+               mgmt_cmt_infos_del(&mm->cmts, cmt_info);
+               XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
+       }
+
+       if (!mgmt_cmt_infos_count(&mm->cmts)) {
+               mgmt_ds_reset_candidate();
+               ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true);
+       }
+
+       return ret;
+}
+
+void show_mgmt_cmt_history(struct vty *vty)
+{
+       struct mgmt_cmt_info_t *cmt_info;
+       int slno = 0;
+
+       vty_out(vty, "Last 10 commit history:\n");
+       vty_out(vty, "  Sl.No\tCommit-ID(HEX)\t\t\t  Commit-Record-Time\n");
+       FOREACH_CMT_REC (mm, cmt_info) {
+               vty_out(vty, "  %d\t%s  %s\n", slno, cmt_info->cmtid_str,
+                       cmt_info->time_str);
+               slno++;
+       }
+}
+
+void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
+{
+       struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
+       mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
+       mgmt_history_dump_cmt_record_index();
+}
+
+void mgmt_history_init(void)
+{
+       /* Create commit record for previously stored commit-apply */
+       mgmt_cmt_infos_init(&mm->cmts);
+       mgmt_history_read_cmt_record_index();
+}
+
+void mgmt_history_destroy(void)
+{
+       struct mgmt_cmt_info_t *cmt_info;
+
+       FOREACH_CMT_REC(mm, cmt_info) {
+               mgmt_cmt_infos_del(&mm->cmts, cmt_info);
+               XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
+       }
+
+       mgmt_cmt_infos_fini(&mm->cmts);
+}
diff --git a/mgmtd/mgmt_history.h b/mgmtd/mgmt_history.h
new file mode 100644 (file)
index 0000000..23ce406
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+  * Copyright (C) 2021  Vmware, Inc.
+  *                   Pushpasis Sarkar <spushpasis@vmware.com>
+  * Copyright (c) 2023, LabN Consulting, L.L.C.
+  *
+  */
+#ifndef _FRR_MGMTD_HISTORY_H_
+#define _FRR_MGMTD_HISTORY_H_
+
+#include "vrf.h"
+
+PREDECL_DLIST(mgmt_cmt_infos);
+
+struct mgmt_ds_ctx;
+
+/*
+ * Rollback specific commit from commit history.
+ *
+ * vty
+ *    VTY context.
+ *
+ * cmtid_str
+ *    Specific commit id from commit history.
+ *
+ * Returns:
+ *    0 on success, -1 on failure.
+ */
+extern int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str);
+
+/*
+ * Rollback n commits from commit history.
+ *
+ * vty
+ *    VTY context.
+ *
+ * num_cmts
+ *    Number of commits to be rolled back.
+ *
+ * Returns:
+ *    0 on success, -1 on failure.
+ */
+extern int mgmt_history_rollback_n(struct vty *vty, int num_cmts);
+
+/*
+ * Show mgmt commit history.
+ */
+extern void show_mgmt_cmt_history(struct vty *vty);
+
+extern void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx);
+
+extern void mgmt_history_destroy(void);
+extern void mgmt_history_init(void);
+
+#endif /* _FRR_MGMTD_HISTORY_H_ */
index 39e036c304aa7744f4d09199c5125704193d03fe..2858bc7e45031abdc89f51750540574675e55d82 100644 (file)
@@ -22,3 +22,15 @@ DEFINE_MTYPE(MGMTD, MGMTD, "MGMTD instance");
 DEFINE_MTYPE(MGMTD, MGMTD_BE_ADPATER, "MGMTD backend adapter");
 DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "MGMTD Frontend adapter");
 DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "MGMTD Frontend Client Session");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN, "MGMTD Transaction");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_REQ, "MGMTD Transaction Requests");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_SETCFG_REQ,
+            "MGMTD Transaction Set-Config Requests");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ,
+            "MGMTD Transaction Commit-Config Requests");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REQ,
+            "MGMTD Transaction Get-Data Requests");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REPLY,
+            "MGMTD Transaction Get-Data Replies");
+DEFINE_MTYPE(MGMTD, MGMTD_TXN_CFG_BATCH, "MGMTD Transaction Gonfig Batches");
+DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "MGMTD commit info for tracking commits");
index d8b3ac7e0edda29930a54937518d9284dae78d2d..5cfcafc749046172ee1c981baac6192840f4646d 100644 (file)
@@ -16,4 +16,13 @@ DECLARE_MTYPE(MGMTD);
 DECLARE_MTYPE(MGMTD_BE_ADPATER);
 DECLARE_MTYPE(MGMTD_FE_ADPATER);
 DECLARE_MTYPE(MGMTD_FE_SESSION);
+DECLARE_MTYPE(MGMTD_TXN);
+DECLARE_MTYPE(MGMTD_TXN_REQ);
+DECLARE_MTYPE(MGMTD_TXN_SETCFG_REQ);
+DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ);
+DECLARE_MTYPE(MGMTD_TXN_GETDATA_REQ);
+DECLARE_MTYPE(MGMTD_TXN_GETDATA_REPLY);
+DECLARE_MTYPE(MGMTD_TXN_CFG_BATCH);
+DECLARE_MTYPE(MGMTD_BE_ADAPTER_MSG_BUF);
+DECLARE_MTYPE(MGMTD_CMT_INFO);
 #endif /* _FRR_MGMTD_MEMORY_H */
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
new file mode 100644 (file)
index 0000000..115aa53
--- /dev/null
@@ -0,0 +1,2875 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MGMTD Transactions
+ *
+ * Copyright (C) 2021  Vmware, Inc.
+ *                    Pushpasis Sarkar <spushpasis@vmware.com>
+ */
+
+#include <zebra.h>
+#include "hash.h"
+#include "jhash.h"
+#include "libfrr.h"
+#include "mgmtd/mgmt.h"
+#include "mgmtd/mgmt_memory.h"
+#include "mgmtd/mgmt_txn.h"
+
+#ifdef REDIRECT_DEBUG_TO_STDERR
+#define MGMTD_TXN_DBG(fmt, ...)                                               \
+       fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__)
+#define MGMTD_TXN_ERR(fmt, ...)                                               \
+       fprintf(stderr, "%s: ERROR, " fmt "\n", __func__, ##__VA_ARGS__)
+#else /* REDIRECT_DEBUG_TO_STDERR */
+#define MGMTD_TXN_DBG(fmt, ...)                                               \
+       do {                                                                   \
+               if (mgmt_debug_txn)                                           \
+                       zlog_err("%s: " fmt, __func__, ##__VA_ARGS__);         \
+       } while (0)
+#define MGMTD_TXN_ERR(fmt, ...)                                               \
+       zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+#endif /* REDIRECT_DEBUG_TO_STDERR */
+
+#define MGMTD_TXN_LOCK(txn) mgmt_txn_lock(txn, __FILE__, __LINE__)
+#define MGMTD_TXN_UNLOCK(txn) mgmt_txn_unlock(txn, __FILE__, __LINE__)
+
+enum mgmt_txn_event {
+       MGMTD_TXN_PROC_SETCFG = 1,
+       MGMTD_TXN_PROC_COMMITCFG,
+       MGMTD_TXN_PROC_GETCFG,
+       MGMTD_TXN_PROC_GETDATA,
+       MGMTD_TXN_COMMITCFG_TIMEOUT,
+       MGMTD_TXN_CLEANUP
+};
+
+PREDECL_LIST(mgmt_txn_reqs);
+
+struct mgmt_set_cfg_req {
+       Mgmtd__DatastoreId ds_id;
+       struct mgmt_ds_ctx *ds_ctx;
+       struct nb_cfg_change cfg_changes[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       uint16_t num_cfg_changes;
+       bool implicit_commit;
+       Mgmtd__DatastoreId dst_ds_id;
+       struct mgmt_ds_ctx *dst_ds_ctx;
+       struct mgmt_setcfg_stats *setcfg_stats;
+};
+
+enum mgmt_commit_phase {
+       MGMTD_COMMIT_PHASE_PREPARE_CFG = 0,
+       MGMTD_COMMIT_PHASE_TXN_CREATE,
+       MGMTD_COMMIT_PHASE_SEND_CFG,
+       MGMTD_COMMIT_PHASE_APPLY_CFG,
+       MGMTD_COMMIT_PHASE_TXN_DELETE,
+       MGMTD_COMMIT_PHASE_MAX
+};
+
+static inline const char *
+mgmt_commit_phase2str(enum mgmt_commit_phase cmt_phase)
+{
+       switch (cmt_phase) {
+       case MGMTD_COMMIT_PHASE_PREPARE_CFG:
+               return "PREP-CFG";
+       case MGMTD_COMMIT_PHASE_TXN_CREATE:
+               return "CREATE-TXN";
+       case MGMTD_COMMIT_PHASE_SEND_CFG:
+               return "SEND-CFG";
+       case MGMTD_COMMIT_PHASE_APPLY_CFG:
+               return "APPLY-CFG";
+       case MGMTD_COMMIT_PHASE_TXN_DELETE:
+               return "DELETE-TXN";
+       case MGMTD_COMMIT_PHASE_MAX:
+               return "Invalid/Unknown";
+       }
+
+       return "Invalid/Unknown";
+}
+
+PREDECL_LIST(mgmt_txn_batches);
+
+struct mgmt_txn_be_cfg_batch {
+       struct mgmt_txn_ctx *txn;
+       uint64_t batch_id;
+       enum mgmt_be_client_id be_id;
+       struct mgmt_be_client_adapter *be_adapter;
+       union mgmt_be_xpath_subscr_info
+               xp_subscr[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       Mgmtd__YangCfgDataReq cfg_data[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       Mgmtd__YangCfgDataReq * cfg_datap[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       Mgmtd__YangData data[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       Mgmtd__YangDataValue value[MGMTD_MAX_CFG_CHANGES_IN_BATCH];
+       size_t num_cfg_data;
+       int buf_space_left;
+       enum mgmt_commit_phase comm_phase;
+       struct mgmt_txn_batches_item list_linkage;
+};
+
+DECLARE_LIST(mgmt_txn_batches, struct mgmt_txn_be_cfg_batch, list_linkage);
+
+#define FOREACH_TXN_CFG_BATCH_IN_LIST(list, batch)                             \
+       frr_each_safe (mgmt_txn_batches, list, batch)
+
+struct mgmt_commit_cfg_req {
+       Mgmtd__DatastoreId src_ds_id;
+       struct mgmt_ds_ctx *src_ds_ctx;
+       Mgmtd__DatastoreId dst_ds_id;
+       struct mgmt_ds_ctx *dst_ds_ctx;
+       uint32_t nb_txn_id;
+       uint8_t validate_only : 1;
+       uint8_t abort : 1;
+       uint8_t implicit : 1;
+       uint8_t rollback : 1;
+
+       /* Track commit phases */
+       enum mgmt_commit_phase curr_phase;
+       enum mgmt_commit_phase next_phase;
+
+       /*
+        * Set of config changes to commit. This is used only
+        * when changes are NOT to be determined by comparing
+        * candidate and running DSs. This is typically used
+        * for downloading all relevant configs for a new backend
+        * client that has recently come up and connected with
+        * MGMTD.
+        */
+       struct nb_config_cbs *cfg_chgs;
+
+       /*
+        * Details on all the Backend Clients associated with
+        * this commit.
+        */
+       struct mgmt_be_client_subscr_info subscr_info;
+
+       /*
+        * List of backend batches for this commit to be validated
+        * and applied at the backend.
+        *
+        * FIXME: Need to re-think this design for the case set of
+        * validators for a given YANG data item is different from
+        * the set of notifiers for the same. We may need to have
+        * separate list of batches for VALIDATE and APPLY.
+        */
+       struct mgmt_txn_batches_head curr_batches[MGMTD_BE_CLIENT_ID_MAX];
+       struct mgmt_txn_batches_head next_batches[MGMTD_BE_CLIENT_ID_MAX];
+       /*
+        * The last batch added for any backend client. This is always on
+        * 'curr_batches'
+        */
+       struct mgmt_txn_be_cfg_batch
+               *last_be_cfg_batch[MGMTD_BE_CLIENT_ID_MAX];
+       struct hash *batches;
+       uint64_t next_batch_id;
+
+       struct mgmt_commit_stats *cmt_stats;
+};
+
+struct mgmt_get_data_reply {
+       /* Buffer space for preparing data reply */
+       int num_reply;
+       int last_batch;
+       Mgmtd__YangDataReply data_reply;
+       Mgmtd__YangData reply_data[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH];
+       Mgmtd__YangData * reply_datap[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH];
+       Mgmtd__YangDataValue reply_value[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH];
+       char *reply_xpathp[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH];
+};
+
+struct mgmt_get_data_req {
+       Mgmtd__DatastoreId ds_id;
+       struct mgmt_ds_ctx *ds_ctx;
+       char *xpaths[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH];
+       int num_xpaths;
+
+       /*
+        * Buffer space for preparing reply.
+        * NOTE: Should only be malloc-ed on demand to reduce
+        * memory footprint. Freed up via mgmt_trx_req_free()
+        */
+       struct mgmt_get_data_reply *reply;
+
+       int total_reply;
+};
+
+struct mgmt_txn_req {
+       struct mgmt_txn_ctx *txn;
+       enum mgmt_txn_event req_event;
+       uint64_t req_id;
+       union {
+               struct mgmt_set_cfg_req *set_cfg;
+               struct mgmt_get_data_req *get_data;
+               struct mgmt_commit_cfg_req commit_cfg;
+       } req;
+
+       bool pending_be_proc;
+       struct mgmt_txn_reqs_item list_linkage;
+};
+
+DECLARE_LIST(mgmt_txn_reqs, struct mgmt_txn_req, list_linkage);
+
+#define FOREACH_TXN_REQ_IN_LIST(list, req)                                     \
+       frr_each_safe (mgmt_txn_reqs, list, req)
+
+struct mgmt_txn_ctx {
+       uint64_t session_id; /* One transaction per client session */
+       uint64_t txn_id;
+       enum mgmt_txn_type type;
+
+       /* struct mgmt_master *mm; */
+
+       struct thread *proc_set_cfg;
+       struct thread *proc_comm_cfg;
+       struct thread *proc_get_cfg;
+       struct thread *proc_get_data;
+       struct thread *comm_cfg_timeout;
+       struct thread *clnup;
+
+       /* List of backend adapters involved in this transaction */
+       struct mgmt_txn_badapters_head be_adapters;
+
+       int refcount;
+
+       struct mgmt_txns_item list_linkage;
+
+       /*
+        * List of pending set-config requests for a given
+        * transaction/session. Just one list for requests
+        * not processed at all. There's no backend interaction
+        * involved.
+        */
+       struct mgmt_txn_reqs_head set_cfg_reqs;
+       /*
+        * List of pending get-config requests for a given
+        * transaction/session. Just one list for requests
+        * not processed at all. There's no backend interaction
+        * involved.
+        */
+       struct mgmt_txn_reqs_head get_cfg_reqs;
+       /*
+        * List of pending get-data requests for a given
+        * transaction/session Two lists, one for requests
+        * not processed at all, and one for requests that
+        * has been sent to backend for processing.
+        */
+       struct mgmt_txn_reqs_head get_data_reqs;
+       struct mgmt_txn_reqs_head pending_get_datas;
+       /*
+        * There will always be one commit-config allowed for a given
+        * transaction/session. No need to maintain lists for it.
+        */
+       struct mgmt_txn_req *commit_cfg_req;
+};
+
+DECLARE_LIST(mgmt_txns, struct mgmt_txn_ctx, list_linkage);
+
+#define FOREACH_TXN_IN_LIST(mm, txn)                                           \
+       frr_each_safe (mgmt_txns, &(mm)->txn_list, (txn))
+
+static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
+                                         enum mgmt_result result,
+                                         const char *error_if_any);
+
+static inline const char *
+mgmt_txn_commit_phase_str(struct mgmt_txn_ctx *txn, bool curr)
+{
+       if (!txn->commit_cfg_req)
+               return "None";
+
+       return (mgmt_commit_phase2str(
+               curr ? txn->commit_cfg_req->req.commit_cfg.curr_phase
+                    : txn->commit_cfg_req->req.commit_cfg.next_phase));
+}
+
+static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file,
+                          int line);
+static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file,
+                            int line);
+static int
+mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn,
+                                struct mgmt_be_client_adapter *adapter);
+
+static struct thread_master *mgmt_txn_tm;
+static struct mgmt_master *mgmt_txn_mm;
+
+static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn,
+                                    enum mgmt_txn_event event);
+
+static int
+mgmt_move_be_commit_to_next_phase(struct mgmt_txn_ctx *txn,
+                                    struct mgmt_be_client_adapter *adapter);
+
+static struct mgmt_txn_be_cfg_batch *
+mgmt_txn_cfg_batch_alloc(struct mgmt_txn_ctx *txn,
+                         enum mgmt_be_client_id id,
+                         struct mgmt_be_client_adapter *be_adapter)
+{
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+
+       cfg_btch = XCALLOC(MTYPE_MGMTD_TXN_CFG_BATCH,
+                          sizeof(struct mgmt_txn_be_cfg_batch));
+       assert(cfg_btch);
+       cfg_btch->be_id = id;
+
+       cfg_btch->txn = txn;
+       MGMTD_TXN_LOCK(txn);
+       assert(txn->commit_cfg_req);
+       mgmt_txn_batches_add_tail(
+               &txn->commit_cfg_req->req.commit_cfg.curr_batches[id],
+               cfg_btch);
+       cfg_btch->be_adapter = be_adapter;
+       cfg_btch->buf_space_left = MGMTD_BE_CFGDATA_MAX_MSG_LEN;
+       if (be_adapter)
+               mgmt_be_adapter_lock(be_adapter);
+
+       txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] =
+               cfg_btch;
+       if (!txn->commit_cfg_req->req.commit_cfg.next_batch_id)
+               txn->commit_cfg_req->req.commit_cfg.next_batch_id++;
+       cfg_btch->batch_id =
+               txn->commit_cfg_req->req.commit_cfg.next_batch_id++;
+       hash_get(txn->commit_cfg_req->req.commit_cfg.batches, cfg_btch,
+                hash_alloc_intern);
+
+       return cfg_btch;
+}
+
+static void
+mgmt_txn_cfg_batch_free(struct mgmt_txn_be_cfg_batch **cfg_btch)
+{
+       size_t indx;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+
+       MGMTD_TXN_DBG(" Batch: %p, Txn: %p", *cfg_btch, (*cfg_btch)->txn);
+
+       assert((*cfg_btch)->txn
+              && (*cfg_btch)->txn->type == MGMTD_TXN_TYPE_CONFIG);
+
+       cmtcfg_req = &(*cfg_btch)->txn->commit_cfg_req->req.commit_cfg;
+       hash_release(cmtcfg_req->batches, *cfg_btch);
+       mgmt_txn_batches_del(&cmtcfg_req->curr_batches[(*cfg_btch)->be_id],
+                            *cfg_btch);
+       mgmt_txn_batches_del(&cmtcfg_req->next_batches[(*cfg_btch)->be_id],
+                            *cfg_btch);
+
+       if ((*cfg_btch)->be_adapter)
+               mgmt_be_adapter_unlock(&(*cfg_btch)->be_adapter);
+
+       for (indx = 0; indx < (*cfg_btch)->num_cfg_data; indx++) {
+               if ((*cfg_btch)->data[indx].xpath) {
+                       free((*cfg_btch)->data[indx].xpath);
+                       (*cfg_btch)->data[indx].xpath = NULL;
+               }
+       }
+
+       MGMTD_TXN_UNLOCK(&(*cfg_btch)->txn);
+
+       XFREE(MTYPE_MGMTD_TXN_CFG_BATCH, *cfg_btch);
+       *cfg_btch = NULL;
+}
+
+static unsigned int mgmt_txn_cfgbatch_hash_key(const void *data)
+{
+       const struct mgmt_txn_be_cfg_batch *batch = data;
+
+       return jhash2((uint32_t *) &batch->batch_id,
+                     sizeof(batch->batch_id) / sizeof(uint32_t), 0);
+}
+
+static bool mgmt_txn_cfgbatch_hash_cmp(const void *d1, const void *d2)
+{
+       const struct mgmt_txn_be_cfg_batch *batch1 = d1;
+       const struct mgmt_txn_be_cfg_batch *batch2 = d2;
+
+       return (batch1->batch_id == batch2->batch_id);
+}
+
+static void mgmt_txn_cfgbatch_hash_free(void *data)
+{
+       struct mgmt_txn_be_cfg_batch *batch = data;
+
+       mgmt_txn_cfg_batch_free(&batch);
+}
+
+static inline struct mgmt_txn_be_cfg_batch *
+mgmt_txn_cfgbatch_id2ctx(struct mgmt_txn_ctx *txn, uint64_t batch_id)
+{
+       struct mgmt_txn_be_cfg_batch key = {0};
+       struct mgmt_txn_be_cfg_batch *batch;
+
+       if (!txn->commit_cfg_req)
+               return NULL;
+
+       key.batch_id = batch_id;
+       batch = hash_lookup(txn->commit_cfg_req->req.commit_cfg.batches,
+                           &key);
+
+       return batch;
+}
+
+static void mgmt_txn_cleanup_be_cfg_batches(struct mgmt_txn_ctx *txn,
+                                           enum mgmt_be_client_id id)
+{
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_txn_batches_head *list;
+
+       list = &txn->commit_cfg_req->req.commit_cfg.curr_batches[id];
+       FOREACH_TXN_CFG_BATCH_IN_LIST (list, cfg_btch)
+               mgmt_txn_cfg_batch_free(&cfg_btch);
+
+       mgmt_txn_batches_fini(list);
+
+       list = &txn->commit_cfg_req->req.commit_cfg.next_batches[id];
+       FOREACH_TXN_CFG_BATCH_IN_LIST (list, cfg_btch)
+               mgmt_txn_cfg_batch_free(&cfg_btch);
+
+       mgmt_txn_batches_fini(list);
+
+       txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] = NULL;
+}
+
+static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn,
+                                                uint64_t req_id,
+                                                enum mgmt_txn_event req_event)
+{
+       struct mgmt_txn_req *txn_req;
+       enum mgmt_be_client_id id;
+
+       txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_txn_req));
+       assert(txn_req);
+       txn_req->txn = txn;
+       txn_req->req_id = req_id;
+       txn_req->req_event = req_event;
+       txn_req->pending_be_proc = false;
+
+       switch (txn_req->req_event) {
+       case MGMTD_TXN_PROC_SETCFG:
+               txn_req->req.set_cfg = XCALLOC(MTYPE_MGMTD_TXN_SETCFG_REQ,
+                                              sizeof(struct mgmt_set_cfg_req));
+               assert(txn_req->req.set_cfg);
+               mgmt_txn_reqs_add_tail(&txn->set_cfg_reqs, txn_req);
+               MGMTD_TXN_DBG(
+                       "Added a new SETCFG Req: %p for Txn: %p, Sessn: 0x%llx",
+                       txn_req, txn, (unsigned long long)txn->session_id);
+               break;
+       case MGMTD_TXN_PROC_COMMITCFG:
+               txn->commit_cfg_req = txn_req;
+               MGMTD_TXN_DBG(
+                       "Added a new COMMITCFG Req: %p for Txn: %p, Sessn: 0x%llx",
+                       txn_req, txn, (unsigned long long)txn->session_id);
+
+               FOREACH_MGMTD_BE_CLIENT_ID (id) {
+                       mgmt_txn_batches_init(
+                               &txn_req->req.commit_cfg.curr_batches[id]);
+                       mgmt_txn_batches_init(
+                               &txn_req->req.commit_cfg.next_batches[id]);
+               }
+
+               txn_req->req.commit_cfg.batches =
+                       hash_create(mgmt_txn_cfgbatch_hash_key,
+                                   mgmt_txn_cfgbatch_hash_cmp,
+                                   "MGMT Config Batches");
+               break;
+       case MGMTD_TXN_PROC_GETCFG:
+               txn_req->req.get_data =
+                       XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REQ,
+                               sizeof(struct mgmt_get_data_req));
+               assert(txn_req->req.get_data);
+               mgmt_txn_reqs_add_tail(&txn->get_cfg_reqs, txn_req);
+               MGMTD_TXN_DBG(
+                       "Added a new GETCFG Req: %p for Txn: %p, Sessn: 0x%llx",
+                       txn_req, txn, (unsigned long long)txn->session_id);
+               break;
+       case MGMTD_TXN_PROC_GETDATA:
+               txn_req->req.get_data =
+                       XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REQ,
+                               sizeof(struct mgmt_get_data_req));
+               assert(txn_req->req.get_data);
+               mgmt_txn_reqs_add_tail(&txn->get_data_reqs, txn_req);
+               MGMTD_TXN_DBG(
+                       "Added a new GETDATA Req: %p for Txn: %p, Sessn: 0x%llx",
+                       txn_req, txn, (unsigned long long)txn->session_id);
+               break;
+       case MGMTD_TXN_COMMITCFG_TIMEOUT:
+       case MGMTD_TXN_CLEANUP:
+               break;
+       }
+
+       MGMTD_TXN_LOCK(txn);
+
+       return txn_req;
+}
+
+static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req)
+{
+       int indx;
+       struct mgmt_txn_reqs_head *req_list = NULL;
+       struct mgmt_txn_reqs_head *pending_list = NULL;
+       enum mgmt_be_client_id id;
+       struct mgmt_be_client_adapter *adapter;
+
+       switch ((*txn_req)->req_event) {
+       case MGMTD_TXN_PROC_SETCFG:
+               for (indx = 0; indx < (*txn_req)->req.set_cfg->num_cfg_changes;
+                    indx++) {
+                       if ((*txn_req)->req.set_cfg->cfg_changes[indx].value) {
+                               MGMTD_TXN_DBG(
+                                       "Freeing value for %s at %p ==> '%s'",
+                                       (*txn_req)
+                                               ->req.set_cfg->cfg_changes[indx]
+                                               .xpath,
+                                       (*txn_req)
+                                               ->req.set_cfg->cfg_changes[indx]
+                                               .value,
+                                       (*txn_req)
+                                               ->req.set_cfg->cfg_changes[indx]
+                                               .value);
+                               free((void *)(*txn_req)
+                                            ->req.set_cfg->cfg_changes[indx]
+                                            .value);
+                       }
+               }
+               req_list = &(*txn_req)->txn->set_cfg_reqs;
+               MGMTD_TXN_DBG("Deleting SETCFG Req: %p for Txn: %p",
+                              *txn_req, (*txn_req)->txn);
+               XFREE(MTYPE_MGMTD_TXN_SETCFG_REQ, (*txn_req)->req.set_cfg);
+               break;
+       case MGMTD_TXN_PROC_COMMITCFG:
+               MGMTD_TXN_DBG("Deleting COMMITCFG Req: %p for Txn: %p",
+                              *txn_req, (*txn_req)->txn);
+               FOREACH_MGMTD_BE_CLIENT_ID (id) {
+                       /*
+                        * Send TXN_DELETE to cleanup state for this
+                        * transaction on backend
+                        */
+                       if ((*txn_req)->req.commit_cfg.curr_phase
+                                   >= MGMTD_COMMIT_PHASE_TXN_CREATE
+                           && (*txn_req)->req.commit_cfg.curr_phase
+                                      < MGMTD_COMMIT_PHASE_TXN_DELETE
+                           && (*txn_req)
+                                      ->req.commit_cfg.subscr_info
+                                      .xpath_subscr[id]
+                                      .subscribed) {
+                               adapter = mgmt_be_get_adapter_by_id(id);
+                               if (adapter)
+                                       mgmt_txn_send_be_txn_delete(
+                                               (*txn_req)->txn, adapter);
+                       }
+
+                       mgmt_txn_cleanup_be_cfg_batches((*txn_req)->txn,
+                                                           id);
+                       if ((*txn_req)->req.commit_cfg.batches) {
+                               hash_clean((*txn_req)->req.commit_cfg.batches,
+                                          mgmt_txn_cfgbatch_hash_free);
+                               hash_free((*txn_req)->req.commit_cfg.batches);
+                               (*txn_req)->req.commit_cfg.batches = NULL;
+                       }
+               }
+               break;
+       case MGMTD_TXN_PROC_GETCFG:
+               for (indx = 0; indx < (*txn_req)->req.get_data->num_xpaths;
+                    indx++) {
+                       if ((*txn_req)->req.get_data->xpaths[indx])
+                               free((void *)(*txn_req)
+                                            ->req.get_data->xpaths[indx]);
+               }
+               req_list = &(*txn_req)->txn->get_cfg_reqs;
+               MGMTD_TXN_DBG("Deleting GETCFG Req: %p for Txn: %p",
+                              *txn_req, (*txn_req)->txn);
+               if ((*txn_req)->req.get_data->reply)
+                       XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY,
+                             (*txn_req)->req.get_data->reply);
+               XFREE(MTYPE_MGMTD_TXN_GETDATA_REQ, (*txn_req)->req.get_data);
+               break;
+       case MGMTD_TXN_PROC_GETDATA:
+               for (indx = 0; indx < (*txn_req)->req.get_data->num_xpaths;
+                    indx++) {
+                       if ((*txn_req)->req.get_data->xpaths[indx])
+                               free((void *)(*txn_req)
+                                            ->req.get_data->xpaths[indx]);
+               }
+               pending_list = &(*txn_req)->txn->pending_get_datas;
+               req_list = &(*txn_req)->txn->get_data_reqs;
+               MGMTD_TXN_DBG("Deleting GETDATA Req: %p for Txn: %p",
+                              *txn_req, (*txn_req)->txn);
+               if ((*txn_req)->req.get_data->reply)
+                       XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY,
+                             (*txn_req)->req.get_data->reply);
+               XFREE(MTYPE_MGMTD_TXN_GETDATA_REQ, (*txn_req)->req.get_data);
+               break;
+       case MGMTD_TXN_COMMITCFG_TIMEOUT:
+       case MGMTD_TXN_CLEANUP:
+               break;
+       }
+
+       if ((*txn_req)->pending_be_proc && pending_list) {
+               mgmt_txn_reqs_del(pending_list, *txn_req);
+               MGMTD_TXN_DBG("Removed Req: %p from pending-list (left:%d)",
+                             *txn_req, (int)mgmt_txn_reqs_count(pending_list));
+       } else if (req_list) {
+               mgmt_txn_reqs_del(req_list, *txn_req);
+               MGMTD_TXN_DBG("Removed Req: %p from request-list (left:%d)",
+                             *txn_req, (int)mgmt_txn_reqs_count(req_list));
+       }
+
+       (*txn_req)->pending_be_proc = false;
+       MGMTD_TXN_UNLOCK(&(*txn_req)->txn);
+       XFREE(MTYPE_MGMTD_TXN_REQ, (*txn_req));
+       *txn_req = NULL;
+}
+
+static void mgmt_txn_process_set_cfg(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       struct mgmt_ds_ctx *ds_ctx;
+       struct nb_config *nb_config;
+       char err_buf[1024];
+       bool error;
+       int num_processed = 0;
+       size_t left;
+       struct mgmt_commit_stats *cmt_stats;
+       int ret = 0;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+       cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id);
+
+       MGMTD_TXN_DBG(
+               "Processing %d SET_CONFIG requests for Txn:%p Session:0x%llx",
+               (int)mgmt_txn_reqs_count(&txn->set_cfg_reqs), txn,
+               (unsigned long long)txn->session_id);
+
+       FOREACH_TXN_REQ_IN_LIST (&txn->set_cfg_reqs, txn_req) {
+               error = false;
+               assert(txn_req->req_event == MGMTD_TXN_PROC_SETCFG);
+               ds_ctx = txn_req->req.set_cfg->ds_ctx;
+               if (!ds_ctx) {
+                       mgmt_fe_send_set_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.set_cfg->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, "No such datastore!",
+                               txn_req->req.set_cfg->implicit_commit);
+                       error = true;
+                       goto mgmt_txn_process_set_cfg_done;
+               }
+
+               nb_config = mgmt_ds_get_nb_config(ds_ctx);
+               if (!nb_config) {
+                       mgmt_fe_send_set_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.set_cfg->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR,
+                               "Unable to retrieve DS Config Tree!",
+                               txn_req->req.set_cfg->implicit_commit);
+                       error = true;
+                       goto mgmt_txn_process_set_cfg_done;
+               }
+
+               error = false;
+               nb_candidate_edit_config_changes(
+                       nb_config, txn_req->req.set_cfg->cfg_changes,
+                       (size_t)txn_req->req.set_cfg->num_cfg_changes, NULL,
+                       NULL, 0, err_buf, sizeof(err_buf), &error);
+               if (error) {
+                       mgmt_fe_send_set_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.set_cfg->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, err_buf,
+                               txn_req->req.set_cfg->implicit_commit);
+                       goto mgmt_txn_process_set_cfg_done;
+               }
+
+               if (txn_req->req.set_cfg->implicit_commit) {
+                       assert(mgmt_txn_reqs_count(&txn->set_cfg_reqs) == 1);
+                       assert(txn_req->req.set_cfg->dst_ds_ctx);
+
+                       ret = mgmt_ds_write_lock(
+                               txn_req->req.set_cfg->dst_ds_ctx);
+                       if (ret != 0) {
+                               MGMTD_TXN_ERR(
+                                       "Failed to lock the DS %u for txn: %p session 0x%llx, errstr %s!",
+                                       txn_req->req.set_cfg->dst_ds_id, txn,
+                                       (unsigned long long)txn->session_id,
+                                       strerror(ret));
+                               mgmt_txn_send_commit_cfg_reply(
+                                       txn, MGMTD_DS_LOCK_FAILED,
+                                       "Lock running DS before implicit commit failed!");
+                               goto mgmt_txn_process_set_cfg_done;
+                       }
+
+                       mgmt_txn_send_commit_config_req(
+                               txn->txn_id, txn_req->req_id,
+                               txn_req->req.set_cfg->ds_id,
+                               txn_req->req.set_cfg->ds_ctx,
+                               txn_req->req.set_cfg->dst_ds_id,
+                               txn_req->req.set_cfg->dst_ds_ctx, false,
+                               false, true);
+
+                       if (mm->perf_stats_en)
+                               gettimeofday(&cmt_stats->last_start, NULL);
+                       cmt_stats->commit_cnt++;
+               } else if (mgmt_fe_send_set_cfg_reply(
+                                  txn->session_id, txn->txn_id,
+                                  txn_req->req.set_cfg->ds_id,
+                                  txn_req->req_id, MGMTD_SUCCESS, NULL, false)
+                          != 0) {
+                       MGMTD_TXN_ERR(
+                               "Failed to send SET_CONFIG_REPLY for txn %p session 0x%llx",
+                               txn, (unsigned long long)txn->session_id);
+                       error = true;
+               }
+
+       mgmt_txn_process_set_cfg_done:
+
+               /*
+                * Note: The following will remove it from the list as well.
+                */
+               mgmt_txn_req_free(&txn_req);
+
+               num_processed++;
+               if (num_processed == MGMTD_TXN_MAX_NUM_SETCFG_PROC)
+                       break;
+       }
+
+       left = mgmt_txn_reqs_count(&txn->set_cfg_reqs);
+       if (left) {
+               MGMTD_TXN_DBG(
+                       "Processed maximum number of Set-Config requests (%d/%d/%d). Rescheduling for rest.",
+                       num_processed, MGMTD_TXN_MAX_NUM_SETCFG_PROC,
+                       (int)left);
+               mgmt_txn_register_event(txn, MGMTD_TXN_PROC_SETCFG);
+       }
+}
+
+static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
+                                          enum mgmt_result result,
+                                          const char *error_if_any)
+{
+       int ret = 0;
+       bool success, create_cmt_info_rec;
+
+       if (!txn->commit_cfg_req)
+               return -1;
+
+       success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES);
+
+       if (!txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id
+           && mgmt_fe_send_commit_cfg_reply(
+                      txn->session_id, txn->txn_id,
+                      txn->commit_cfg_req->req.commit_cfg.src_ds_id,
+                      txn->commit_cfg_req->req.commit_cfg.dst_ds_id,
+                      txn->commit_cfg_req->req_id,
+                      txn->commit_cfg_req->req.commit_cfg.validate_only,
+                      result, error_if_any)
+                      != 0) {
+               MGMTD_TXN_ERR(
+                       "Failed to send COMMIT-CONFIG-REPLY for Txn %p Sessn 0x%llx",
+                       txn, (unsigned long long)txn->session_id);
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id
+           && mgmt_fe_send_set_cfg_reply(
+                      txn->session_id, txn->txn_id,
+                      txn->commit_cfg_req->req.commit_cfg.src_ds_id,
+                      txn->commit_cfg_req->req_id,
+                      success ? MGMTD_SUCCESS : MGMTD_INTERNAL_ERROR,
+                      error_if_any, true)
+                      != 0) {
+               MGMTD_TXN_ERR(
+                       "Failed to send SET-CONFIG-REPLY for Txn %p Sessn 0x%llx",
+                       txn, (unsigned long long)txn->session_id);
+       }
+
+       if (success) {
+               /* Stop the commit-timeout timer */
+               THREAD_OFF(txn->comm_cfg_timeout);
+
+               create_cmt_info_rec =
+                       (result != MGMTD_NO_CFG_CHANGES &&
+                        !txn->commit_cfg_req->req.commit_cfg.rollback);
+
+               /*
+                * Successful commit: Merge Src DS into Dst DS if and only if
+                * this was not a validate-only or abort request.
+                */
+               if ((txn->session_id
+                    && !txn->commit_cfg_req->req.commit_cfg.validate_only
+                    && !txn->commit_cfg_req->req.commit_cfg.abort)
+                   || txn->commit_cfg_req->req.commit_cfg.rollback) {
+                       mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg
+                                                .src_ds_ctx,
+                                        txn->commit_cfg_req->req.commit_cfg
+                                                .dst_ds_ctx,
+                                        create_cmt_info_rec);
+               }
+
+               /*
+                * Restore Src DS back to Dest DS only through a commit abort
+                * request.
+                */
+               if (txn->session_id
+                   && txn->commit_cfg_req->req.commit_cfg.abort)
+                       mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg
+                                                .dst_ds_ctx,
+                                        txn->commit_cfg_req->req.commit_cfg
+                                                .src_ds_ctx,
+                                        false);
+       } else {
+               /*
+                * The commit has failied. For implicit commit requests restore
+                * back the contents of the candidate DS.
+                */
+               if (txn->commit_cfg_req->req.commit_cfg.implicit)
+                       mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg
+                                                .dst_ds_ctx,
+                                        txn->commit_cfg_req->req.commit_cfg
+                                                .src_ds_ctx,
+                                        false);
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.rollback) {
+               ret = mgmt_ds_unlock(
+                       txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx);
+               if (ret != 0)
+                       MGMTD_TXN_ERR(
+                               "Failed to unlock the dst DS during rollback : %s",
+                               strerror(ret));
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.implicit)
+               if (mgmt_ds_unlock(
+                           txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx)
+                   != 0)
+                       MGMTD_TXN_ERR(
+                               "Failed to unlock the dst DS during implicit : %s",
+                               strerror(ret));
+
+       txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL;
+       mgmt_txn_req_free(&txn->commit_cfg_req);
+
+       /*
+        * The CONFIG Transaction should be destroyed from Frontend-adapter.
+        * But in case the transaction is not triggered from a front-end session
+        * we need to cleanup by itself.
+        */
+       if (!txn->session_id)
+               mgmt_txn_register_event(txn, MGMTD_TXN_CLEANUP);
+
+       return 0;
+}
+
+static void
+mgmt_move_txn_cfg_batch_to_next(struct mgmt_commit_cfg_req *cmtcfg_req,
+                               struct mgmt_txn_be_cfg_batch *cfg_btch,
+                               struct mgmt_txn_batches_head *src_list,
+                               struct mgmt_txn_batches_head *dst_list,
+                               bool update_commit_phase,
+                               enum mgmt_commit_phase to_phase)
+{
+       mgmt_txn_batches_del(src_list, cfg_btch);
+
+       if (update_commit_phase) {
+               MGMTD_TXN_DBG("Move Txn-Id %p Batch-Id %p from '%s' --> '%s'",
+                             cfg_btch->txn, cfg_btch,
+                             mgmt_commit_phase2str(cfg_btch->comm_phase),
+                             mgmt_txn_commit_phase_str(cfg_btch->txn, false));
+               cfg_btch->comm_phase = to_phase;
+       }
+
+       mgmt_txn_batches_add_tail(dst_list, cfg_btch);
+}
+
+static void mgmt_move_txn_cfg_batches(struct mgmt_txn_ctx *txn,
+                                     struct mgmt_commit_cfg_req *cmtcfg_req,
+                                     struct mgmt_txn_batches_head *src_list,
+                                     struct mgmt_txn_batches_head *dst_list,
+                                     bool update_commit_phase,
+                                     enum mgmt_commit_phase to_phase)
+{
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+
+       FOREACH_TXN_CFG_BATCH_IN_LIST (src_list, cfg_btch) {
+               mgmt_move_txn_cfg_batch_to_next(cmtcfg_req, cfg_btch, src_list,
+                                                dst_list, update_commit_phase,
+                                                to_phase);
+       }
+}
+
+static int
+mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn,
+                                  struct mgmt_commit_cfg_req *cmtcfg_req)
+{
+       struct mgmt_txn_batches_head *curr_list, *next_list;
+       enum mgmt_be_client_id id;
+
+       MGMTD_TXN_DBG("Txn-Id %p, Phase(current:'%s' next:'%s')", txn,
+                     mgmt_txn_commit_phase_str(txn, true),
+                     mgmt_txn_commit_phase_str(txn, false));
+
+       /*
+        * Check if all clients has moved to next phase or not.
+        */
+       FOREACH_MGMTD_BE_CLIENT_ID (id) {
+               if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed &&
+                   mgmt_txn_batches_count(&cmtcfg_req->curr_batches[id])) {
+                       /*
+                        * There's atleast once client who hasn't moved to
+                        * next phase.
+                        *
+                        * TODO: Need to re-think this design for the case
+                        * set of validators for a given YANG data item is
+                        * different from the set of notifiers for the same.
+                        */
+                       return -1;
+               }
+       }
+
+       MGMTD_TXN_DBG("Move entire Txn-Id %p from '%s' to '%s'", txn,
+                      mgmt_txn_commit_phase_str(txn, true),
+                      mgmt_txn_commit_phase_str(txn, false));
+
+       /*
+        * If we are here, it means all the clients has moved to next phase.
+        * So we can move the whole commit to next phase.
+        */
+       cmtcfg_req->curr_phase = cmtcfg_req->next_phase;
+       cmtcfg_req->next_phase++;
+       MGMTD_TXN_DBG(
+               "Move back all config batches for Txn %p from next to current branch",
+               txn);
+       FOREACH_MGMTD_BE_CLIENT_ID (id) {
+               curr_list = &cmtcfg_req->curr_batches[id];
+               next_list = &cmtcfg_req->next_batches[id];
+               mgmt_move_txn_cfg_batches(txn, cmtcfg_req, next_list,
+                                          curr_list, false, 0);
+       }
+
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG);
+
+       return 0;
+}
+
+static int
+mgmt_move_be_commit_to_next_phase(struct mgmt_txn_ctx *txn,
+                                 struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       struct mgmt_txn_batches_head *curr_list, *next_list;
+
+       if (txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req)
+               return -1;
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+
+       MGMTD_TXN_DBG(
+               "Move Txn-Id %p for '%s' Phase(current: '%s' next:'%s')", txn,
+               adapter->name, mgmt_txn_commit_phase_str(txn, true),
+               mgmt_txn_commit_phase_str(txn, false));
+
+       MGMTD_TXN_DBG(
+               "Move all config batches for '%s' from current to next list",
+               adapter->name);
+       curr_list = &cmtcfg_req->curr_batches[adapter->id];
+       next_list = &cmtcfg_req->next_batches[adapter->id];
+       mgmt_move_txn_cfg_batches(txn, cmtcfg_req, curr_list, next_list, true,
+                                  cmtcfg_req->next_phase);
+
+       MGMTD_TXN_DBG("Txn-Id %p, Phase(current:'%s' next:'%s')", txn,
+                      mgmt_txn_commit_phase_str(txn, true),
+                      mgmt_txn_commit_phase_str(txn, false));
+
+       /*
+        * Check if all clients has moved to next phase or not.
+        */
+       mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+
+       return 0;
+}
+
+static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req,
+                                          struct nb_config_cbs *changes)
+{
+       struct nb_config_cb *cb, *nxt;
+       struct nb_config_change *chg;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_be_client_subscr_info subscr_info;
+       char *xpath = NULL, *value = NULL;
+       char err_buf[1024];
+       enum mgmt_be_client_id id;
+       struct mgmt_be_client_adapter *adapter;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       bool found_validator;
+       int num_chgs = 0;
+       int xpath_len, value_len;
+
+       cmtcfg_req = &txn_req->req.commit_cfg;
+
+       RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) {
+               chg = (struct nb_config_change *)cb;
+
+               /*
+                * Could have directly pointed to xpath in nb_node.
+                * But dont want to mess with it now.
+                * xpath = chg->cb.nb_node->xpath;
+                */
+               xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0);
+               if (!xpath) {
+                       (void)mgmt_txn_send_commit_cfg_reply(
+                               txn_req->txn, MGMTD_INTERNAL_ERROR,
+                               "Internal error! Could not get Xpath from Ds node!");
+                       goto mgmt_txn_create_config_batches_failed;
+               }
+
+               value = (char *)lyd_get_value(chg->cb.dnode);
+               if (!value)
+                       value = (char *)MGMTD_BE_CONTAINER_NODE_VAL;
+
+               MGMTD_TXN_DBG("XPATH: %s, Value: '%s'", xpath,
+                              value ? value : "NIL");
+
+               if (mgmt_be_get_subscr_info_for_xpath(xpath, &subscr_info)
+                   != 0) {
+                       snprintf(err_buf, sizeof(err_buf),
+                                "No backend module found for XPATH: '%s",
+                                xpath);
+                       (void)mgmt_txn_send_commit_cfg_reply(
+                               txn_req->txn, MGMTD_INTERNAL_ERROR, err_buf);
+                       goto mgmt_txn_create_config_batches_failed;
+               }
+
+               xpath_len = strlen(xpath) + 1;
+               value_len = strlen(value) + 1;
+               found_validator = false;
+               FOREACH_MGMTD_BE_CLIENT_ID (id) {
+                       if (!subscr_info.xpath_subscr[id].validate_config
+                           && !subscr_info.xpath_subscr[id].notify_config)
+                               continue;
+
+                       adapter = mgmt_be_get_adapter_by_id(id);
+                       if (!adapter)
+                               continue;
+
+                       cfg_btch = cmtcfg_req->last_be_cfg_batch[id];
+                       if (!cfg_btch
+                           || (cfg_btch->num_cfg_data
+                               == MGMTD_MAX_CFG_CHANGES_IN_BATCH)
+                           || (cfg_btch->buf_space_left
+                               < (xpath_len + value_len))) {
+                               /* Allocate a new config batch */
+                               cfg_btch = mgmt_txn_cfg_batch_alloc(
+                                       txn_req->txn, id, adapter);
+                       }
+
+                       cfg_btch->buf_space_left -= (xpath_len + value_len);
+                       memcpy(&cfg_btch->xp_subscr[cfg_btch->num_cfg_data],
+                              &subscr_info.xpath_subscr[id],
+                              sizeof(cfg_btch->xp_subscr[0]));
+
+                       mgmt_yang_cfg_data_req_init(
+                               &cfg_btch->cfg_data[cfg_btch->num_cfg_data]);
+                       cfg_btch->cfg_datap[cfg_btch->num_cfg_data] =
+                               &cfg_btch->cfg_data[cfg_btch->num_cfg_data];
+
+                       if (chg->cb.operation == NB_OP_DESTROY)
+                               cfg_btch->cfg_data[cfg_btch->num_cfg_data]
+                                       .req_type =
+                                       MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA;
+                       else
+                               cfg_btch->cfg_data[cfg_btch->num_cfg_data]
+                                       .req_type =
+                                       MGMTD__CFG_DATA_REQ_TYPE__SET_DATA;
+
+                       mgmt_yang_data_init(
+                               &cfg_btch->data[cfg_btch->num_cfg_data]);
+                       cfg_btch->cfg_data[cfg_btch->num_cfg_data].data =
+                               &cfg_btch->data[cfg_btch->num_cfg_data];
+                       cfg_btch->data[cfg_btch->num_cfg_data].xpath = xpath;
+                       xpath = NULL;
+
+                       mgmt_yang_data_value_init(
+                               &cfg_btch->value[cfg_btch->num_cfg_data]);
+                       cfg_btch->data[cfg_btch->num_cfg_data].value =
+                               &cfg_btch->value[cfg_btch->num_cfg_data];
+                       cfg_btch->value[cfg_btch->num_cfg_data].value_case =
+                               MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL;
+                       cfg_btch->value[cfg_btch->num_cfg_data]
+                               .encoded_str_val = value;
+                       value = NULL;
+
+                       if (subscr_info.xpath_subscr[id].validate_config)
+                               found_validator = true;
+
+                       cmtcfg_req->subscr_info.xpath_subscr[id].subscribed |=
+                               subscr_info.xpath_subscr[id].subscribed;
+                       MGMTD_TXN_DBG(
+                               " -- %s, {V:%d, N:%d}, Batch: %p, Item:%d",
+                               adapter->name,
+                               subscr_info.xpath_subscr[id].validate_config,
+                               subscr_info.xpath_subscr[id].notify_config,
+                               cfg_btch, (int)cfg_btch->num_cfg_data);
+
+                       cfg_btch->num_cfg_data++;
+                       num_chgs++;
+               }
+
+               if (!found_validator) {
+                       snprintf(err_buf, sizeof(err_buf),
+                                "No validator module found for XPATH: '%s",
+                                xpath);
+                       MGMTD_TXN_ERR("***** %s", err_buf);
+               }
+       }
+
+       cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs;
+       if (!num_chgs) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn_req->txn, MGMTD_NO_CFG_CHANGES,
+                       "No changes found to commit!");
+               goto mgmt_txn_create_config_batches_failed;
+       }
+
+       cmtcfg_req->next_phase = MGMTD_COMMIT_PHASE_TXN_CREATE;
+       return 0;
+
+mgmt_txn_create_config_batches_failed:
+
+       if (xpath)
+               free(xpath);
+
+       return -1;
+}
+
+static int mgmt_txn_prepare_config(struct mgmt_txn_ctx *txn)
+{
+       struct nb_context nb_ctx;
+       struct nb_config *nb_config;
+       struct nb_config_cbs changes;
+       struct nb_config_cbs *cfg_chgs = NULL;
+       int ret;
+       bool del_cfg_chgs = false;
+
+       ret = 0;
+       memset(&nb_ctx, 0, sizeof(nb_ctx));
+       memset(&changes, 0, sizeof(changes));
+       if (txn->commit_cfg_req->req.commit_cfg.cfg_chgs) {
+               cfg_chgs = txn->commit_cfg_req->req.commit_cfg.cfg_chgs;
+               del_cfg_chgs = true;
+               goto mgmt_txn_prep_config_validation_done;
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.src_ds_id
+           != MGMTD_DS_CANDIDATE) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INVALID_PARAM,
+                       "Source DS cannot be any other than CANDIDATE!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.dst_ds_id
+           != MGMTD_DS_RUNNING) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INVALID_PARAM,
+                       "Destination DS cannot be any other than RUNNING!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       if (!txn->commit_cfg_req->req.commit_cfg.src_ds_ctx) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INVALID_PARAM, "No such source datastore!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       if (!txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INVALID_PARAM,
+                       "No such destination datastore!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.abort) {
+               /*
+                * This is a commit abort request. Return back success.
+                * That should trigger a restore of Candidate datastore to
+                * Running.
+                */
+               (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS,
+                                                     NULL);
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       nb_config = mgmt_ds_get_nb_config(
+               txn->commit_cfg_req->req.commit_cfg.src_ds_ctx);
+       if (!nb_config) {
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INTERNAL_ERROR,
+                       "Unable to retrieve Commit DS Config Tree!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       /*
+        * Check for diffs from scratch buffer. If found empty
+        * get the diff from Candidate DS itself.
+        */
+       cfg_chgs = &nb_config->cfg_chgs;
+       if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
+               /*
+                * This could be the case when the config is directly
+                * loaded onto the candidate DS from a file. Get the
+                * diff from a full comparison of the candidate and
+                * running DSs.
+                */
+               nb_config_diff(
+                       mgmt_ds_get_nb_config(txn->commit_cfg_req->req
+                                                     .commit_cfg.dst_ds_ctx),
+                       nb_config, &changes);
+               cfg_chgs = &changes;
+               del_cfg_chgs = true;
+       }
+
+       if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
+               /*
+                * This means there's no changes to commit whatsoever
+                * is the source of the changes in config.
+                */
+               (void)mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_NO_CFG_CHANGES,
+                       "No changes found to be committed!");
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED
+       if (mm->perf_stats_en)
+               gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats
+                                     ->validate_start,
+                            NULL);
+       /*
+        * Validate YANG contents of the source DS and get the diff
+        * between source and destination DS contents.
+        */
+       char err_buf[1024] = {0};
+       nb_ctx.client = NB_CLIENT_MGMTD_SERVER;
+       nb_ctx.user = (void *)txn;
+       ret = nb_candidate_validate_yang(nb_config, false, err_buf,
+                                        sizeof(err_buf) - 1);
+       if (ret != NB_OK) {
+               if (strncmp(err_buf, " ", strlen(err_buf)) == 0)
+                       strlcpy(err_buf, "Validation failed", sizeof(err_buf));
+               (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM,
+                                                     err_buf);
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+       /*
+        * Perform application level validations locally on the MGMTD
+        * process by calling application specific validation routines
+        * loaded onto MGMTD process using libraries.
+        */
+       ret = nb_candidate_validate_code(&nb_ctx, nb_config, &changes, err_buf,
+                                        sizeof(err_buf) - 1);
+       if (ret != NB_OK) {
+               if (strncmp(err_buf, " ", strlen(err_buf)) == 0)
+                       strlcpy(err_buf, "Validation failed", sizeof(err_buf));
+               (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM,
+                                                     err_buf);
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       if (txn->commit_cfg_req->req.commit_cfg.validate_only) {
+               /*
+                * This was a validate-only COMMIT request return success.
+                */
+               (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS,
+                                                     NULL);
+               goto mgmt_txn_prepare_config_done;
+       }
+#endif /* ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED */
+
+mgmt_txn_prep_config_validation_done:
+
+       if (mm->perf_stats_en)
+               gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats
+                                     ->prep_cfg_start,
+                            NULL);
+
+       /*
+        * Iterate over the diffs and create ordered batches of config
+        * commands to be validated.
+        */
+       ret = mgmt_txn_create_config_batches(txn->commit_cfg_req, cfg_chgs);
+       if (ret != 0) {
+               ret = -1;
+               goto mgmt_txn_prepare_config_done;
+       }
+
+       /* Move to the Transaction Create Phase */
+       txn->commit_cfg_req->req.commit_cfg.curr_phase =
+               MGMTD_COMMIT_PHASE_TXN_CREATE;
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG);
+
+       /*
+        * Start the COMMIT Timeout Timer to abort Txn if things get stuck at
+        * backend.
+        */
+       mgmt_txn_register_event(txn, MGMTD_TXN_COMMITCFG_TIMEOUT);
+mgmt_txn_prepare_config_done:
+
+       if (cfg_chgs && del_cfg_chgs)
+               nb_config_diff_del_changes(cfg_chgs);
+
+       return ret;
+}
+
+static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn)
+{
+       enum mgmt_be_client_id id;
+       struct mgmt_be_client_adapter *adapter;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+
+       assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       FOREACH_MGMTD_BE_CLIENT_ID (id) {
+               if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed) {
+                       adapter = mgmt_be_get_adapter_by_id(id);
+                       if (mgmt_be_create_txn(adapter, txn->txn_id)
+                           != 0) {
+                               (void)mgmt_txn_send_commit_cfg_reply(
+                                       txn, MGMTD_INTERNAL_ERROR,
+                                       "Could not send TXN_CREATE to backend adapter");
+                               return -1;
+                       }
+
+                       FOREACH_TXN_CFG_BATCH_IN_LIST (
+                               &txn->commit_cfg_req->req.commit_cfg
+                                        .curr_batches[id],
+                               cfg_btch)
+                               cfg_btch->comm_phase =
+                                       MGMTD_COMMIT_PHASE_TXN_CREATE;
+               }
+       }
+
+       txn->commit_cfg_req->req.commit_cfg.next_phase =
+               MGMTD_COMMIT_PHASE_SEND_CFG;
+
+       /*
+        * Dont move the commit to next phase yet. Wait for the TXN_REPLY to
+        * come back.
+        */
+
+       MGMTD_TXN_DBG(
+               "Txn:%p Session:0x%llx, Phase(Current:'%s', Next: '%s')", txn,
+               (unsigned long long)txn->session_id,
+               mgmt_txn_commit_phase_str(txn, true),
+               mgmt_txn_commit_phase_str(txn, false));
+
+       return 0;
+}
+
+static int
+mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn,
+                             struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_be_cfgreq cfg_req = {0};
+       size_t num_batches, indx;
+
+       assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       assert(cmtcfg_req->subscr_info.xpath_subscr[adapter->id].subscribed);
+
+       indx = 0;
+       num_batches =
+               mgmt_txn_batches_count(&cmtcfg_req->curr_batches[adapter->id]);
+       FOREACH_TXN_CFG_BATCH_IN_LIST (&cmtcfg_req->curr_batches[adapter->id],
+                                      cfg_btch) {
+               assert(cmtcfg_req->next_phase == MGMTD_COMMIT_PHASE_SEND_CFG);
+
+               cfg_req.cfgdata_reqs = cfg_btch->cfg_datap;
+               cfg_req.num_reqs = cfg_btch->num_cfg_data;
+               indx++;
+               if (mgmt_be_send_cfg_data_create_req(
+                           adapter, txn->txn_id, cfg_btch->batch_id, &cfg_req,
+                           indx == num_batches ? true : false)
+                   != 0) {
+                       (void)mgmt_txn_send_commit_cfg_reply(
+                               txn, MGMTD_INTERNAL_ERROR,
+                               "Internal Error! Could not send config data to backend!");
+                       MGMTD_TXN_ERR(
+                               "Could not send CFGDATA_CREATE for Txn %p Batch %p to client '%s",
+                               txn, cfg_btch, adapter->name);
+                       return -1;
+               }
+
+               cmtcfg_req->cmt_stats->last_num_cfgdata_reqs++;
+               mgmt_move_txn_cfg_batch_to_next(
+                       cmtcfg_req, cfg_btch,
+                       &cmtcfg_req->curr_batches[adapter->id],
+                       &cmtcfg_req->next_batches[adapter->id], true,
+                       MGMTD_COMMIT_PHASE_SEND_CFG);
+       }
+
+       /*
+        * This could ne the last Backend Client to send CFGDATA_CREATE_REQ to.
+        * Try moving the commit to next phase.
+        */
+       mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+
+       return 0;
+}
+
+static int
+mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn,
+                                struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+
+       assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       if (cmtcfg_req->subscr_info.xpath_subscr[adapter->id].subscribed) {
+               adapter = mgmt_be_get_adapter_by_id(adapter->id);
+               (void)mgmt_be_destroy_txn(adapter, txn->txn_id);
+
+               FOREACH_TXN_CFG_BATCH_IN_LIST (
+                       &txn->commit_cfg_req->req.commit_cfg
+                                .curr_batches[adapter->id],
+                       cfg_btch)
+                       cfg_btch->comm_phase = MGMTD_COMMIT_PHASE_TXN_DELETE;
+       }
+
+       return 0;
+}
+
+static void mgmt_txn_cfg_commit_timedout(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+
+       assert(txn->type == MGMTD_TXN_TYPE_CONFIG);
+
+       if (!txn->commit_cfg_req)
+               return;
+
+       MGMTD_TXN_ERR(
+               "Backend operations for Config Txn %p has timedout! Aborting commit!!",
+               txn);
+
+       /*
+        * Send a COMMIT_CONFIG_REPLY with failure.
+        * NOTE: The transaction cleanup will be triggered from Front-end
+        * adapter.
+        */
+       mgmt_txn_send_commit_cfg_reply(
+               txn, MGMTD_INTERNAL_ERROR,
+               "Operation on the backend timed-out. Aborting commit!");
+}
+
+/*
+ * Send CFG_APPLY_REQs to all the backend client.
+ *
+ * NOTE: This is always dispatched when all CFGDATA_CREATE_REQs
+ * for all backend clients has been generated. Please see
+ * mgmt_txn_register_event() and mgmt_txn_process_commit_cfg()
+ * for details.
+ */
+static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn)
+{
+       enum mgmt_be_client_id id;
+       struct mgmt_be_client_adapter *adapter;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       struct mgmt_txn_batches_head *btch_list;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+
+       assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req);
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       if (cmtcfg_req->validate_only) {
+               /*
+                * If this was a validate-only COMMIT request return success.
+                */
+               (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS,
+                                                     NULL);
+               return 0;
+       }
+
+       FOREACH_MGMTD_BE_CLIENT_ID (id) {
+               if (cmtcfg_req->subscr_info.xpath_subscr[id].notify_config) {
+                       adapter = mgmt_be_get_adapter_by_id(id);
+                       if (!adapter)
+                               return -1;
+
+                       btch_list = &cmtcfg_req->curr_batches[id];
+                       if (mgmt_be_send_cfg_apply_req(adapter, txn->txn_id)
+                           != 0) {
+                               (void)mgmt_txn_send_commit_cfg_reply(
+                                       txn, MGMTD_INTERNAL_ERROR,
+                                       "Could not send CFG_APPLY_REQ to backend adapter");
+                               return -1;
+                       }
+                       cmtcfg_req->cmt_stats->last_num_apply_reqs++;
+
+                       UNSET_FLAG(adapter->flags,
+                                  MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+
+                       FOREACH_TXN_CFG_BATCH_IN_LIST (btch_list, cfg_btch)
+                               cfg_btch->comm_phase =
+                                       MGMTD_COMMIT_PHASE_APPLY_CFG;
+               }
+       }
+
+       txn->commit_cfg_req->req.commit_cfg.next_phase =
+               MGMTD_COMMIT_PHASE_TXN_DELETE;
+
+       /*
+        * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES
+        * to come back.
+        */
+
+       return 0;
+}
+
+static void mgmt_txn_process_commit_cfg(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+
+       MGMTD_TXN_DBG(
+               "Processing COMMIT_CONFIG for Txn:%p Session:0x%llx, Phase(Current:'%s', Next: '%s')",
+               txn, (unsigned long long)txn->session_id,
+               mgmt_txn_commit_phase_str(txn, true),
+               mgmt_txn_commit_phase_str(txn, false));
+
+       assert(txn->commit_cfg_req);
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       switch (cmtcfg_req->curr_phase) {
+       case MGMTD_COMMIT_PHASE_PREPARE_CFG:
+               mgmt_txn_prepare_config(txn);
+               break;
+       case MGMTD_COMMIT_PHASE_TXN_CREATE:
+               if (mm->perf_stats_en)
+                       gettimeofday(&cmtcfg_req->cmt_stats->txn_create_start,
+                                    NULL);
+               /*
+                * Send TXN_CREATE_REQ to all Backend now.
+                */
+               mgmt_txn_send_be_txn_create(txn);
+               break;
+       case MGMTD_COMMIT_PHASE_SEND_CFG:
+               if (mm->perf_stats_en)
+                       gettimeofday(&cmtcfg_req->cmt_stats->send_cfg_start,
+                                    NULL);
+                       /*
+                        * All CFGDATA_CREATE_REQ should have been sent to
+                        * Backend by now.
+                        */
+#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED
+               assert(cmtcfg_req->next_phase == MGMTD_COMMIT_PHASE_APPLY_CFG);
+               MGMTD_TXN_DBG(
+                       "Txn:%p Session:0x%llx, trigger sending CFG_VALIDATE_REQ to all backend clients",
+                       txn, (unsigned long long)txn->session_id);
+#else  /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */
+               assert(cmtcfg_req->next_phase == MGMTD_COMMIT_PHASE_APPLY_CFG);
+               MGMTD_TXN_DBG(
+                       "Txn:%p Session:0x%llx, trigger sending CFG_APPLY_REQ to all backend clients",
+                       txn, (unsigned long long)txn->session_id);
+#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */
+               break;
+       case MGMTD_COMMIT_PHASE_APPLY_CFG:
+               if (mm->perf_stats_en)
+                       gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_start,
+                                    NULL);
+               /*
+                * We should have received successful CFG_VALIDATE_REPLY from
+                * all concerned Backend Clients by now. Send out the
+                * CFG_APPLY_REQs now.
+                */
+               mgmt_txn_send_be_cfg_apply(txn);
+               break;
+       case MGMTD_COMMIT_PHASE_TXN_DELETE:
+               if (mm->perf_stats_en)
+                       gettimeofday(&cmtcfg_req->cmt_stats->txn_del_start,
+                                    NULL);
+               /*
+                * We would have sent TXN_DELETE_REQ to all backend by now.
+                * Send a successful CONFIG_COMMIT_REPLY back to front-end.
+                * NOTE: This should also trigger DS merge/unlock and Txn
+                * cleanup. Please see mgmt_fe_send_commit_cfg_reply() for
+                * more details.
+                */
+               THREAD_OFF(txn->comm_cfg_timeout);
+               mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL);
+               break;
+       case MGMTD_COMMIT_PHASE_MAX:
+               break;
+       }
+
+       MGMTD_TXN_DBG(
+               "Txn:%p Session:0x%llx, Phase updated to (Current:'%s', Next: '%s')",
+               txn, (unsigned long long)txn->session_id,
+               mgmt_txn_commit_phase_str(txn, true),
+               mgmt_txn_commit_phase_str(txn, false));
+}
+
+static void mgmt_init_get_data_reply(struct mgmt_get_data_reply *get_reply)
+{
+       size_t indx;
+
+       for (indx = 0; indx < array_size(get_reply->reply_data); indx++)
+               get_reply->reply_datap[indx] = &get_reply->reply_data[indx];
+}
+
+static void mgmt_reset_get_data_reply(struct mgmt_get_data_reply *get_reply)
+{
+       int indx;
+
+       for (indx = 0; indx < get_reply->num_reply; indx++) {
+               if (get_reply->reply_xpathp[indx]) {
+                       free(get_reply->reply_xpathp[indx]);
+                       get_reply->reply_xpathp[indx] = 0;
+               }
+               if (get_reply->reply_data[indx].xpath) {
+                       zlog_debug("%s free xpath %p", __func__,
+                                  get_reply->reply_data[indx].xpath);
+                       free(get_reply->reply_data[indx].xpath);
+                       get_reply->reply_data[indx].xpath = 0;
+               }
+       }
+
+       get_reply->num_reply = 0;
+       memset(&get_reply->data_reply, 0, sizeof(get_reply->data_reply));
+       memset(&get_reply->reply_data, 0, sizeof(get_reply->reply_data));
+       memset(&get_reply->reply_datap, 0, sizeof(get_reply->reply_datap));
+
+       memset(&get_reply->reply_value, 0, sizeof(get_reply->reply_value));
+
+       mgmt_init_get_data_reply(get_reply);
+}
+
+static void mgmt_reset_get_data_reply_buf(struct mgmt_get_data_req *get_data)
+{
+       if (get_data->reply)
+               mgmt_reset_get_data_reply(get_data->reply);
+}
+
+static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req,
+                                            struct mgmt_get_data_req *get_req)
+{
+       struct mgmt_get_data_reply *get_reply;
+       Mgmtd__YangDataReply *data_reply;
+
+       get_reply = get_req->reply;
+       if (!get_reply)
+               return;
+
+       data_reply = &get_reply->data_reply;
+       mgmt_yang_data_reply_init(data_reply);
+       data_reply->n_data = get_reply->num_reply;
+       data_reply->data = get_reply->reply_datap;
+       data_reply->next_indx =
+               (!get_reply->last_batch ? get_req->total_reply : -1);
+
+       MGMTD_TXN_DBG("Sending %d Get-Config/Data replies (next-idx:%lld)",
+               (int) data_reply->n_data,
+               (long long)data_reply->next_indx);
+
+       switch (txn_req->req_event) {
+       case MGMTD_TXN_PROC_GETCFG:
+               if (mgmt_fe_send_get_cfg_reply(
+                           txn_req->txn->session_id, txn_req->txn->txn_id,
+                           get_req->ds_id, txn_req->req_id, MGMTD_SUCCESS,
+                           data_reply, NULL)
+                   != 0) {
+                       MGMTD_TXN_ERR(
+                               "Failed to send GET-CONFIG-REPLY for Txn %p, Sessn: 0x%llx, Req: %llu",
+                               txn_req->txn,
+                               (unsigned long long)txn_req->txn->session_id,
+                               (unsigned long long)txn_req->req_id);
+               }
+               break;
+       case MGMTD_TXN_PROC_GETDATA:
+               if (mgmt_fe_send_get_data_reply(
+                           txn_req->txn->session_id, txn_req->txn->txn_id,
+                           get_req->ds_id, txn_req->req_id, MGMTD_SUCCESS,
+                           data_reply, NULL)
+                   != 0) {
+                       MGMTD_TXN_ERR(
+                               "Failed to send GET-DATA-REPLY for Txn %p, Sessn: 0x%llx, Req: %llu",
+                               txn_req->txn,
+                               (unsigned long long)txn_req->txn->session_id,
+                               (unsigned long long)txn_req->req_id);
+               }
+               break;
+       case MGMTD_TXN_PROC_SETCFG:
+       case MGMTD_TXN_PROC_COMMITCFG:
+       case MGMTD_TXN_COMMITCFG_TIMEOUT:
+       case MGMTD_TXN_CLEANUP:
+               MGMTD_TXN_ERR("Invalid Txn-Req-Event %u",
+                              txn_req->req_event);
+               break;
+       }
+
+       /*
+        * Reset reply buffer for next reply.
+        */
+       mgmt_reset_get_data_reply_buf(get_req);
+}
+
+static void mgmt_txn_iter_and_send_get_cfg_reply(struct mgmt_ds_ctx *ds_ctx,
+                                                 char *xpath,
+                                                 struct lyd_node *node,
+                                                 struct nb_node *nb_node,
+                                                 void *ctx)
+{
+       struct mgmt_txn_req *txn_req;
+       struct mgmt_get_data_req *get_req;
+       struct mgmt_get_data_reply *get_reply;
+       Mgmtd__YangData *data;
+       Mgmtd__YangDataValue *data_value;
+
+       txn_req = (struct mgmt_txn_req *)ctx;
+       if (!txn_req)
+               goto mgmtd_ignore_get_cfg_reply_data;
+
+       if (!(node->schema->nodetype & LYD_NODE_TERM))
+               goto mgmtd_ignore_get_cfg_reply_data;
+
+       assert(txn_req->req_event == MGMTD_TXN_PROC_GETCFG
+              || txn_req->req_event == MGMTD_TXN_PROC_GETDATA);
+
+       get_req = txn_req->req.get_data;
+       assert(get_req);
+       get_reply = get_req->reply;
+       data = &get_reply->reply_data[get_reply->num_reply];
+       data_value = &get_reply->reply_value[get_reply->num_reply];
+
+       mgmt_yang_data_init(data);
+       data->xpath = xpath;
+       mgmt_yang_data_value_init(data_value);
+       data_value->value_case = MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL;
+       data_value->encoded_str_val = (char *)lyd_get_value(node);
+       data->value = data_value;
+
+       get_reply->num_reply++;
+       get_req->total_reply++;
+       MGMTD_TXN_DBG(" [%d] XPATH: '%s', Value: '%s'", get_req->total_reply,
+                      data->xpath, data_value->encoded_str_val);
+
+       if (get_reply->num_reply == MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH)
+               mgmt_txn_send_getcfg_reply_data(txn_req, get_req);
+
+       return;
+
+mgmtd_ignore_get_cfg_reply_data:
+       if (xpath)
+               free(xpath);
+}
+
+static int mgmt_txn_get_config(struct mgmt_txn_ctx *txn,
+                              struct mgmt_txn_req *txn_req,
+                              struct mgmt_ds_ctx *ds_ctx)
+{
+       struct mgmt_txn_reqs_head *req_list = NULL;
+       struct mgmt_txn_reqs_head *pending_list = NULL;
+       int indx;
+       struct mgmt_get_data_req *get_data;
+       struct mgmt_get_data_reply *get_reply;
+
+       switch (txn_req->req_event) {
+       case MGMTD_TXN_PROC_GETCFG:
+               req_list = &txn->get_cfg_reqs;
+               break;
+       case MGMTD_TXN_PROC_GETDATA:
+               req_list = &txn->get_data_reqs;
+               break;
+       case MGMTD_TXN_PROC_SETCFG:
+       case MGMTD_TXN_PROC_COMMITCFG:
+       case MGMTD_TXN_COMMITCFG_TIMEOUT:
+       case MGMTD_TXN_CLEANUP:
+               assert(!"Wrong txn request type!");
+               break;
+       }
+
+       get_data = txn_req->req.get_data;
+
+       if (!get_data->reply) {
+               get_data->reply = XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REPLY,
+                                         sizeof(struct mgmt_get_data_reply));
+               if (!get_data->reply) {
+                       mgmt_fe_send_get_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               get_data->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, NULL,
+                               "Internal error: Unable to allocate reply buffers!");
+                       goto mgmt_txn_get_config_failed;
+               }
+       }
+
+       /*
+        * Read data contents from the DS and respond back directly.
+        * No need to go to backend for getting data.
+        */
+       get_reply = get_data->reply;
+       for (indx = 0; indx < get_data->num_xpaths; indx++) {
+               MGMTD_TXN_DBG("Trying to get all data under '%s'",
+                              get_data->xpaths[indx]);
+               mgmt_init_get_data_reply(get_reply);
+               if (mgmt_ds_iter_data(get_data->ds_ctx, get_data->xpaths[indx],
+                                     mgmt_txn_iter_and_send_get_cfg_reply,
+                                     (void *)txn_req, true)
+                   == -1) {
+                       MGMTD_TXN_DBG("Invalid Xpath '%s",
+                                      get_data->xpaths[indx]);
+                       mgmt_fe_send_get_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               get_data->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, NULL, "Invalid xpath");
+                       goto mgmt_txn_get_config_failed;
+               }
+               MGMTD_TXN_DBG("Got %d remaining data-replies for xpath '%s'",
+                              get_reply->num_reply, get_data->xpaths[indx]);
+               get_reply->last_batch = true;
+               mgmt_txn_send_getcfg_reply_data(txn_req, get_data);
+       }
+
+mgmt_txn_get_config_failed:
+
+       if (pending_list) {
+               /*
+                * Move the transaction to corresponding pending list.
+                */
+               if (req_list)
+                       mgmt_txn_reqs_del(req_list, txn_req);
+               txn_req->pending_be_proc = true;
+               mgmt_txn_reqs_add_tail(pending_list, txn_req);
+               MGMTD_TXN_DBG(
+                       "Moved Req: %p for Txn: %p from Req-List to Pending-List",
+                       txn_req, txn_req->txn);
+       } else {
+               /*
+                * Delete the txn request. It will also remove it from request
+                * list.
+                */
+               mgmt_txn_req_free(&txn_req);
+       }
+
+       return 0;
+}
+
+static void mgmt_txn_process_get_cfg(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       struct mgmt_ds_ctx *ds_ctx;
+       int num_processed = 0;
+       bool error;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+
+       MGMTD_TXN_DBG(
+               "Processing %d GET_CONFIG requests for Txn:%p Session:0x%llx",
+               (int)mgmt_txn_reqs_count(&txn->get_cfg_reqs), txn,
+               (unsigned long long)txn->session_id);
+
+       FOREACH_TXN_REQ_IN_LIST (&txn->get_cfg_reqs, txn_req) {
+               error = false;
+               assert(txn_req->req_event == MGMTD_TXN_PROC_GETCFG);
+               ds_ctx = txn_req->req.get_data->ds_ctx;
+               if (!ds_ctx) {
+                       mgmt_fe_send_get_cfg_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.get_data->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, NULL,
+                               "No such datastore!");
+                       error = true;
+                       goto mgmt_txn_process_get_cfg_done;
+               }
+
+               if (mgmt_txn_get_config(txn, txn_req, ds_ctx) != 0) {
+                       MGMTD_TXN_ERR(
+                               "Unable to retrieve Config from DS %d for Txn %p, Sessn: 0x%llx, Req: %llu!",
+                               txn_req->req.get_data->ds_id, txn,
+                               (unsigned long long)txn->session_id,
+                               (unsigned long long)txn_req->req_id);
+                       error = true;
+               }
+
+       mgmt_txn_process_get_cfg_done:
+
+               if (error) {
+                       /*
+                        * Delete the txn request.
+                        * Note: The following will remove it from the list
+                        * as well.
+                        */
+                       mgmt_txn_req_free(&txn_req);
+               }
+
+               /*
+                * Else the transaction would have been already deleted or
+                * moved to corresponding pending list. No need to delete it.
+                */
+               num_processed++;
+               if (num_processed == MGMTD_TXN_MAX_NUM_GETCFG_PROC)
+                       break;
+       }
+
+       if (mgmt_txn_reqs_count(&txn->get_cfg_reqs)) {
+               MGMTD_TXN_DBG(
+                       "Processed maximum number of Get-Config requests (%d/%d). Rescheduling for rest.",
+                       num_processed, MGMTD_TXN_MAX_NUM_GETCFG_PROC);
+               mgmt_txn_register_event(txn, MGMTD_TXN_PROC_GETCFG);
+       }
+}
+
+static void mgmt_txn_process_get_data(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       struct mgmt_ds_ctx *ds_ctx;
+       int num_processed = 0;
+       bool error;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+
+       MGMTD_TXN_DBG(
+               "Processing %d GET_DATA requests for Txn:%p Session:0x%llx",
+               (int)mgmt_txn_reqs_count(&txn->get_data_reqs), txn,
+               (unsigned long long)txn->session_id);
+
+       FOREACH_TXN_REQ_IN_LIST (&txn->get_data_reqs, txn_req) {
+               error = false;
+               assert(txn_req->req_event == MGMTD_TXN_PROC_GETDATA);
+               ds_ctx = txn_req->req.get_data->ds_ctx;
+               if (!ds_ctx) {
+                       mgmt_fe_send_get_data_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.get_data->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, NULL,
+                               "No such datastore!");
+                       error = true;
+                       goto mgmt_txn_process_get_data_done;
+               }
+
+               if (mgmt_ds_is_config(ds_ctx)) {
+                       if (mgmt_txn_get_config(txn, txn_req, ds_ctx)
+                           != 0) {
+                               MGMTD_TXN_ERR(
+                                       "Unable to retrieve Config from DS %d for Txn %p, Sessn: 0x%llx, Req: %llu!",
+                                       txn_req->req.get_data->ds_id, txn,
+                                       (unsigned long long)txn->session_id,
+                                       (unsigned long long)txn_req->req_id);
+                               error = true;
+                       }
+               } else {
+                       /*
+                        * TODO: Trigger GET procedures for Backend
+                        * For now return back error.
+                        */
+                       mgmt_fe_send_get_data_reply(
+                               txn->session_id, txn->txn_id,
+                               txn_req->req.get_data->ds_id, txn_req->req_id,
+                               MGMTD_INTERNAL_ERROR, NULL,
+                               "GET-DATA on Oper DS is not supported yet!");
+                       error = true;
+               }
+
+       mgmt_txn_process_get_data_done:
+
+               if (error) {
+                       /*
+                        * Delete the txn request.
+                        * Note: The following will remove it from the list
+                        * as well.
+                        */
+                       mgmt_txn_req_free(&txn_req);
+               }
+
+               /*
+                * Else the transaction would have been already deleted or
+                * moved to corresponding pending list. No need to delete it.
+                */
+               num_processed++;
+               if (num_processed == MGMTD_TXN_MAX_NUM_GETDATA_PROC)
+                       break;
+       }
+
+       if (mgmt_txn_reqs_count(&txn->get_data_reqs)) {
+               MGMTD_TXN_DBG(
+                       "Processed maximum number of Get-Data requests (%d/%d). Rescheduling for rest.",
+                       num_processed, MGMTD_TXN_MAX_NUM_GETDATA_PROC);
+               mgmt_txn_register_event(txn, MGMTD_TXN_PROC_GETDATA);
+       }
+}
+
+static struct mgmt_txn_ctx *
+mgmt_fe_find_txn_by_session_id(struct mgmt_master *cm, uint64_t session_id,
+                                   enum mgmt_txn_type type)
+{
+       struct mgmt_txn_ctx *txn;
+
+       FOREACH_TXN_IN_LIST (cm, txn) {
+               if (txn->session_id == session_id && txn->type == type)
+                       return txn;
+       }
+
+       return NULL;
+}
+
+static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id,
+                                                  enum mgmt_txn_type type)
+{
+       struct mgmt_txn_ctx *txn = NULL;
+
+       /*
+        * For 'CONFIG' transaction check if one is already created
+        * or not.
+        */
+       if (type == MGMTD_TXN_TYPE_CONFIG && mgmt_txn_mm->cfg_txn) {
+               if (mgmt_config_txn_in_progress() == session_id)
+                       txn = mgmt_txn_mm->cfg_txn;
+               goto mgmt_create_txn_done;
+       }
+
+       txn = mgmt_fe_find_txn_by_session_id(mgmt_txn_mm, session_id,
+                                                  type);
+       if (!txn) {
+               txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn_ctx));
+               assert(txn);
+
+               txn->session_id = session_id;
+               txn->type = type;
+               mgmt_txn_badapters_init(&txn->be_adapters);
+               mgmt_txns_add_tail(&mgmt_txn_mm->txn_list, txn);
+               mgmt_txn_reqs_init(&txn->set_cfg_reqs);
+               mgmt_txn_reqs_init(&txn->get_cfg_reqs);
+               mgmt_txn_reqs_init(&txn->get_data_reqs);
+               mgmt_txn_reqs_init(&txn->pending_get_datas);
+               txn->commit_cfg_req = NULL;
+               txn->refcount = 0;
+               if (!mgmt_txn_mm->next_txn_id)
+                       mgmt_txn_mm->next_txn_id++;
+               txn->txn_id = mgmt_txn_mm->next_txn_id++;
+               hash_get(mgmt_txn_mm->txn_hash, txn, hash_alloc_intern);
+
+               MGMTD_TXN_DBG("Added new '%s' MGMTD Transaction '%p'",
+                              mgmt_txn_type2str(type), txn);
+
+               if (type == MGMTD_TXN_TYPE_CONFIG)
+                       mgmt_txn_mm->cfg_txn = txn;
+
+               MGMTD_TXN_LOCK(txn);
+       }
+
+mgmt_create_txn_done:
+       return txn;
+}
+
+static void mgmt_txn_delete(struct mgmt_txn_ctx **txn)
+{
+       MGMTD_TXN_UNLOCK(txn);
+}
+
+static unsigned int mgmt_txn_hash_key(const void *data)
+{
+       const struct mgmt_txn_ctx *txn = data;
+
+       return jhash2((uint32_t *) &txn->txn_id,
+                     sizeof(txn->txn_id) / sizeof(uint32_t), 0);
+}
+
+static bool mgmt_txn_hash_cmp(const void *d1, const void *d2)
+{
+       const struct mgmt_txn_ctx *txn1 = d1;
+       const struct mgmt_txn_ctx *txn2 = d2;
+
+       return (txn1->txn_id == txn2->txn_id);
+}
+
+static void mgmt_txn_hash_free(void *data)
+{
+       struct mgmt_txn_ctx *txn = data;
+
+       mgmt_txn_delete(&txn);
+}
+
+static void mgmt_txn_hash_init(void)
+{
+       if (!mgmt_txn_mm || mgmt_txn_mm->txn_hash)
+               return;
+
+       mgmt_txn_mm->txn_hash = hash_create(mgmt_txn_hash_key,
+                                             mgmt_txn_hash_cmp,
+                                             "MGMT Transactions");
+}
+
+static void mgmt_txn_hash_destroy(void)
+{
+       if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
+               return;
+
+       hash_clean(mgmt_txn_mm->txn_hash,
+                  mgmt_txn_hash_free);
+       hash_free(mgmt_txn_mm->txn_hash);
+       mgmt_txn_mm->txn_hash = NULL;
+}
+
+static inline struct mgmt_txn_ctx *
+mgmt_txn_id2ctx(uint64_t txn_id)
+{
+       struct mgmt_txn_ctx key = {0};
+       struct mgmt_txn_ctx *txn;
+
+       if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
+               return NULL;
+
+       key.txn_id = txn_id;
+       txn = hash_lookup(mgmt_txn_mm->txn_hash, &key);
+
+       return txn;
+}
+
+static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file,
+                          int line)
+{
+       txn->refcount++;
+       MGMTD_TXN_DBG("%s:%d --> Lock %s Txn %p, Count: %d", file, line,
+                      mgmt_txn_type2str(txn->type), txn, txn->refcount);
+}
+
+static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file,
+                            int line)
+{
+       assert(*txn && (*txn)->refcount);
+
+       (*txn)->refcount--;
+       MGMTD_TXN_DBG("%s:%d --> Unlock %s Txn %p, Count: %d", file, line,
+                      mgmt_txn_type2str((*txn)->type), *txn,
+                      (*txn)->refcount);
+       if (!(*txn)->refcount) {
+               if ((*txn)->type == MGMTD_TXN_TYPE_CONFIG)
+                       if (mgmt_txn_mm->cfg_txn == *txn)
+                               mgmt_txn_mm->cfg_txn = NULL;
+               THREAD_OFF((*txn)->proc_get_cfg);
+               THREAD_OFF((*txn)->proc_get_data);
+               THREAD_OFF((*txn)->proc_comm_cfg);
+               THREAD_OFF((*txn)->comm_cfg_timeout);
+               hash_release(mgmt_txn_mm->txn_hash, *txn);
+               mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn);
+
+               MGMTD_TXN_DBG("Deleted %s Txn %p for Sessn: 0x%llx",
+                             mgmt_txn_type2str((*txn)->type), *txn,
+                             (unsigned long long)(*txn)->session_id);
+
+               XFREE(MTYPE_MGMTD_TXN, *txn);
+       }
+
+       *txn = NULL;
+}
+
+static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn)
+{
+       /* TODO: Any other cleanup applicable */
+
+       mgmt_txn_delete(txn);
+}
+
+static void
+mgmt_txn_cleanup_all_txns(void)
+{
+       struct mgmt_txn_ctx *txn;
+
+       if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash)
+               return;
+
+       FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn)
+               mgmt_txn_cleanup_txn(&txn);
+}
+
+static void mgmt_txn_cleanup(struct thread *thread)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = (struct mgmt_txn_ctx *)THREAD_ARG(thread);
+       assert(txn);
+
+       mgmt_txn_cleanup_txn(&txn);
+}
+
+static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn,
+                                    enum mgmt_txn_event event)
+{
+       struct timeval tv = {.tv_sec = 0,
+                            .tv_usec = MGMTD_TXN_PROC_DELAY_USEC};
+
+       assert(mgmt_txn_mm && mgmt_txn_tm);
+
+       switch (event) {
+       case MGMTD_TXN_PROC_SETCFG:
+               thread_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_set_cfg,
+                                   txn, &tv, &txn->proc_set_cfg);
+               assert(txn->proc_set_cfg);
+               break;
+       case MGMTD_TXN_PROC_COMMITCFG:
+               thread_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_commit_cfg,
+                                   txn, &tv, &txn->proc_comm_cfg);
+               assert(txn->proc_comm_cfg);
+               break;
+       case MGMTD_TXN_PROC_GETCFG:
+               thread_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_get_cfg,
+                                   txn, &tv, &txn->proc_get_cfg);
+               assert(txn->proc_get_cfg);
+               break;
+       case MGMTD_TXN_PROC_GETDATA:
+               thread_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_get_data,
+                                   txn, &tv, &txn->proc_get_data);
+               assert(txn->proc_get_data);
+               break;
+       case MGMTD_TXN_COMMITCFG_TIMEOUT:
+               thread_add_timer_msec(mgmt_txn_tm,
+                                     mgmt_txn_cfg_commit_timedout, txn,
+                                     MGMTD_TXN_CFG_COMMIT_MAX_DELAY_MSEC,
+                                     &txn->comm_cfg_timeout);
+               assert(txn->comm_cfg_timeout);
+               break;
+       case MGMTD_TXN_CLEANUP:
+               tv.tv_usec = MGMTD_TXN_CLEANUP_DELAY_USEC;
+               thread_add_timer_tv(mgmt_txn_tm, mgmt_txn_cleanup, txn, &tv,
+                                   &txn->clnup);
+               assert(txn->clnup);
+       }
+}
+
+int mgmt_txn_init(struct mgmt_master *mm, struct thread_master *tm)
+{
+       if (mgmt_txn_mm || mgmt_txn_tm)
+               assert(!"MGMTD TXN: Call txn_init() only once");
+
+       mgmt_txn_mm = mm;
+       mgmt_txn_tm = tm;
+       mgmt_txns_init(&mm->txn_list);
+       mgmt_txn_hash_init();
+       assert(!mm->cfg_txn);
+       mm->cfg_txn = NULL;
+
+       return 0;
+}
+
+void mgmt_txn_destroy(void)
+{
+       mgmt_txn_cleanup_all_txns();
+       mgmt_txn_hash_destroy();
+}
+
+uint64_t mgmt_config_txn_in_progress(void)
+{
+       if (mgmt_txn_mm && mgmt_txn_mm->cfg_txn)
+               return mgmt_txn_mm->cfg_txn->session_id;
+
+       return MGMTD_SESSION_ID_NONE;
+}
+
+uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = mgmt_txn_create_new(session_id, type);
+       return txn ? txn->txn_id : MGMTD_TXN_ID_NONE;
+}
+
+bool mgmt_txn_id_is_valid(uint64_t txn_id)
+{
+       return mgmt_txn_id2ctx(txn_id) ? true : false;
+}
+
+void mgmt_destroy_txn(uint64_t *txn_id)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = mgmt_txn_id2ctx(*txn_id);
+       if (!txn)
+               return;
+
+       mgmt_txn_delete(&txn);
+       *txn_id = MGMTD_TXN_ID_NONE;
+}
+
+enum mgmt_txn_type mgmt_get_txn_type(uint64_t txn_id)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return MGMTD_TXN_TYPE_NONE;
+
+       return txn->type;
+}
+
+int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id,
+                                 Mgmtd__DatastoreId ds_id,
+                                 struct mgmt_ds_ctx *ds_ctx,
+                                 Mgmtd__YangCfgDataReq **cfg_req,
+                                 size_t num_req, bool implicit_commit,
+                                 Mgmtd__DatastoreId dst_ds_id,
+                                 struct mgmt_ds_ctx *dst_ds_ctx)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       size_t indx;
+       uint16_t *num_chgs;
+       struct nb_cfg_change *cfg_chg;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return -1;
+
+       if (implicit_commit && mgmt_txn_reqs_count(&txn->set_cfg_reqs)) {
+               MGMTD_TXN_ERR(
+                       "For implicit commit config only one SETCFG-REQ can be allowed!");
+               return -1;
+       }
+
+       txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_SETCFG);
+       txn_req->req.set_cfg->ds_id = ds_id;
+       txn_req->req.set_cfg->ds_ctx = ds_ctx;
+       num_chgs = &txn_req->req.set_cfg->num_cfg_changes;
+       for (indx = 0; indx < num_req; indx++) {
+               cfg_chg = &txn_req->req.set_cfg->cfg_changes[*num_chgs];
+
+               if (cfg_req[indx]->req_type
+                   == MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA)
+                       cfg_chg->operation = NB_OP_DESTROY;
+               else if (cfg_req[indx]->req_type
+                        == MGMTD__CFG_DATA_REQ_TYPE__SET_DATA)
+                       cfg_chg->operation =
+                               mgmt_ds_find_data_node_by_xpath(
+                                       ds_ctx, cfg_req[indx]->data->xpath)
+                                       ? NB_OP_MODIFY
+                                       : NB_OP_CREATE;
+               else
+                       continue;
+
+               MGMTD_TXN_DBG(
+                       "XPath: '%s', Value: '%s'", cfg_req[indx]->data->xpath,
+                       (cfg_req[indx]->data->value
+                                        && cfg_req[indx]
+                                                   ->data->value
+                                                   ->encoded_str_val
+                                ? cfg_req[indx]->data->value->encoded_str_val
+                                : "NULL"));
+               strlcpy(cfg_chg->xpath, cfg_req[indx]->data->xpath,
+                       sizeof(cfg_chg->xpath));
+               cfg_chg->value = (cfg_req[indx]->data->value
+                                                 && cfg_req[indx]
+                                                            ->data->value
+                                                            ->encoded_str_val
+                                         ? strdup(cfg_req[indx]
+                                                          ->data->value
+                                                          ->encoded_str_val)
+                                         : NULL);
+               if (cfg_chg->value)
+                       MGMTD_TXN_DBG("Allocated value at %p ==> '%s'",
+                                      cfg_chg->value, cfg_chg->value);
+
+               (*num_chgs)++;
+       }
+       txn_req->req.set_cfg->implicit_commit = implicit_commit;
+       txn_req->req.set_cfg->dst_ds_id = dst_ds_id;
+       txn_req->req.set_cfg->dst_ds_ctx = dst_ds_ctx;
+       txn_req->req.set_cfg->setcfg_stats =
+               mgmt_fe_get_session_setcfg_stats(txn->session_id);
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_SETCFG);
+
+       return 0;
+}
+
+int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
+                                    Mgmtd__DatastoreId src_ds_id,
+                                    struct mgmt_ds_ctx *src_ds_ctx,
+                                    Mgmtd__DatastoreId dst_ds_id,
+                                    struct mgmt_ds_ctx *dst_ds_ctx,
+                                    bool validate_only, bool abort,
+                                    bool implicit)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return -1;
+
+       if (txn->commit_cfg_req) {
+               MGMTD_TXN_ERR(
+                       "A commit is already in-progress for Txn %p, session 0x%llx. Cannot start another!",
+                       txn, (unsigned long long)txn->session_id);
+               return -1;
+       }
+
+       txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_COMMITCFG);
+       txn_req->req.commit_cfg.src_ds_id = src_ds_id;
+       txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx;
+       txn_req->req.commit_cfg.dst_ds_id = dst_ds_id;
+       txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx;
+       txn_req->req.commit_cfg.validate_only = validate_only;
+       txn_req->req.commit_cfg.abort = abort;
+       txn_req->req.commit_cfg.implicit = implicit;
+       txn_req->req.commit_cfg.cmt_stats =
+               mgmt_fe_get_session_commit_stats(txn->session_id);
+
+       /*
+        * Trigger a COMMIT-CONFIG process.
+        */
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG);
+       return 0;
+}
+
+int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter,
+                                       bool connect)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       struct mgmt_commit_cfg_req *cmtcfg_req;
+       static struct mgmt_commit_stats dummy_stats;
+       struct nb_config_cbs *adapter_cfgs = NULL;
+
+       memset(&dummy_stats, 0, sizeof(dummy_stats));
+       if (connect) {
+               /* Get config for this single backend client */
+               mgmt_be_get_adapter_config(adapter, mm->running_ds,
+                                             &adapter_cfgs);
+
+               if (!adapter_cfgs || RB_EMPTY(nb_config_cbs, adapter_cfgs)) {
+                       SET_FLAG(adapter->flags,
+                                MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+                       return 0;
+               }
+
+               /*
+                * Create a CONFIG transaction to push the config changes
+                * provided to the backend client.
+                */
+               txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG);
+               if (!txn) {
+                       MGMTD_TXN_ERR(
+                               "Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'",
+                               adapter->name);
+                       return -1;
+               }
+
+               /*
+                * Set the changeset for transaction to commit and trigger the
+                * commit request.
+                */
+               txn_req =
+                       mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG);
+               txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_NONE;
+               txn_req->req.commit_cfg.src_ds_ctx = 0;
+               txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_NONE;
+               txn_req->req.commit_cfg.dst_ds_ctx = 0;
+               txn_req->req.commit_cfg.validate_only = false;
+               txn_req->req.commit_cfg.abort = false;
+               txn_req->req.commit_cfg.cmt_stats = &dummy_stats;
+               txn_req->req.commit_cfg.cfg_chgs = adapter_cfgs;
+
+               /*
+                * Trigger a COMMIT-CONFIG process.
+                */
+               mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG);
+
+       } else {
+               /*
+                * Check if any transaction is currently on-going that
+                * involves this backend client. If so, report the transaction
+                * has failed.
+                */
+               FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) {
+                       if (txn->type == MGMTD_TXN_TYPE_CONFIG) {
+                               cmtcfg_req = txn->commit_cfg_req
+                                                    ? &txn->commit_cfg_req
+                                                               ->req.commit_cfg
+                                                    : NULL;
+                               if (cmtcfg_req
+                                   && cmtcfg_req->subscr_info
+                                              .xpath_subscr[adapter->id]
+                                              .subscribed) {
+                                       mgmt_txn_send_commit_cfg_reply(
+                                               txn, MGMTD_INTERNAL_ERROR,
+                                               "Backend daemon disconnected while processing commit!");
+                               }
+                       }
+               }
+       }
+
+       return 0;
+}
+
+int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create,
+                                     bool success,
+                                     struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG)
+               return -1;
+
+       if (!create && !txn->commit_cfg_req)
+               return 0;
+
+       assert(txn->commit_cfg_req);
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+       if (create) {
+               if (success) {
+                       /*
+                        * Done with TXN_CREATE. Move the backend client to
+                        * next phase.
+                        */
+                       assert(cmtcfg_req->curr_phase
+                              == MGMTD_COMMIT_PHASE_TXN_CREATE);
+
+                       /*
+                        * Send CFGDATA_CREATE-REQs to the backend immediately.
+                        */
+                       mgmt_txn_send_be_cfg_data(txn, adapter);
+               } else {
+                       mgmt_txn_send_commit_cfg_reply(
+                               txn, MGMTD_INTERNAL_ERROR,
+                               "Internal error! Failed to initiate transaction at backend!");
+               }
+       } else {
+               /*
+                * Done with TXN_DELETE. Move the backend client to next phase.
+                */
+               if (false)
+                       mgmt_move_be_commit_to_next_phase(txn, adapter);
+       }
+
+       return 0;
+}
+
+int mgmt_txn_notify_be_cfgdata_reply(
+       uint64_t txn_id, uint64_t batch_id, bool success, char *error_if_any,
+       struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG)
+               return -1;
+
+       if (!txn->commit_cfg_req)
+               return -1;
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+
+       cfg_btch = mgmt_txn_cfgbatch_id2ctx(txn, batch_id);
+       if (!cfg_btch || cfg_btch->txn != txn)
+               return -1;
+
+       if (!success) {
+               MGMTD_TXN_ERR(
+                       "CFGDATA_CREATE_REQ sent to '%s' failed for Txn %p, Batch %p, Err: %s",
+                       adapter->name, txn, cfg_btch,
+                       error_if_any ? error_if_any : "None");
+               mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INTERNAL_ERROR,
+                       "Internal error! Failed to download config data to backend!");
+               return 0;
+       }
+
+       MGMTD_TXN_DBG(
+               "CFGDATA_CREATE_REQ sent to '%s' was successful for Txn %p, Batch %p, Err: %s",
+               adapter->name, txn, cfg_btch,
+               error_if_any ? error_if_any : "None");
+       mgmt_move_txn_cfg_batch_to_next(
+               cmtcfg_req, cfg_btch, &cmtcfg_req->curr_batches[adapter->id],
+               &cmtcfg_req->next_batches[adapter->id], true,
+               MGMTD_COMMIT_PHASE_APPLY_CFG);
+
+       mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+
+       return 0;
+}
+
+int mgmt_txn_notify_be_cfg_validate_reply(
+       uint64_t txn_id, bool success, uint64_t batch_ids[],
+       size_t num_batch_ids, char *error_if_any,
+       struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+       size_t indx;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG)
+               return -1;
+
+       assert(txn->commit_cfg_req);
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+
+       if (!success) {
+               MGMTD_TXN_ERR(
+                       "CFGDATA_VALIDATE_REQ sent to '%s' failed for Txn %p, Batches [0x%llx - 0x%llx], Err: %s",
+                       adapter->name, txn, (unsigned long long)batch_ids[0],
+                       (unsigned long long)batch_ids[num_batch_ids - 1],
+                       error_if_any ? error_if_any : "None");
+               mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INTERNAL_ERROR,
+                       "Internal error! Failed to validate config data on backend!");
+               return 0;
+       }
+
+       for (indx = 0; indx < num_batch_ids; indx++) {
+               cfg_btch = mgmt_txn_cfgbatch_id2ctx(txn, batch_ids[indx]);
+               if (cfg_btch->txn != txn)
+                       return -1;
+               mgmt_move_txn_cfg_batch_to_next(
+                       cmtcfg_req, cfg_btch,
+                       &cmtcfg_req->curr_batches[adapter->id],
+                       &cmtcfg_req->next_batches[adapter->id], true,
+                       MGMTD_COMMIT_PHASE_APPLY_CFG);
+       }
+
+       mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+
+       return 0;
+}
+
+extern int
+mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success,
+                                      uint64_t batch_ids[],
+                                      size_t num_batch_ids, char *error_if_any,
+                                      struct mgmt_be_client_adapter *adapter)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_be_cfg_batch *cfg_btch;
+       struct mgmt_commit_cfg_req *cmtcfg_req = NULL;
+       size_t indx;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG
+           || !txn->commit_cfg_req)
+               return -1;
+
+       cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg;
+
+       if (!success) {
+               MGMTD_TXN_ERR(
+                       "CFGDATA_APPLY_REQ sent to '%s' failed for Txn %p, Batches [0x%llx - 0x%llx], Err: %s",
+                       adapter->name, txn, (unsigned long long)batch_ids[0],
+                       (unsigned long long)batch_ids[num_batch_ids - 1],
+                       error_if_any ? error_if_any : "None");
+               mgmt_txn_send_commit_cfg_reply(
+                       txn, MGMTD_INTERNAL_ERROR,
+                       "Internal error! Failed to apply config data on backend!");
+               return 0;
+       }
+
+       for (indx = 0; indx < num_batch_ids; indx++) {
+               cfg_btch = mgmt_txn_cfgbatch_id2ctx(txn, batch_ids[indx]);
+               if (cfg_btch->txn != txn)
+                       return -1;
+               mgmt_move_txn_cfg_batch_to_next(
+                       cmtcfg_req, cfg_btch,
+                       &cmtcfg_req->curr_batches[adapter->id],
+                       &cmtcfg_req->next_batches[adapter->id], true,
+                       MGMTD_COMMIT_PHASE_TXN_DELETE);
+       }
+
+       if (!mgmt_txn_batches_count(&cmtcfg_req->curr_batches[adapter->id])) {
+               /*
+                * All configuration for the specific backend has been applied.
+                * Send TXN-DELETE to wrap up the transaction for this backend.
+                */
+               SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED);
+               mgmt_txn_send_be_txn_delete(txn, adapter);
+       }
+
+       mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req);
+       if (mm->perf_stats_en)
+               gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL);
+
+       return 0;
+}
+
+int mgmt_txn_send_commit_config_reply(uint64_t txn_id,
+                                      enum mgmt_result result,
+                                      const char *error_if_any)
+{
+       struct mgmt_txn_ctx *txn;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return -1;
+
+       if (!txn->commit_cfg_req) {
+               MGMTD_TXN_ERR(
+                       "NO commit in-progress for Txn %p, session 0x%llx!",
+                       txn, (unsigned long long)txn->session_id);
+               return -1;
+       }
+
+       return mgmt_txn_send_commit_cfg_reply(txn, result, error_if_any);
+}
+
+int mgmt_txn_send_get_config_req(uint64_t txn_id, uint64_t req_id,
+                                 Mgmtd__DatastoreId ds_id,
+                                 struct mgmt_ds_ctx *ds_ctx,
+                                 Mgmtd__YangGetDataReq **data_req,
+                                 size_t num_reqs)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       size_t indx;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return -1;
+
+       txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETCFG);
+       txn_req->req.get_data->ds_id = ds_id;
+       txn_req->req.get_data->ds_ctx = ds_ctx;
+       for (indx = 0;
+            indx < num_reqs && indx < MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH;
+            indx++) {
+               MGMTD_TXN_DBG("XPath: '%s'", data_req[indx]->data->xpath);
+               txn_req->req.get_data->xpaths[indx] =
+                       strdup(data_req[indx]->data->xpath);
+               txn_req->req.get_data->num_xpaths++;
+       }
+
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_GETCFG);
+
+       return 0;
+}
+
+int mgmt_txn_send_get_data_req(uint64_t txn_id, uint64_t req_id,
+                               Mgmtd__DatastoreId ds_id,
+                               struct mgmt_ds_ctx *ds_ctx,
+                               Mgmtd__YangGetDataReq **data_req,
+                               size_t num_reqs)
+{
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       size_t indx;
+
+       txn = mgmt_txn_id2ctx(txn_id);
+       if (!txn)
+               return -1;
+
+       txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETDATA);
+       txn_req->req.get_data->ds_id = ds_id;
+       txn_req->req.get_data->ds_ctx = ds_ctx;
+       for (indx = 0;
+            indx < num_reqs && indx < MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH;
+            indx++) {
+               MGMTD_TXN_DBG("XPath: '%s'", data_req[indx]->data->xpath);
+               txn_req->req.get_data->xpaths[indx] =
+                       strdup(data_req[indx]->data->xpath);
+               txn_req->req.get_data->num_xpaths++;
+       }
+
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_GETDATA);
+
+       return 0;
+}
+
+void mgmt_txn_status_write(struct vty *vty)
+{
+       struct mgmt_txn_ctx *txn;
+
+       vty_out(vty, "MGMTD Transactions\n");
+
+       FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) {
+               vty_out(vty, "  Txn: \t\t\t%p\n", txn);
+               vty_out(vty, "    Txn-Id: \t\t\t%llu\n",
+                       (unsigned long long)txn->txn_id);
+               vty_out(vty, "    Session-Id: \t\t%llu\n",
+                       (unsigned long long)txn->session_id);
+               vty_out(vty, "    Type: \t\t\t%s\n",
+                       mgmt_txn_type2str(txn->type));
+               vty_out(vty, "    Ref-Count: \t\t\t%d\n", txn->refcount);
+       }
+       vty_out(vty, "  Total: %d\n",
+               (int)mgmt_txns_count(&mgmt_txn_mm->txn_list));
+}
+
+int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
+                                       struct mgmt_ds_ctx *dst_ds_ctx)
+{
+       static struct nb_config_cbs changes;
+       struct nb_config_cbs *cfg_chgs = NULL;
+       struct mgmt_txn_ctx *txn;
+       struct mgmt_txn_req *txn_req;
+       static struct mgmt_commit_stats dummy_stats;
+
+       memset(&changes, 0, sizeof(changes));
+       memset(&dummy_stats, 0, sizeof(dummy_stats));
+       /*
+        * This could be the case when the config is directly
+        * loaded onto the candidate DS from a file. Get the
+        * diff from a full comparison of the candidate and
+        * running DSs.
+        */
+       nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx),
+                      mgmt_ds_get_nb_config(src_ds_ctx), &changes);
+       cfg_chgs = &changes;
+
+       if (RB_EMPTY(nb_config_cbs, cfg_chgs)) {
+               /*
+                * This means there's no changes to commit whatsoever
+                * is the source of the changes in config.
+                */
+               return -1;
+       }
+
+       /*
+        * Create a CONFIG transaction to push the config changes
+        * provided to the backend client.
+        */
+       txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG);
+       if (!txn) {
+               MGMTD_TXN_ERR(
+                       "Failed to create CONFIG Transaction for downloading CONFIGs");
+               return -1;
+       }
+
+       /*
+        * Set the changeset for transaction to commit and trigger the commit
+        * request.
+        */
+       txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG);
+       txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_CANDIDATE;
+       txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx;
+       txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING;
+       txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx;
+       txn_req->req.commit_cfg.validate_only = false;
+       txn_req->req.commit_cfg.abort = false;
+       txn_req->req.commit_cfg.rollback = true;
+       txn_req->req.commit_cfg.cmt_stats = &dummy_stats;
+       txn_req->req.commit_cfg.cfg_chgs = cfg_chgs;
+
+       /*
+        * Trigger a COMMIT-CONFIG process.
+        */
+       mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG);
+       return 0;
+}
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
new file mode 100644 (file)
index 0000000..f026a39
--- /dev/null
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MGMTD Transactions
+ *
+ * Copyright (C) 2021  Vmware, Inc.
+ *                    Pushpasis Sarkar <spushpasis@vmware.com>
+ */
+
+#ifndef _FRR_MGMTD_TXN_H_
+#define _FRR_MGMTD_TXN_H_
+
+#include "mgmtd/mgmt_be_adapter.h"
+#include "mgmtd/mgmt.h"
+#include "mgmtd/mgmt_ds.h"
+
+#define MGMTD_TXN_PROC_DELAY_MSEC 5
+#define MGMTD_TXN_PROC_DELAY_USEC 10
+#define MGMTD_TXN_MAX_NUM_SETCFG_PROC 128
+#define MGMTD_TXN_MAX_NUM_GETCFG_PROC 128
+#define MGMTD_TXN_MAX_NUM_GETDATA_PROC 128
+
+#define MGMTD_TXN_SEND_CFGVALIDATE_DELAY_MSEC 100
+#define MGMTD_TXN_SEND_CFGAPPLY_DELAY_MSEC 100
+#define MGMTD_TXN_CFG_COMMIT_MAX_DELAY_MSEC 30000 /* 30 seconds */
+
+#define MGMTD_TXN_CLEANUP_DELAY_MSEC 100
+#define MGMTD_TXN_CLEANUP_DELAY_USEC 10
+
+/*
+ * The following definition enables local validation of config
+ * on the MGMTD process by loading client-defined NB callbacks
+ * and calling them locally before sening CNFG_APPLY_REQ to
+ * backend for actual apply of configuration on internal state
+ * of the backend application.
+ *
+ * #define MGMTD_LOCAL_VALIDATIONS_ENABLED
+ *
+ * Note: Enabled by default in configure.ac, if this needs to be
+ * disabled then pass --enable-mgmtd-local-validations=no to
+ * the list of arguments passed to ./configure
+ */
+
+PREDECL_LIST(mgmt_txns);
+
+struct mgmt_master;
+
+enum mgmt_txn_type {
+       MGMTD_TXN_TYPE_NONE = 0,
+       MGMTD_TXN_TYPE_CONFIG,
+       MGMTD_TXN_TYPE_SHOW
+};
+
+static inline const char *mgmt_txn_type2str(enum mgmt_txn_type type)
+{
+       switch (type) {
+       case MGMTD_TXN_TYPE_NONE:
+               return "None";
+       case MGMTD_TXN_TYPE_CONFIG:
+               return "CONFIG";
+       case MGMTD_TXN_TYPE_SHOW:
+               return "SHOW";
+       }
+
+       return "Unknown";
+}
+
+/* Initialise transaction module. */
+extern int mgmt_txn_init(struct mgmt_master *cm, struct thread_master *tm);
+
+/* Destroy the transaction module. */
+extern void mgmt_txn_destroy(void);
+
+/*
+ * Check if transaction is in progress.
+ *
+ * Returns:
+ *    session ID if in-progress, MGMTD_SESSION_ID_NONE otherwise.
+ */
+extern uint64_t mgmt_config_txn_in_progress(void);
+
+/*
+ * Create transaction.
+ *
+ * session_id
+ *    Session ID.
+ *
+ * type
+ *    Transaction type (CONFIG/SHOW/NONE)
+ *
+ * Returns:
+ *    transaction ID.
+ */
+extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type);
+
+/*
+ * Destroy transaction.
+ *
+ * txn_id
+ *     Unique transaction identifier.
+ */
+extern void mgmt_destroy_txn(uint64_t *txn_id);
+
+/*
+ * Check if transaction is valid given an ID.
+ */
+extern bool mgmt_txn_id_is_valid(uint64_t txn_id);
+
+/*
+ * Returns the type of transaction given an ID.
+ */
+extern enum mgmt_txn_type mgmt_get_txn_type(uint64_t txn_id);
+
+/*
+ * Send set-config request to be processed later in transaction.
+ *
+ * txn_id
+ *    Unique transaction identifier.
+ *
+ * req_id
+ *    Unique transaction request identifier.
+ *
+ * ds_id
+ *    Datastore ID.
+ *
+ * ds_hndl
+ *    Datastore handle.
+ *
+ * cfg_req
+ *    Config requests.
+ *
+ * num_req
+ *    Number of config requests.
+ *
+ * implicit_commit
+ *    TRUE if the commit is implicit, FALSE otherwise.
+ *
+ * dst_ds_id
+ *    Destination datastore ID.
+ *
+ * dst_ds_handle
+ *    Destination datastore handle.
+ *
+ * Returns:
+ *    0 on success, -1 on failures.
+ */
+extern int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id,
+                                        Mgmtd__DatastoreId ds_id,
+                                        struct mgmt_ds_ctx *ds_ctx,
+                                        Mgmtd__YangCfgDataReq **cfg_req,
+                                        size_t num_req, bool implicit_commit,
+                                        Mgmtd__DatastoreId dst_ds_id,
+                                        struct mgmt_ds_ctx *dst_ds_ctx);
+
+/*
+ * Send commit-config request to be processed later in transaction.
+ *
+ * txn_id
+ *    Unique transaction identifier.
+ *
+ * req_id
+ *    Unique transaction request identifier.
+ *
+ * src_ds_id
+ *    Source datastore ID.
+ *
+ * src_ds_hndl
+ *    Source Datastore handle.
+ *
+ * validate_only
+ *    TRUE if commit request needs to be validated only, FALSE otherwise.
+ *
+ * abort
+ *    TRUE if need to restore Src DS back to Dest DS, FALSE otherwise.
+ *
+ * implicit
+ *    TRUE if the commit is implicit, FALSE otherwise.
+ *
+ * Returns:
+ *    0 on success, -1 on failures.
+ */
+extern int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
+                                           Mgmtd__DatastoreId src_ds_id,
+                                           struct mgmt_ds_ctx *dst_ds_ctx,
+                                           Mgmtd__DatastoreId dst_ds_id,
+                                           struct mgmt_ds_ctx *src_ds_ctx,
+                                           bool validate_only, bool abort,
+                                           bool implicit);
+
+extern int mgmt_txn_send_commit_config_reply(uint64_t txn_id,
+                                             enum mgmt_result result,
+                                             const char *error_if_any);
+
+/*
+ * Send get-config request to be processed later in transaction.
+ *
+ * Similar to set-config request.
+ */
+extern int mgmt_txn_send_get_config_req(uint64_t txn_id, uint64_t req_id,
+                                        Mgmtd__DatastoreId ds_id,
+                                        struct mgmt_ds_ctx *ds_ctx,
+                                        Mgmtd__YangGetDataReq **data_req,
+                                        size_t num_reqs);
+
+/*
+ * Send get-data request to be processed later in transaction.
+ *
+ * Similar to get-config request, but here data is fetched from backedn client.
+ */
+extern int mgmt_txn_send_get_data_req(uint64_t txn_id, uint64_t req_id,
+                                      Mgmtd__DatastoreId ds_id,
+                                      struct mgmt_ds_ctx *ds_ctx,
+                                      Mgmtd__YangGetDataReq **data_req,
+                                      size_t num_reqs);
+
+/*
+ * Notifiy backend adapter on connection.
+ */
+extern int
+mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter,
+                                   bool connect);
+
+/*
+ * Reply to backend adapter about transaction create/delete.
+ */
+extern int
+mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success,
+                                 struct mgmt_be_client_adapter *adapter);
+
+/*
+ * Reply to backend adapater with config data create request.
+ */
+extern int
+mgmt_txn_notify_be_cfgdata_reply(uint64_t txn_id, uint64_t batch_id,
+                                    bool success, char *error_if_any,
+                                    struct mgmt_be_client_adapter *adapter);
+
+/*
+ * Reply to backend adapater with config data validate request.
+ */
+extern int mgmt_txn_notify_be_cfg_validate_reply(
+       uint64_t txn_id, bool success, uint64_t batch_ids[],
+       size_t num_batch_ids, char *error_if_any,
+       struct mgmt_be_client_adapter *adapter);
+
+/*
+ * Reply to backend adapater with config data apply request.
+ */
+extern int
+mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success,
+                                      uint64_t batch_ids[],
+                                      size_t num_batch_ids, char *error_if_any,
+                                      struct mgmt_be_client_adapter *adapter);
+
+/*
+ * Dump transaction status to vty.
+ */
+extern void mgmt_txn_status_write(struct vty *vty);
+
+/*
+ * Trigger rollback config apply.
+ *
+ * Creates a new transaction and commit request for rollback.
+ */
+extern int
+mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx,
+                                    struct mgmt_ds_ctx *dst_ds_ctx);
+#endif /* _FRR_MGMTD_TXN_H_ */
index 8ee5921dbfb2ab0e64b3eee1bd3a58c57ab55403..0ac4ebc9e65f49bd657021dff39f3906133f98b6 100644 (file)
@@ -16,6 +16,7 @@
 #include "mgmtd/mgmt_fe_server.h"
 #include "mgmtd/mgmt_fe_adapter.h"
 #include "mgmtd/mgmt_ds.h"
+#include "mgmtd/mgmt_history.h"
 
 #include "mgmtd/mgmt_vty_clippy.c"
 
@@ -57,6 +58,45 @@ DEFPY(show_mgmt_fe_adapter, show_mgmt_fe_adapter_cmd,
        return CMD_SUCCESS;
 }
 
+DEFPY_HIDDEN(mgmt_performance_measurement,
+            mgmt_performance_measurement_cmd,
+            "[no] mgmt performance-measurement",
+            NO_STR
+            MGMTD_STR
+            "Enable performance measurement\n")
+{
+       if (no)
+               mgmt_fe_adapter_perf_measurement(vty, false);
+       else
+               mgmt_fe_adapter_perf_measurement(vty, true);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY(mgmt_reset_performance_stats,
+      mgmt_reset_performance_stats_cmd,
+      "mgmt reset-statistics",
+      MGMTD_STR
+      "Reset the Performance measurement statistics\n")
+{
+       mgmt_fe_adapter_reset_perf_stats(vty);
+
+       return CMD_SUCCESS;
+}
+
+DEFPY(show_mgmt_txn,
+      show_mgmt_txn_cmd,
+      "show mgmt transaction all",
+      SHOW_STR
+      MGMTD_STR
+      MGMTD_TXN_STR
+      "Display all Transactions\n")
+{
+       mgmt_txn_status_write(vty);
+
+       return CMD_SUCCESS;
+}
+
 DEFPY(show_mgmt_ds,
       show_mgmt_ds_cmd,
       "show mgmt datastore [all|candidate|operational|running]$dsname",
@@ -309,6 +349,43 @@ DEFPY(mgmt_save_config,
        return CMD_SUCCESS;
 }
 
+DEFPY(show_mgmt_cmt_hist,
+      show_mgmt_cmt_hist_cmd,
+      "show mgmt commit-history",
+      SHOW_STR
+      MGMTD_STR
+      "Show commit history\n")
+{
+       show_mgmt_cmt_history(vty);
+       return CMD_SUCCESS;
+}
+
+DEFPY(mgmt_rollback,
+      mgmt_rollback_cmd,
+      "mgmt rollback <commit-id WORD$commit | last [(1-10)]$last>",
+      MGMTD_STR
+      "Rollback commits\n"
+      "Rollback to commit ID\n"
+      "Commit-ID\n"
+      "Rollbak n commits\n"
+      "Number of commits\n")
+{
+       if (commit)
+               mgmt_history_rollback_by_id(vty, commit);
+       else
+               mgmt_history_rollback_n(vty, last);
+
+       return CMD_SUCCESS;
+}
+
+static int config_write_mgmt_debug(struct vty *vty);
+static struct cmd_node debug_node = {
+       .name = "debug",
+       .node = DEBUG_NODE,
+       .prompt = "",
+       .config_write = config_write_mgmt_debug,
+};
+
 static int config_write_mgmt_debug(struct vty *vty)
 {
        int n = mgmt_debug_be + mgmt_debug_fe + mgmt_debug_ds + mgmt_debug_txn;
@@ -333,12 +410,6 @@ static int config_write_mgmt_debug(struct vty *vty)
 
        return 0;
 }
-static struct cmd_node debug_node = {
-       .name = "debug",
-       .node = DEBUG_NODE,
-       .prompt = "",
-       .config_write = config_write_mgmt_debug,
-};
 
 DEFPY(debug_mgmt,
       debug_mgmt_cmd,
@@ -388,22 +459,29 @@ void mgmt_vty_init(void)
        install_element(VIEW_NODE, &show_mgmt_be_adapter_cmd);
        install_element(VIEW_NODE, &show_mgmt_be_xpath_reg_cmd);
        install_element(VIEW_NODE, &show_mgmt_fe_adapter_cmd);
+       install_element(VIEW_NODE, &show_mgmt_txn_cmd);
        install_element(VIEW_NODE, &show_mgmt_ds_cmd);
        install_element(VIEW_NODE, &show_mgmt_get_config_cmd);
        install_element(VIEW_NODE, &show_mgmt_get_data_cmd);
        install_element(VIEW_NODE, &show_mgmt_dump_data_cmd);
        install_element(VIEW_NODE, &show_mgmt_map_xpath_cmd);
+       install_element(VIEW_NODE, &show_mgmt_cmt_hist_cmd);
 
        install_element(CONFIG_NODE, &mgmt_commit_cmd);
        install_element(CONFIG_NODE, &mgmt_set_config_data_cmd);
        install_element(CONFIG_NODE, &mgmt_delete_config_data_cmd);
        install_element(CONFIG_NODE, &mgmt_load_config_cmd);
        install_element(CONFIG_NODE, &mgmt_save_config_cmd);
+       install_element(CONFIG_NODE, &mgmt_rollback_cmd);
 
        install_element(VIEW_NODE, &debug_mgmt_cmd);
        install_element(CONFIG_NODE, &debug_mgmt_cmd);
 
+       /* Enable view */
+       install_element(ENABLE_NODE, &mgmt_performance_measurement_cmd);
+       install_element(ENABLE_NODE, &mgmt_reset_performance_stats_cmd);
+
        /*
-        * TODO: Register and handlers for auto-completion here (if any).
+        * TODO: Register and handlers for auto-completion here.
         */
 }
index 64228f968b91deb2c2aa0c51b07bcbfa0469cb39..2387917f998ee30baf6d8c15aa8dcb2b906befae 100644 (file)
@@ -22,7 +22,9 @@ mgmtd_libmgmtd_a_SOURCES = \
        mgmtd/mgmt_be_adapter.c \
        mgmtd/mgmt_fe_server.c \
        mgmtd/mgmt_fe_adapter.c \
+       mgmtd/mgmt_history.c \
        mgmtd/mgmt_memory.c \
+       mgmtd/mgmt_txn.c \
        mgmtd/mgmt_vty.c \
        # end
 
@@ -38,7 +40,9 @@ noinst_HEADERS += \
        mgmtd/mgmt_ds.h \
        mgmtd/mgmt_fe_server.h \
        mgmtd/mgmt_fe_adapter.h \
+       mgmtd/mgmt_history.h \
        mgmtd/mgmt_memory.h \
+       mgmtd/mgmt_txn.h \
        # end
 
 sbin_PROGRAMS += mgmtd/mgmtd