diff --git a/.clang-format b/.clang-format index 01e909d930..1b18323348 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 7d8b73ddd6..4b07536337 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 b9b840a60d..b91308fb1a 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 b63b216b0d..96f257c8d9 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 2ead14cc39..523b383c62 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 78738f7d4d..70a3251ab3 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 34db900afa..e1acfde390 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 ad9f03c422..2a9d947517 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 f5385d218e..b47ed3afe1 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 2af8f3f553..ee2f376f48 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 2f4e7f8492..1724afb182 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 7a6737cd2f..89a2ea9425 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 ***************************************************************/ @@ -202,25 +198,6 @@ extern int mgmt_ds_write_lock(struct mgmt_ds_ctx *ds_ctx); */ extern int mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx); -/* - * Merge two datastores. - * - * src_ds - * Source datastore handle. - * - * dst_ds - * Destination datastore handle. - * - * update_cmd_rec - * TRUE if need to update commit record, FALSE otherwise. - * - * Returns: - * 0 on success, -1 on failure. - */ -extern int mgmt_ds_merge_dss(struct mgmt_ds_ctx *src_ds_ctx, - struct mgmt_ds_ctx *dst_ds_ctx, - bool update_cmt_rec); - /* * Copy from source to destination datastore. * @@ -388,4 +365,10 @@ extern void mgmt_ds_status_write_one(struct vty *vty, */ extern void mgmt_ds_status_write(struct vty *vty); + +/* + * Reset the candidate DS to empty state + */ +void mgmt_ds_reset_candidate(void); + #endif /* _FRR_MGMTD_DS_H_ */ diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index fa1e0826d1..1ea812c1a7 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 0000000000..4fecfa9835 --- /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 + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include +#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 0000000000..23ce4062ef --- /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 + * 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 39e036c304..2858bc7e45 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 d8b3ac7e0e..5cfcafc749 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 0000000000..115aa532c4 --- /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 + */ + +#include +#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 0000000000..f026a39937 --- /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 + */ + +#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 8ee5921dbf..0ac4ebc9e6 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 ", + 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 64228f968b..2387917f99 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