summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYash Ranjan <ranjany@vmware.com>2021-10-28 09:07:11 +0200
committerChristian Hopps <chopps@labn.net>2023-03-22 03:08:32 +0100
commit74335ceb2753cc73afe2854b20640018431acc19 (patch)
tree0ff2afc2aacd7078131086fd6051321725b5cd94
parentmgmtd: Add MGMT Backend Interface Framework (diff)
downloadfrr-74335ceb2753cc73afe2854b20640018431acc19.tar.xz
frr-74335ceb2753cc73afe2854b20640018431acc19.zip
mgmtd: Add MGMT Transaction Framework
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>
-rw-r--r--.clang-format4
-rw-r--r--configure.ac7
-rw-r--r--lib/northbound.c12
-rw-r--r--lib/northbound.h132
-rw-r--r--lib/northbound_cli.c12
-rw-r--r--lib/yang.c7
-rw-r--r--mgmtd/mgmt.c11
-rw-r--r--mgmtd/mgmt.h34
-rw-r--r--mgmtd/mgmt_be_adapter.c62
-rw-r--r--mgmtd/mgmt_defines.h3
-rw-r--r--mgmtd/mgmt_ds.c61
-rw-r--r--mgmtd/mgmt_ds.h33
-rw-r--r--mgmtd/mgmt_fe_adapter.c289
-rw-r--r--mgmtd/mgmt_history.c356
-rw-r--r--mgmtd/mgmt_history.h55
-rw-r--r--mgmtd/mgmt_memory.c12
-rw-r--r--mgmtd/mgmt_memory.h9
-rw-r--r--mgmtd/mgmt_txn.c2875
-rw-r--r--mgmtd/mgmt_txn.h267
-rw-r--r--mgmtd/mgmt_vty.c92
-rw-r--r--mgmtd/subdir.am4
21 files changed, 4075 insertions, 262 deletions
diff --git a/.clang-format b/.clang-format
index 01e909d93..1b1832334 100644
--- a/.clang-format
+++ b/.clang-format
@@ -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
diff --git a/configure.ac b/configure.ac
index 7d8b73ddd..4b0753633 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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"], [
diff --git a/lib/northbound.c b/lib/northbound.c
index b9b840a60..b91308fb1 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -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",
diff --git a/lib/northbound.h b/lib/northbound.h
index b63b216b0..96f257c8d 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -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.
*
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
index 2ead14cc3..523b383c6 100644
--- a/lib/northbound_cli.c
+++ b/lib/northbound_cli.c
@@ -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);
}
diff --git a/lib/yang.c b/lib/yang.c
index 78738f7d4..70a3251ab 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -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)
diff --git a/mgmtd/mgmt.c b/mgmtd/mgmt.c
index 34db900af..e1acfde39 100644
--- a/mgmtd/mgmt.c
+++ b/mgmtd/mgmt.c
@@ -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();
}
diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h
index ad9f03c42..2a9d94751 100644
--- a/mgmtd/mgmt.h
+++ b/mgmtd/mgmt.h
@@ -10,21 +10,26 @@
#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;
}
diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c
index f5385d218..b47ed3afe 100644
--- a/mgmtd/mgmt_be_adapter.c
+++ b/mgmtd/mgmt_be_adapter.c
@@ -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
diff --git a/mgmtd/mgmt_defines.h b/mgmtd/mgmt_defines.h
index 2af8f3f55..ee2f376f4 100644
--- a/mgmtd/mgmt_defines.h
+++ b/mgmtd/mgmt_defines.h
@@ -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 */
diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c
index 2f4e7f849..1724afb18 100644
--- a/mgmtd/mgmt_ds.c
+++ b/mgmtd/mgmt_ds.c
@@ -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;
diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h
index 7a6737cd2..89a2ea942 100644
--- a/mgmtd/mgmt_ds.h
+++ b/mgmtd/mgmt_ds.h
@@ -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
@@ -35,16 +37,10 @@
#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
***************************************************************/
@@ -203,25 +199,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.
*
* src_ds
@@ -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_ */
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index fa1e0826d..1ea812c1a 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -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
index 000000000..4fecfa983
--- /dev/null
+++ b/mgmtd/mgmt_history.c
@@ -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
index 000000000..23ce4062e
--- /dev/null
+++ b/mgmtd/mgmt_history.h
@@ -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_ */
diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c
index 39e036c30..2858bc7e4 100644
--- a/mgmtd/mgmt_memory.c
+++ b/mgmtd/mgmt_memory.c
@@ -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");
diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h
index d8b3ac7e0..5cfcafc74 100644
--- a/mgmtd/mgmt_memory.h
+++ b/mgmtd/mgmt_memory.h
@@ -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
index 000000000..115aa532c
--- /dev/null
+++ b/mgmtd/mgmt_txn.c
@@ -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
index 000000000..f026a3993
--- /dev/null
+++ b/mgmtd/mgmt_txn.h
@@ -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_ */
diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c
index 8ee5921db..0ac4ebc9e 100644
--- a/mgmtd/mgmt_vty.c
+++ b/mgmtd/mgmt_vty.c
@@ -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.
*/
}
diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am
index 64228f968..2387917f9 100644
--- a/mgmtd/subdir.am
+++ b/mgmtd/subdir.am
@@ -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