From: Yash Ranjan Date: Thu, 28 Oct 2021 07:07:11 +0000 (-0700) Subject: mgmtd: Add MGMT Transaction Framework X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=c89dea3143fc79c2c3cc6192a62c98e6b31bf940;p=mirror%2Ffrr.git mgmtd: Add MGMT Transaction Framework This commit introduces the MGMT Transaction framework that takes management requests from one (or more) frontend client sessions, translates them into transactions and drives them to completion in co-oridination with one (or more) backend client daemons involved in the request. This commit includes the following functionalities in the changeset: 1. Introduces the actual Transaction module. Commands added related to transaction are: a. show mgmt transaction all 2. Adds support for commit rollback feature which stores upto the 10 commit buffers. Each commit has a commit-id which can be used to rollback to the exact configuration state. Commands supported for this feature are: a. show mgmt commit-history b. mgmt rollback commit-id COMMIT_ID 3. Add hidden commands to enable record various performance metrics: a. mgmt performance-measurement b. mgmt reset-statistic Co-authored-by: Pushpasis Sarkar Co-authored-by: Abhinay Ramesh Co-authored-by: Ujwal P Signed-off-by: Yash Ranjan --- diff --git a/.clang-format b/.clang-format index 097be71088..a024531b11 100644 --- a/.clang-format +++ b/.clang-format @@ -82,6 +82,10 @@ ForEachMacros: # ospfd - LSDB_LOOP # mgmtd + - FOREACH_CMT_REC + - FOREACH_TRXN_CFG_BATCH_IN_LIST + - FOREACH_TRXN_REQ_IN_LIST + - FOREACH_TRXN_IN_LIST - FOREACH_MGMTD_DB_ID - FOREACH_ADPTR_IN_LIST - FOREACH_SESSN_IN_LIST diff --git a/configure.ac b/configure.ac index 1f5f9fff31..129b2ecb67 100644 --- a/configure.ac +++ b/configure.ac @@ -1713,6 +1713,11 @@ AS_IF([test "$enable_mgmtd" != "no"], [ ]) AC_DEFINE([HAVE_MGMTD], [1], [mgmtd]) + + # Enable MGMTD local validations + AS_IF([test "$enable_mgmtd_local_validations" != "no"], [ + 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 8927e4fc9b..e024934095 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -933,11 +933,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; @@ -993,7 +994,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; @@ -1032,7 +1034,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 60613730fd..3666eb39f6 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -896,6 +896,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); @@ -910,25 +923,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, @@ -993,6 +1115,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 69ebda78b3..7dc4d9481b 100644 --- a/lib/northbound_cli.c +++ b/lib/northbound_cli.c @@ -207,6 +207,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_frntnd_enabled()) { + VTY_CHECK_XPATH; + return vty_mgmt_send_config_data(vty); + } + return nb_cli_apply_changes_internal(vty, xpath_base, false); } @@ -223,6 +229,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_frntnd_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 ef1cf898aa..adf9f7bd18 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -407,7 +407,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 ba3fb4f13a..ecdde8f4a2 100644 --- a/mgmtd/mgmt.c +++ b/mgmtd/mgmt.c @@ -61,6 +61,9 @@ void mgmt_init(void) /* Initialize databases */ mgmt_db_init(mm); + /* Initialize MGMTD Transaction module */ + mgmt_trxn_init(mm, mm->master); + /* Initialize the MGMTD Backend Adapter Module */ mgmt_bcknd_adapter_init(mm->master); @@ -73,7 +76,7 @@ void mgmt_init(void) /* Start the MGMTD Frontend Server for clients to connect */ mgmt_frntnd_server_init(mm->master); - /* MGMTD VTY commands installation. */ + /* MGMTD VTY commands installation. */ mgmt_vty_init(); } @@ -83,5 +86,6 @@ void mgmt_terminate(void) mgmt_frntnd_adapter_destroy(); mgmt_bcknd_server_destroy(); mgmt_bcknd_adapter_destroy(); + mgmt_trxn_destroy(); mgmt_db_destroy(); } diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h index c694e6bb91..22db565866 100644 --- a/mgmtd/mgmt.h +++ b/mgmtd/mgmt.h @@ -21,21 +21,25 @@ #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_trxn.h" #include "mgmtd/mgmt_db.h" #define MGMTD_VTY_PORT 2622 #define MGMTD_SOCKET_BUF_SIZE 65535 +#define MGMTD_MAX_COMMIT_LIST 10 extern bool mgmt_debug_bcknd; extern bool mgmt_debug_frntnd; extern bool mgmt_debug_db; extern bool mgmt_debug_trxn; +struct mgmt_trxn_ctxt; + /* * MGMTD master for system wide configurations and variables. */ @@ -45,6 +49,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_trxn_list_head trxn_list; + + /* Map of Transactions and its ID */ + struct hash *trxn_hash; + uint64_t next_trxn_id; + + /* The single instance of config transaction allowed at any time */ + struct mgmt_trxn_ctxt *cfg_trxn; + /* Databases */ struct mgmt_db_ctxt *running_db; struct mgmt_db_ctxt *candidate_db; @@ -52,6 +66,10 @@ 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_info_dlist_head + cmt_dlist; /* List of last 10 commits executed. */ }; extern struct mgmt_master *mm; diff --git a/mgmtd/mgmt_bcknd_adapter.c b/mgmtd/mgmt_bcknd_adapter.c index 03bed86c13..3176b078cb 100644 --- a/mgmtd/mgmt_bcknd_adapter.c +++ b/mgmtd/mgmt_bcknd_adapter.c @@ -341,9 +341,9 @@ mgmt_bcknd_adapter_disconnect(struct mgmt_bcknd_client_adapter *adptr) } /* - * TODO: Notify about client disconnect for appropriate cleanup - * mgmt_trxn_notify_bcknd_adapter_conn(adptr, false); + * Notify about client disconnect for appropriate cleanup */ + mgmt_trxn_notify_bcknd_adapter_conn(adptr, false); if (adptr->id < MGMTD_BCKND_CLIENT_ID_MAX) { mgmt_bcknd_adptrs_by_id[adptr->id] = NULL; @@ -411,12 +411,12 @@ mgmt_bcknd_adapter_handle_msg(struct mgmt_bcknd_client_adapter *adptr, adptr->name, bcknd_msg->trxn_reply->success ? "success" : "failure"); /* - * TODO: Forward the TRXN_REPLY to trxn module. - * mgmt_trxn_notify_bcknd_trxn_reply( - * bcknd_msg->trxn_reply->trxn_id, - * bcknd_msg->trxn_reply->create, - * bcknd_msg->trxn_reply->success, adptr); + * Forward the TRXN_REPLY to trxn module. */ + mgmt_trxn_notify_bcknd_trxn_reply( + bcknd_msg->trxn_reply->trxn_id, + bcknd_msg->trxn_reply->create, + bcknd_msg->trxn_reply->success, adptr); break; case MGMTD__BCKND_MESSAGE__MESSAGE_CFG_DATA_REPLY: MGMTD_BCKND_ADPTR_DBG( @@ -428,13 +428,13 @@ mgmt_bcknd_adapter_handle_msg(struct mgmt_bcknd_client_adapter *adptr, ? bcknd_msg->cfg_data_reply->error_if_any : "None"); /* - * TODO: Forward the CGFData-create reply to trxn module. - * mgmt_trxn_notify_bcknd_cfgdata_reply( - * bcknd_msg->cfg_data_reply->trxn_id, - * bcknd_msg->cfg_data_reply->batch_id, - * bcknd_msg->cfg_data_reply->success, - * bcknd_msg->cfg_data_reply->error_if_any, adptr); + * Forward the CGFData-create reply to trxn module. */ + mgmt_trxn_notify_bcknd_cfgdata_reply( + bcknd_msg->cfg_data_reply->trxn_id, + bcknd_msg->cfg_data_reply->batch_id, + bcknd_msg->cfg_data_reply->success, + bcknd_msg->cfg_data_reply->error_if_any, adptr); break; case MGMTD__BCKND_MESSAGE__MESSAGE_CFG_VALIDATE_REPLY: MGMTD_BCKND_ADPTR_DBG( @@ -455,14 +455,14 @@ mgmt_bcknd_adapter_handle_msg(struct mgmt_bcknd_client_adapter *adptr, ? bcknd_msg->cfg_validate_reply->error_if_any : "None"); /* - * TODO: Forward the CGFData-validate reply to trxn module. - * mgmt_trxn_notify_bcknd_cfg_validate_reply( - * bcknd_msg->cfg_validate_reply->trxn_id, - * bcknd_msg->cfg_validate_reply->success, - * (uint64_t *)bcknd_msg->cfg_validate_reply->batch_ids, - * bcknd_msg->cfg_validate_reply->n_batch_ids, - * bcknd_msg->cfg_validate_reply->error_if_any, adptr); + * Forward the CGFData-validate reply to trxn module. */ + mgmt_trxn_notify_bcknd_cfg_validate_reply( + bcknd_msg->cfg_validate_reply->trxn_id, + bcknd_msg->cfg_validate_reply->success, + (uint64_t *)bcknd_msg->cfg_validate_reply->batch_ids, + bcknd_msg->cfg_validate_reply->n_batch_ids, + bcknd_msg->cfg_validate_reply->error_if_any, adptr); break; case MGMTD__BCKND_MESSAGE__MESSAGE_CFG_APPLY_REPLY: MGMTD_BCKND_ADPTR_DBG( @@ -482,14 +482,15 @@ mgmt_bcknd_adapter_handle_msg(struct mgmt_bcknd_client_adapter *adptr, bcknd_msg->cfg_apply_reply->error_if_any ? bcknd_msg->cfg_apply_reply->error_if_any : "None"); - /* TODO: Forward the CGFData-apply reply to trxn module. - * mgmt_trxn_notify_bcknd_cfg_apply_reply( - * bcknd_msg->cfg_apply_reply->trxn_id, - * bcknd_msg->cfg_apply_reply->success, - * (uint64_t *)bcknd_msg->cfg_apply_reply->batch_ids, - * bcknd_msg->cfg_apply_reply->n_batch_ids, - * bcknd_msg->cfg_apply_reply->error_if_any, adptr); + /* + * Forward the CGFData-apply reply to trxn module. */ + mgmt_trxn_notify_bcknd_cfg_apply_reply( + bcknd_msg->cfg_apply_reply->trxn_id, + bcknd_msg->cfg_apply_reply->success, + (uint64_t *)bcknd_msg->cfg_apply_reply->batch_ids, + bcknd_msg->cfg_apply_reply->n_batch_ids, + bcknd_msg->cfg_apply_reply->error_if_any, adptr); break; case MGMTD__BCKND_MESSAGE__MESSAGE_GET_REPLY: case MGMTD__BCKND_MESSAGE__MESSAGE_CFG_CMD_REPLY: @@ -960,29 +961,26 @@ static void mgmt_bcknd_adapter_conn_init(struct thread *thread) assert(adptr && adptr->conn_fd); /* - * 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_trxn_in_progress() != MGMTD_SESSION_ID_NONE) { mgmt_bcknd_adptr_register_event(adptr, MGMTD_BCKND_CONN_INIT); - return 0; + return; } - */ - /* - * TODO: Notify TRXN module to create a CONFIG transaction and - * download the CONFIGs identified for this new client. - * If the TRXN 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 TRXN module to create a CONFIG transaction and + * download the CONFIGs identified for this new client. + * If the TRXN 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_trxn_notify_bcknd_adapter_conn(adptr, true) != 0) { mgmt_bcknd_adapter_disconnect(adptr); adptr = NULL; } - */ - - return 0; } static void diff --git a/mgmtd/mgmt_db.c b/mgmtd/mgmt_db.c index 39c916d102..e1651a498a 100644 --- a/mgmtd/mgmt_db.c +++ b/mgmtd/mgmt_db.c @@ -23,6 +23,7 @@ #include "mgmtd/mgmt.h" #include "mgmtd/mgmt_memory.h" #include "mgmtd/mgmt_db.h" +#include "mgmtd/mgmt_trxn.h" #include "libyang/libyang.h" #ifdef REDIRECT_DEBUG_TO_STDERR @@ -34,7 +35,7 @@ #define MGMTD_DB_DBG(fmt, ...) \ do { \ if (mgmt_debug_db) \ - zlog_debug("%s: " fmt, __func__, ##__VA_ARGS__); \ + zlog_err("%s: " fmt, __func__, ##__VA_ARGS__); \ } while (0) #define MGMTD_DB_ERR(fmt, ...) \ zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) @@ -52,6 +53,19 @@ struct mgmt_db_ctxt { } root; }; +struct mgmt_cmt_info_t { + struct mgmt_cmt_info_dlist_item cmt_dlist; + + char cmtid_str[MGMTD_MD5_HASH_STR_HEX_LEN]; + char time_str[MGMTD_COMMIT_TIME_STR_LEN]; + char cmt_json_file[MGMTD_MAX_COMMIT_FILE_PATH_LEN]; +}; + +DECLARE_DLIST(mgmt_cmt_info_dlist, struct mgmt_cmt_info_t, cmt_dlist); + +#define FOREACH_CMT_REC(mm, cmt_info) \ + frr_each_safe(mgmt_cmt_info_dlist, &mm->cmt_dlist, cmt_info) + const char *mgmt_db_names[MGMTD_DB_MAX_ID + 1] = { MGMTD_DB_NAME_NONE, /* MGMTD_DB_NONE */ MGMTD_DB_NAME_RUNNING, /* MGMTD_DB_RUNNING */ @@ -119,6 +133,14 @@ static int mgmt_db_replace_dst_with_src_db(struct mgmt_db_ctxt *src, else dst->root.dnode_root = dst_dnode; + if (src->db_id == MGMTD_DB_CANDIDATE) { + /* + * Drop the changes in scratch-buffer. + */ + MGMTD_DB_DBG("Emptying Candidate Scratch buffer!"); + nb_config_diff_del_changes(&src->root.cfg_root->cfg_chgs); + } + if (dst->db_id == MGMTD_DB_RUNNING) { if (ly_out_new_filepath(MGMTD_STARTUP_DB_FILE_PATH, &out) == LY_SUCCESS) @@ -153,6 +175,14 @@ static int mgmt_db_merge_src_with_dst_db(struct mgmt_db_ctxt *src, return ret; } + if (src->db_id == MGMTD_DB_CANDIDATE) { + /* + * Drop the changes in scratch-buffer. + */ + MGMTD_DB_DBG("Emptying Candidate Scratch buffer!"); + nb_config_diff_del_changes(&src->root.cfg_root->cfg_chgs); + } + if (dst->db_id == MGMTD_DB_RUNNING) { if (ly_out_new_filepath(MGMTD_STARTUP_DB_FILE_PATH, &out) == LY_SUCCESS) @@ -181,6 +211,244 @@ static int mgmt_db_load_cfg_from_file(const char *filepath, return 0; } +static bool mgmt_db_cmt_rec_exists(char *file_path) +{ + int exist; + + exist = access(file_path, F_OK); + if (exist == 0) + return true; + else + return false; +} + +static void mgmt_db_remove_cmt_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_db_compute_cmt_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_db_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_db_compute_cmt_hash(new->time_str, new->cmtid_str); + snprintf(new->cmt_json_file, MGMTD_MAX_COMMIT_FILE_PATH_LEN, + MGMTD_COMMIT_FILE_PATH, new->cmtid_str); + + if (mgmt_cmt_info_dlist_count(&mm->cmt_dlist) + == MGMTD_MAX_COMMIT_LIST) { + FOREACH_CMT_REC (mm, cmt_info) + last_cmt_info = cmt_info; + + if (last_cmt_info) { + mgmt_db_remove_cmt_file(last_cmt_info->cmt_json_file); + mgmt_cmt_info_dlist_del(&mm->cmt_dlist, last_cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info); + } + } + + mgmt_cmt_info_dlist_add_head(&mm->cmt_dlist, new); + return new; +} + +static struct mgmt_cmt_info_t *mgmt_db_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_db_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_db_cmt_rec_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_info_dlist_add_tail(&mm->cmt_dlist, 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_db_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_db_remove_cmt_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_db_reset(struct mgmt_db_ctxt *db_ctxt) +{ + struct lyd_node *dnode; + + if (!db_ctxt) + return -1; + + dnode = db_ctxt->config_db ? db_ctxt->root.cfg_root->dnode + : db_ctxt->root.dnode_root; + + if (dnode) + yang_dnode_free(dnode); + + dnode = yang_dnode_new(ly_native_ctx, true); + + if (db_ctxt->config_db) + db_ctxt->root.cfg_root->dnode = dnode; + else + db_ctxt->root.dnode_root = dnode; + + return 0; +} + +static int mgmt_db_rollback_to_cmt(struct vty *vty, + struct mgmt_cmt_info_t *cmt_info, + bool skip_file_load) +{ + struct mgmt_db_ctxt *src_db_ctxt; + struct mgmt_db_ctxt *dst_db_ctxt; + int ret = 0; + + src_db_ctxt = mgmt_db_get_hndl_by_id(mm, MGMTD_DB_CANDIDATE); + if (!src_db_ctxt) { + vty_out(vty, "ERROR: Couldnot access Candidate database!\n"); + return -1; + } + + /* + * Note: Write lock on src_db is not required. This is already + * taken in 'conf te'. + */ + dst_db_ctxt = mgmt_db_get_hndl_by_id(mm, MGMTD_DB_RUNNING); + if (!dst_db_ctxt) { + vty_out(vty, "ERROR: Couldnot access Running database!\n"); + return -1; + } + + ret = mgmt_db_write_lock(dst_db_ctxt); + if (ret != 0) { + vty_out(vty, + "Failed to lock the DB %u for rollback Reason: %s!\n", + MGMTD_DB_RUNNING, strerror(ret)); + return -1; + } + + if (!skip_file_load) { + ret = mgmt_db_load_config_from_file( + src_db_ctxt, cmt_info->cmt_json_file, false); + if (ret != 0) { + mgmt_db_unlock(dst_db_ctxt); + vty_out(vty, + "Error with parsing the file with error code %d\n", + ret); + return ret; + } + } + + /* Internally trigger a commit-request. */ + ret = mgmt_trxn_rollback_trigger_cfg_apply(src_db_ctxt, dst_db_ctxt); + if (ret != 0) { + mgmt_db_unlock(dst_db_ctxt); + vty_out(vty, + "Error with creating commit apply trxn with error code %d\n", + ret); + return ret; + } + + mgmt_db_dump_cmt_record_index(); + return 0; +} + int mgmt_db_init(struct mgmt_master *mm) { struct lyd_node *root; @@ -206,6 +474,12 @@ int mgmt_db_init(struct mgmt_master *mm) candidate.config_db = true; candidate.db_id = MGMTD_DB_CANDIDATE; + /* + * Redirect lib/vty candidate-config database to the global candidate + * config Db 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_db = false; oper.db_id = MGMTD_DB_OPERATIONAL; @@ -215,15 +489,27 @@ int mgmt_db_init(struct mgmt_master *mm) mm->oper_db = &oper; mgmt_db_mm = mm; + /* Create commit record for previously stored commit-apply */ + mgmt_cmt_info_dlist_init(&mgmt_db_mm->cmt_dlist); + mgmt_db_read_cmt_record_index(); + return 0; } void mgmt_db_destroy(void) { + struct mgmt_cmt_info_t *cmt_info; /* * TODO: Free the databases. */ + + FOREACH_CMT_REC (mgmt_db_mm, cmt_info) { + mgmt_cmt_info_dlist_del(&mgmt_db_mm->cmt_dlist, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + mgmt_cmt_info_dlist_fini(&mgmt_db_mm->cmt_dlist); } struct mgmt_db_ctxt *mgmt_db_get_hndl_by_id(struct mgmt_master *mm, @@ -289,18 +575,34 @@ int mgmt_db_unlock(struct mgmt_db_ctxt *db_ctxt) int mgmt_db_merge_dbs(struct mgmt_db_ctxt *src_db_ctxt, struct mgmt_db_ctxt *dst_db_ctxt, bool updt_cmt_rec) { + struct mgmt_cmt_info_t *cmt_info; + if (mgmt_db_merge_src_with_dst_db(src_db_ctxt, dst_db_ctxt) != 0) return -1; + if (updt_cmt_rec && dst_db_ctxt->db_id == MGMTD_DB_RUNNING) { + cmt_info = mgmt_db_create_cmt_rec(); + mgmt_db_dump_db_to_file(cmt_info->cmt_json_file, dst_db_ctxt); + mgmt_db_dump_cmt_record_index(); + } + return 0; } int mgmt_db_copy_dbs(struct mgmt_db_ctxt *src_db_ctxt, struct mgmt_db_ctxt *dst_db_ctxt, bool updt_cmt_rec) { + struct mgmt_cmt_info_t *cmt_info; + if (mgmt_db_replace_dst_with_src_db(src_db_ctxt, dst_db_ctxt) != 0) return -1; + if (updt_cmt_rec && dst_db_ctxt->db_id == MGMTD_DB_RUNNING) { + cmt_info = mgmt_db_create_cmt_rec(); + mgmt_db_dump_db_to_file(cmt_info->cmt_json_file, dst_db_ctxt); + mgmt_db_dump_cmt_record_index(); + } + return 0; } @@ -386,14 +688,16 @@ static int mgmt_walk_db_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) { @@ -416,9 +720,6 @@ static int mgmt_walk_db_nodes( assert(xpath); MGMTD_DB_DBG(" -- XPATH: %s", xpath); - if (!childs_as_well) - continue; - if (num_nodes) num_found = num_left; @@ -649,3 +950,91 @@ void mgmt_db_status_write(struct vty *vty) mgmt_db_status_write_one(vty, mgmt_db_mm->oper_db); } + +int mgmt_db_rollback_by_cmtid(struct vty *vty, const char *cmtid_str) +{ + int ret = 0; + struct mgmt_cmt_info_t *cmt_info; + + if (!mgmt_cmt_info_dlist_count(&mm->cmt_dlist) + || !mgmt_db_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_db_rollback_to_cmt(vty, cmt_info, false); + return ret; + } + + mgmt_db_remove_cmt_file(cmt_info->cmt_json_file); + mgmt_cmt_info_dlist_del(&mm->cmt_dlist, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + return 0; +} + +int mgmt_db_rollback_commits(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_info_dlist_count(&mm->cmt_dlist); + 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_db_rollback_to_cmt(vty, cmt_info, false); + return ret; + } + + cnt++; + mgmt_db_remove_cmt_file(cmt_info->cmt_json_file); + mgmt_cmt_info_dlist_del(&mm->cmt_dlist, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + if (!mgmt_cmt_info_dlist_count(&mm->cmt_dlist)) { + ret = mgmt_db_reset((struct mgmt_db_ctxt *)mm->candidate_db); + if (ret < 0) + return ret; + ret = mgmt_db_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++; + } +} diff --git a/mgmtd/mgmt_db.h b/mgmtd/mgmt_db.h index 9aec3aaca1..942be5f3ba 100644 --- a/mgmtd/mgmt_db.h +++ b/mgmtd/mgmt_db.h @@ -22,6 +22,8 @@ #define _FRR_MGMTD_DB_H_ #include "mgmtd/mgmt_defines.h" +#include "mgmtd/mgmt_bcknd_adapter.h" +#include "mgmtd/mgmt_frntnd_adapter.h" #define MGMTD_MAX_NUM_DBNODES_PER_BATCH 128 @@ -45,15 +47,11 @@ #define MGMTD_COMMIT_INDEX_FILE_NAME "/etc/frr/commit-index.dat" #define MGMTD_COMMIT_TIME_STR_LEN 100 -struct mgmt_master; - extern struct nb_config *running_config; struct mgmt_db_ctxt; -typedef void (*mgmt_db_node_iter_fn)(uint64_t db_hndl, char *xpath, - struct lyd_node *node, - struct nb_node *nb_node, void *ctxt); +PREDECL_DLIST(mgmt_cmt_info_dlist); /*************************************************************** * Global data exported @@ -387,6 +385,34 @@ extern void mgmt_db_dump_tree(struct vty *vty, struct mgmt_db_ctxt *db_ctxt, extern int mgmt_db_dump_db_to_file(char *file_name, struct mgmt_db_ctxt *db_ctxt); +/* + * 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_db_rollback_by_cmtid(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_db_rollback_commits(struct vty *vty, int num_cmts); + /* * Dump information about specific database. */ @@ -398,4 +424,9 @@ extern void mgmt_db_status_write_one(struct vty *vty, */ extern void mgmt_db_status_write(struct vty *vty); +/* + * Show mgmt commit history. + */ +extern void show_mgmt_cmt_history(struct vty *vty); + #endif /* _FRR_MGMTD_DB_H_ */ diff --git a/mgmtd/mgmt_defines.h b/mgmtd/mgmt_defines.h index c164bd55b6..f1793d8045 100644 --- a/mgmtd/mgmt_defines.h +++ b/mgmtd/mgmt_defines.h @@ -29,6 +29,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 #define MGMTD_MAX_CFG_CHANGES_IN_BATCH \ ((10 * MGMTD_BCKND_MSG_MAX_LEN) \ @@ -67,4 +68,6 @@ enum mgmt_bcknd_event { #define MGMTD_TRXN_ID_NONE 0 +#define MGMTD_TRXN_BATCH_ID_NONE 0 + #endif /* _FRR_MGMTD_DEFINES_H */ diff --git a/mgmtd/mgmt_frntnd_adapter.c b/mgmtd/mgmt_frntnd_adapter.c index 3c49b828d4..2b64cc71de 100644 --- a/mgmtd/mgmt_frntnd_adapter.c +++ b/mgmtd/mgmt_frntnd_adapter.c @@ -193,10 +193,11 @@ mgmt_frntnd_sessn_cfg_trxn_cleanup(struct mgmt_frntnd_sessn_ctxt *sessn) } } - /* TODO: Destroy the actual transaction created earlier. - * if (sessn->cfg_trxn_id != MGMTD_TRXN_ID_NONE) - * mgmt_destroy_trxn(&sessn->cfg_trxn_id); + /* + * Destroy the actual transaction created earlier. */ + if (sessn->cfg_trxn_id != MGMTD_TRXN_ID_NONE) + mgmt_destroy_trxn(&sessn->cfg_trxn_id); } static void @@ -213,10 +214,11 @@ mgmt_frntnd_sessn_show_trxn_cleanup(struct mgmt_frntnd_sessn_ctxt *sessn) } } - /* TODO: Destroy the transaction created recently. - * if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) - * mgmt_destroy_trxn(&sessn->trxn_id); + /* + * Destroy the transaction created recently. */ + if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) + mgmt_destroy_trxn(&sessn->trxn_id); } static void @@ -700,9 +702,6 @@ mgmt_frntnd_session_register_event(struct mgmt_frntnd_sessn_ctxt *sessn, &tv, &sessn->proc_show_trxn_clnp); assert(sessn->proc_show_trxn_clnp); break; - default: - assert(!"mgmt_frntnd_adptr_post_event() called incorrectly"); - break; } } @@ -848,7 +847,7 @@ static int mgmt_frntnd_session_handle_setcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, Mgmtd__FrntndSetConfigReq *setcfg_req) { - /* uint64_t cfg_sessn_id; */ + uint64_t cfg_sessn_id; struct mgmt_db_ctxt *db_ctxt, *dst_db_ctxt; if (mm->perf_stats_en) @@ -881,20 +880,20 @@ mgmt_frntnd_session_handle_setcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, if (sessn->cfg_trxn_id == MGMTD_TRXN_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_sessn_id = mgmt_config_trxn_in_progress(); - * if (cfg_sessn_id != MGMTD_SESSION_ID_NONE - * && cfg_sessn_id != sessn->session_id) { - * mgmt_frntnd_send_setcfg_reply( - * sessn, setcfg_req->db_id, setcfg_req->req_id, - * false, - * "Configuration already in-progress through a - *different user session!", setcfg_req->implicit_commit); goto - *mgmt_frntnd_sess_handle_setcfg_req_failed; - *} */ + cfg_sessn_id = mgmt_config_trxn_in_progress(); + if (cfg_sessn_id != MGMTD_SESSION_ID_NONE + && cfg_sessn_id != sessn->session_id) { + mgmt_frntnd_send_setcfg_reply( + sessn, setcfg_req->db_id, setcfg_req->req_id, + false, + "Configuration already in-progress through a different user session!", + setcfg_req->implicit_commit); + goto mgmt_frntnd_sess_handle_setcfg_req_failed; + } /* @@ -916,18 +915,18 @@ mgmt_frntnd_session_handle_setcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, } /* - * TODO: Start a CONFIG Transaction (if not started already) - * sessn->cfg_trxn_id = mgmt_create_trxn(sessn->session_id, - * MGMTD_TRXN_TYPE_CONFIG); - * if (sessn->cfg_trxn_id == MGMTD_SESSION_ID_NONE) { - * mgmt_frntnd_send_setcfg_reply( - * sessn, setcfg_req->db_id, setcfg_req->req_id, - * false, - * "Failed to create a Configuration session!", - * setcfg_req->implicit_commit); - * goto mgmt_frntnd_sess_handle_setcfg_req_failed; - * } + * Start a CONFIG Transaction (if not started already) */ + sessn->cfg_trxn_id = mgmt_create_trxn(sessn->session_id, + MGMTD_TRXN_TYPE_CONFIG); + if (sessn->cfg_trxn_id == MGMTD_SESSION_ID_NONE) { + mgmt_frntnd_send_setcfg_reply( + sessn, setcfg_req->db_id, setcfg_req->req_id, + false, + "Failed to create a Configuration session!", + setcfg_req->implicit_commit); + goto mgmt_frntnd_sess_handle_setcfg_req_failed; + } MGMTD_FRNTND_ADPTR_DBG( "Created new Config Trxn 0x%llx for session %p", @@ -964,36 +963,31 @@ mgmt_frntnd_session_handle_setcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, } } - /* TODO: Create the SETConfig request under the transaction. - * if (mgmt_trxn_send_set_config_req( - * sessn->cfg_trxn_id, setcfg_req->req_id, setcfg_req->db_id, - * db_ctxt, setcfg_req->data, setcfg_req->n_data, - * setcfg_req->implicit_commit, setcfg_req->commit_db_id, - * dst_db_ctxt) - * != 0) { - * mgmt_frntnd_send_setcfg_reply( - * sessn, setcfg_req->db_id, setcfg_req->req_id, false, - * "Request processing for SET-CONFIG failed!", - * setcfg_req->implicit_commit); - * goto mgmt_frntnd_sess_handle_setcfg_req_failed; - * } - * - * For now send a failure reply. + /* + * Create the SETConfig request under the transaction. */ - mgmt_frntnd_send_setcfg_reply( - sessn, setcfg_req->db_id, setcfg_req->req_id, false, - "Request processing for SET-CONFIG failed!", - setcfg_req->implicit_commit); - goto mgmt_frntnd_sess_handle_setcfg_req_failed; + if (mgmt_trxn_send_set_config_req( + sessn->cfg_trxn_id, setcfg_req->req_id, setcfg_req->db_id, + db_ctxt, setcfg_req->data, setcfg_req->n_data, + setcfg_req->implicit_commit, setcfg_req->commit_db_id, + dst_db_ctxt) + != 0) { + mgmt_frntnd_send_setcfg_reply( + sessn, setcfg_req->db_id, setcfg_req->req_id, false, + "Request processing for SET-CONFIG failed!", + setcfg_req->implicit_commit); + goto mgmt_frntnd_sess_handle_setcfg_req_failed; + } return 0; mgmt_frntnd_sess_handle_setcfg_req_failed: - /* TODO: Delete transaction created recently. - * if (sessn->cfg_trxn_id != MGMTD_TRXN_ID_NONE) - * mgmt_destroy_trxn(&sessn->cfg_trxn_id); + /* + * Delete transaction created recently. */ + if (sessn->cfg_trxn_id != MGMTD_TRXN_ID_NONE) + mgmt_destroy_trxn(&sessn->cfg_trxn_id); if (db_ctxt && sessn->db_write_locked[setcfg_req->db_id]) mgmt_frntnd_session_unlock_db(setcfg_req->db_id, db_ctxt, sessn, true, false); @@ -1056,22 +1050,17 @@ mgmt_frntnd_session_handle_getcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, } /* - * TODO: Start a SHOW Transaction (if not started already) - * sessn->trxn_id = mgmt_create_trxn(sessn->session_id, - * MGMTD_TRXN_TYPE_SHOW); - * if (sessn->trxn_id == MGMTD_SESSION_ID_NONE) { - * mgmt_frntnd_send_getcfg_reply( - * sessn, getcfg_req->db_id, getcfg_req->req_id, - * false, NULL, - * "Failed to create a Show transaction!"); - * goto mgmt_frntnd_sess_handle_getcfg_req_failed; - * } + * Start a SHOW Transaction (if not started already) */ - mgmt_frntnd_send_getcfg_reply( - sessn, getcfg_req->db_id, getcfg_req->req_id, false, - NULL, "Failed to create a Show transaction!"); - goto mgmt_frntnd_sess_handle_getcfg_req_failed; - + sessn->trxn_id = mgmt_create_trxn(sessn->session_id, + MGMTD_TRXN_TYPE_SHOW); + if (sessn->trxn_id == MGMTD_SESSION_ID_NONE) { + mgmt_frntnd_send_getcfg_reply( + sessn, getcfg_req->db_id, getcfg_req->req_id, + false, NULL, + "Failed to create a Show transaction!"); + goto mgmt_frntnd_sess_handle_getcfg_req_failed; + } MGMTD_FRNTND_ADPTR_DBG( "Created new Show Trxn 0x%llx for session %p", @@ -1082,32 +1071,28 @@ mgmt_frntnd_session_handle_getcfg_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, (unsigned long long)sessn->trxn_id, sessn); } - /* TODO: Create a GETConfig request under the transaction. - * if (mgmt_trxn_send_get_config_req(sessn->trxn_id, getcfg_req->req_id, - * getcfg_req->db_id, db_ctxt, - * getcfg_req->data, getcfg_req->n_data) - * != 0) { - * mgmt_frntnd_send_getcfg_reply( - * sessn, getcfg_req->db_id, getcfg_req->req_id, false, - * NULL, "Request processing for GET-CONFIG failed!"); - * goto mgmt_frntnd_sess_handle_getcfg_req_failed; - * } - * - * For now send back a failure reply. + /* + * Create a GETConfig request under the transaction. */ - mgmt_frntnd_send_getcfg_reply( - sessn, getcfg_req->db_id, getcfg_req->req_id, false, NULL, - "Request processing for GET-CONFIG failed!"); - goto mgmt_frntnd_sess_handle_getcfg_req_failed; + if (mgmt_trxn_send_get_config_req(sessn->trxn_id, getcfg_req->req_id, + getcfg_req->db_id, db_ctxt, + getcfg_req->data, getcfg_req->n_data) + != 0) { + mgmt_frntnd_send_getcfg_reply( + sessn, getcfg_req->db_id, getcfg_req->req_id, false, + NULL, "Request processing for GET-CONFIG failed!"); + goto mgmt_frntnd_sess_handle_getcfg_req_failed; + } return 0; mgmt_frntnd_sess_handle_getcfg_req_failed: - /* TODO: Destroy the transaction created recently. - * if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) - * mgmt_destroy_trxn(&sessn->trxn_id); + /* + * Destroy the transaction created recently. */ + if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) + mgmt_destroy_trxn(&sessn->trxn_id); if (db_ctxt && sessn->db_read_locked[getcfg_req->db_id]) mgmt_frntnd_session_unlock_db(getcfg_req->db_id, db_ctxt, sessn, false, true); @@ -1156,23 +1141,17 @@ mgmt_frntnd_session_handle_getdata_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, } /* - * TODO: Start a SHOW Transaction (if not started already) - * sessn->trxn_id = - * mgmt_create_trxn(sessn->session_id, - * MGMTD_TRXN_TYPE_SHOW); - * if (sessn->trxn_id == MGMTD_SESSION_ID_NONE) { - * mgmt_frntnd_send_getdata_reply( - * sessn, getdata_req->db_id, getdata_req->req_id, - * false, NULL, - * "Failed to create a Show transaction!"); - * goto mgmt_frntnd_sess_handle_getdata_req_failed; - * } + * Start a SHOW Transaction (if not started already) */ - mgmt_frntnd_send_getdata_reply( - sessn, getdata_req->db_id, getdata_req->req_id, false, - NULL, "Failed to create a Show transaction!"); - goto mgmt_frntnd_sess_handle_getdata_req_failed; - + sessn->trxn_id = mgmt_create_trxn(sessn->session_id, + MGMTD_TRXN_TYPE_SHOW); + if (sessn->trxn_id == MGMTD_SESSION_ID_NONE) { + mgmt_frntnd_send_getdata_reply( + sessn, getdata_req->db_id, getdata_req->req_id, + false, NULL, + "Failed to create a Show transaction!"); + goto mgmt_frntnd_sess_handle_getdata_req_failed; + } MGMTD_FRNTND_ADPTR_DBG( "Created new Show Trxn 0x%llx for session %p", @@ -1183,32 +1162,28 @@ mgmt_frntnd_session_handle_getdata_req_msg(struct mgmt_frntnd_sessn_ctxt *sessn, (unsigned long long)sessn->trxn_id, sessn); } - /* TODO: Create a GETData request under the transaction. - * if (mgmt_trxn_send_get_data_req(sessn->trxn_id, getdata_req->req_id, - * getdata_req->db_id, db_ctxt, - * getdata_req->data, getdata_req->n_data) - * != 0) { - * mgmt_frntnd_send_getdata_reply( - * sessn, getdata_req->db_id, getdata_req->req_id, false, - * NULL, "Request processing for GET-CONFIG failed!"); - * goto mgmt_frntnd_sess_handle_getdata_req_failed; - * } - * - * For now send back a failure reply. + /* + * Create a GETData request under the transaction. */ - mgmt_frntnd_send_getdata_reply( - sessn, getdata_req->db_id, getdata_req->req_id, false, NULL, - "Request processing for GET-CONFIG failed!"); - goto mgmt_frntnd_sess_handle_getdata_req_failed; + if (mgmt_trxn_send_get_data_req(sessn->trxn_id, getdata_req->req_id, + getdata_req->db_id, db_ctxt, + getdata_req->data, getdata_req->n_data) + != 0) { + mgmt_frntnd_send_getdata_reply( + sessn, getdata_req->db_id, getdata_req->req_id, false, + NULL, "Request processing for GET-CONFIG failed!"); + goto mgmt_frntnd_sess_handle_getdata_req_failed; + } return 0; mgmt_frntnd_sess_handle_getdata_req_failed: - /* TODO: Destroy the transaction created recently. - * if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) - * mgmt_destroy_trxn(&sessn->trxn_id); + /* + * Destroy the transaction created recently. */ + if (sessn->trxn_id != MGMTD_TRXN_ID_NONE) + mgmt_destroy_trxn(&sessn->trxn_id); if (db_ctxt && sessn->db_read_locked[getdata_req->db_id]) mgmt_frntnd_session_unlock_db(getdata_req->db_id, db_ctxt, @@ -1270,25 +1245,19 @@ static int mgmt_frntnd_session_handle_commit_config_req_msg( if (sessn->cfg_trxn_id == MGMTD_TRXN_ID_NONE) { /* - * TODO: Start a CONFIG Transaction (if not started already) - * sessn->cfg_trxn_id = mgmt_create_trxn(sessn->session_id, - * MGMTD_TRXN_TYPE_CONFIG); - * if (sessn->cfg_trxn_id == MGMTD_SESSION_ID_NONE) { - * mgmt_frntnd_send_commitcfg_reply( - * sessn, commcfg_req->src_db_id, - * commcfg_req->dst_db_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_frntnd_send_commitcfg_reply( - sessn, commcfg_req->src_db_id, commcfg_req->dst_db_id, - commcfg_req->req_id, MGMTD_INTERNAL_ERROR, - commcfg_req->validate_only, - "Failed to create a Configuration session!"); - return 0; + sessn->cfg_trxn_id = mgmt_create_trxn(sessn->session_id, + MGMTD_TRXN_TYPE_CONFIG); + if (sessn->cfg_trxn_id == MGMTD_SESSION_ID_NONE) { + mgmt_frntnd_send_commitcfg_reply( + sessn, commcfg_req->src_db_id, + commcfg_req->dst_db_id, commcfg_req->req_id, + MGMTD_INTERNAL_ERROR, + commcfg_req->validate_only, + "Failed to create a Configuration session!"); + return 0; + } } @@ -1311,28 +1280,22 @@ static int mgmt_frntnd_session_handle_commit_config_req_msg( sessn->db_locked_implict[commcfg_req->dst_db_id] = true; } - /* TODO: Create COMMITConfig request under the transaction - * if (mgmt_trxn_send_commit_config_req( - * sessn->cfg_trxn_id, commcfg_req->req_id, - * commcfg_req->src_db_id, src_db_ctxt, commcfg_req->dst_db_id, - * dst_db_ctxt, commcfg_req->validate_only, commcfg_req->abort, - * false) - * != 0) { - * mgmt_frntnd_send_commitcfg_reply( - * sessn, commcfg_req->src_db_id, commcfg_req->dst_db_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 trxn modules send a unsuccessfull reply. + /* + * Create COMMITConfig request under the transaction */ - mgmt_frntnd_send_commitcfg_reply( - sessn, commcfg_req->src_db_id, commcfg_req->dst_db_id, - commcfg_req->req_id, MGMTD_INTERNAL_ERROR, - commcfg_req->validate_only, - "Request processing for COMMIT-CONFIG failed!"); + if (mgmt_trxn_send_commit_config_req( + sessn->cfg_trxn_id, commcfg_req->req_id, + commcfg_req->src_db_id, src_db_ctxt, commcfg_req->dst_db_id, + dst_db_ctxt, commcfg_req->validate_only, commcfg_req->abort, + false) + != 0) { + mgmt_frntnd_send_commitcfg_reply( + sessn, commcfg_req->src_db_id, commcfg_req->dst_db_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_memory.c b/mgmtd/mgmt_memory.c index e3fd7af0be..31b12126d1 100644 --- a/mgmtd/mgmt_memory.c +++ b/mgmtd/mgmt_memory.c @@ -33,3 +33,15 @@ DEFINE_MTYPE(MGMTD, MGMTD, "MGMTD instance"); DEFINE_MTYPE(MGMTD, MGMTD_BCKND_ADPATER, "MGMTD backend adapter"); DEFINE_MTYPE(MGMTD, MGMTD_FRNTND_ADPATER, "MGMTD Frontend adapter"); DEFINE_MTYPE(MGMTD, MGMTD_FRNTND_SESSN, "MGMTD Frontend Client Session"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN, "MGMTD Transaction"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_REQ, "MGMTD Transaction Requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_SETCFG_REQ, + "MGMTD Transaction Set-Config Requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_COMMCFG_REQ, + "MGMTD Transaction Commit-Config Requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_GETDATA_REQ, + "MGMTD Transaction Get-Data Requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_GETDATA_REPLY, + "MGMTD Transaction Get-Data Replies"); +DEFINE_MTYPE(MGMTD, MGMTD_TRXN_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 f697eca797..6a89aa72d4 100644 --- a/mgmtd/mgmt_memory.h +++ b/mgmtd/mgmt_memory.h @@ -27,4 +27,13 @@ DECLARE_MTYPE(MGMTD); DECLARE_MTYPE(MGMTD_BCKND_ADPATER); DECLARE_MTYPE(MGMTD_FRNTND_ADPATER); DECLARE_MTYPE(MGMTD_FRNTND_SESSN); +DECLARE_MTYPE(MGMTD_TRXN); +DECLARE_MTYPE(MGMTD_TRXN_REQ); +DECLARE_MTYPE(MGMTD_TRXN_SETCFG_REQ); +DECLARE_MTYPE(MGMTD_TRXN_COMMCFG_REQ); +DECLARE_MTYPE(MGMTD_TRXN_GETDATA_REQ); +DECLARE_MTYPE(MGMTD_TRXN_GETDATA_REPLY); +DECLARE_MTYPE(MGMTD_TRXN_CFG_BATCH); +DECLARE_MTYPE(MGMTD_BCKND_ADPTR_MSG_BUF); +DECLARE_MTYPE(MGMTD_CMT_INFO); #endif /* _FRR_MGMTD_MEMORY_H */ diff --git a/mgmtd/mgmt_trxn.c b/mgmtd/mgmt_trxn.c new file mode 100644 index 0000000000..267d6922ae --- /dev/null +++ b/mgmtd/mgmt_trxn.c @@ -0,0 +1,2990 @@ +/* + * MGMTD Transactions + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "hash.h" +#include "jhash.h" +#include "libfrr.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_memory.h" +#include "mgmtd/mgmt_trxn.h" + +#ifdef REDIRECT_DEBUG_TO_STDERR +#define MGMTD_TRXN_DBG(fmt, ...) \ + fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__) +#define MGMTD_TRXN_ERR(fmt, ...) \ + fprintf(stderr, "%s: ERROR, " fmt "\n", __func__, ##__VA_ARGS__) +#else /* REDIRECT_DEBUG_TO_STDERR */ +#define MGMTD_TRXN_DBG(fmt, ...) \ + do { \ + if (mgmt_debug_trxn) \ + zlog_err("%s: " fmt, __func__, ##__VA_ARGS__); \ + } while (0) +#define MGMTD_TRXN_ERR(fmt, ...) \ + zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) +#endif /* REDIRECT_DEBUG_TO_STDERR */ + +#define MGMTD_TRXN_LOCK(trxn) mgmt_trxn_lock(trxn, __FILE__, __LINE__) +#define MGMTD_TRXN_UNLOCK(trxn) mgmt_trxn_unlock(trxn, __FILE__, __LINE__) + +enum mgmt_trxn_event { + MGMTD_TRXN_PROC_SETCFG = 1, + MGMTD_TRXN_PROC_COMMITCFG, + MGMTD_TRXN_PROC_GETCFG, + MGMTD_TRXN_PROC_GETDATA, + MGMTD_TRXN_COMMITCFG_TIMEOUT, + MGMTD_TRXN_CLEANUP +}; + +PREDECL_LIST(mgmt_trxn_req_list); + +struct mgmt_set_cfg_req { + Mgmtd__DatabaseId db_id; + struct mgmt_db_ctxt *db_ctxt; + struct nb_cfg_change cfg_changes[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + uint16_t num_cfg_changes; + bool implicit_commit; + Mgmtd__DatabaseId dst_db_id; + struct mgmt_db_ctxt *dst_db_ctxt; + struct mgmt_setcfg_stats *setcfg_stats; +}; + +enum mgmt_commit_phase { + MGMTD_COMMIT_PHASE_PREPARE_CFG = 0, + MGMTD_COMMIT_PHASE_TRXN_CREATE, + MGMTD_COMMIT_PHASE_SEND_CFG, +#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED + MGMTD_COMMIT_PHASE_VALIDATE_CFG, +#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + MGMTD_COMMIT_PHASE_APPLY_CFG, + MGMTD_COMMIT_PHASE_TRXN_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_TRXN_CREATE: + return "CREATE-TRXN"; + case MGMTD_COMMIT_PHASE_SEND_CFG: + return "SEND-CFG"; +#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED + case MGMTD_COMMIT_PHASE_VALIDATE_CFG: + return "VALIDATE-CFG"; +#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + case MGMTD_COMMIT_PHASE_APPLY_CFG: + return "APPLY-CFG"; + case MGMTD_COMMIT_PHASE_TRXN_DELETE: + return "DELETE-TRXN"; + case MGMTD_COMMIT_PHASE_MAX: + return "Invalid/Unknown"; + } + + return "Invalid/Unknown"; +} + +PREDECL_LIST(mgmt_trxn_batch_list); + +struct mgmt_trxn_bcknd_cfg_batch { + struct mgmt_trxn_ctxt *trxn; + uint64_t batch_id; + enum mgmt_bcknd_client_id bcknd_id; + struct mgmt_bcknd_client_adapter *bcknd_adptr; + union mgmt_bcknd_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_trxn_batch_list_item list_linkage; +}; + +DECLARE_LIST(mgmt_trxn_batch_list, struct mgmt_trxn_bcknd_cfg_batch, + list_linkage); + +#define FOREACH_TRXN_CFG_BATCH_IN_LIST(list, batch) \ + frr_each_safe(mgmt_trxn_batch_list, list, batch) + +struct mgmt_commit_cfg_req { + Mgmtd__DatabaseId src_db_id; + struct mgmt_db_ctxt *src_db_ctxt; + Mgmtd__DatabaseId dst_db_id; + struct mgmt_db_ctxt *dst_db_ctxt; + uint32_t nb_trxn_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 DBs. 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_bcknd_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_trxn_batch_list_head + curr_batches[MGMTD_BCKND_CLIENT_ID_MAX]; + struct mgmt_trxn_batch_list_head + next_batches[MGMTD_BCKND_CLIENT_ID_MAX]; + /* + * The last batch added for any backend client. This is always on + * 'curr_batches' + */ + struct mgmt_trxn_bcknd_cfg_batch + *last_bcknd_cfg_batch[MGMTD_BCKND_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__DatabaseId db_id; + struct mgmt_db_ctxt *db_ctxt; + 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_trxn_req { + struct mgmt_trxn_ctxt *trxn; + enum mgmt_trxn_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_bknd_proc; + struct mgmt_trxn_req_list_item list_linkage; +}; + +DECLARE_LIST(mgmt_trxn_req_list, struct mgmt_trxn_req, list_linkage); + +#define FOREACH_TRXN_REQ_IN_LIST(list, req) \ + frr_each_safe(mgmt_trxn_req_list, list, req) + +struct mgmt_trxn_ctxt { + uint64_t session_id; /* One transaction per client session */ + uint64_t trxn_id; + enum mgmt_trxn_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_trxn_badptr_list_head bcknd_adptrs; + + int refcount; + + struct mgmt_trxn_list_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_trxn_req_list_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_trxn_req_list_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_trxn_req_list_head get_data_reqs; + struct mgmt_trxn_req_list_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_trxn_req *commit_cfg_req; +}; + +DECLARE_LIST(mgmt_trxn_list, struct mgmt_trxn_ctxt, list_linkage); + +#define FOREACH_TRXN_IN_LIST(mm, trxn) \ + frr_each_safe(mgmt_trxn_list, &(mm)->trxn_list, (trxn)) + +static int mgmt_trxn_send_commit_cfg_reply(struct mgmt_trxn_ctxt *trxn, + enum mgmt_result result, + const char *error_if_any); + +static inline const char * +mgmt_trxn_commit_phase_str(struct mgmt_trxn_ctxt *trxn, bool curr) +{ + if (!trxn->commit_cfg_req) + return "None"; + + return (mgmt_commit_phase2str( + curr ? trxn->commit_cfg_req->req.commit_cfg.curr_phase + : trxn->commit_cfg_req->req.commit_cfg.next_phase)); +} + +static void mgmt_trxn_lock(struct mgmt_trxn_ctxt *trxn, const char *file, + int line); +static void mgmt_trxn_unlock(struct mgmt_trxn_ctxt **trxn, const char *file, + int line); +static int +mgmt_trxn_send_bcknd_trxn_delete(struct mgmt_trxn_ctxt *trxn, + struct mgmt_bcknd_client_adapter *adptr); + +static struct thread_master *mgmt_trxn_tm; +static struct mgmt_master *mgmt_trxn_mm; + +static void mgmt_trxn_register_event(struct mgmt_trxn_ctxt *trxn, + enum mgmt_trxn_event event); + +static int +mgmt_move_bcknd_commit_to_next_phase(struct mgmt_trxn_ctxt *trxn, + struct mgmt_bcknd_client_adapter *adptr); + +static struct mgmt_trxn_bcknd_cfg_batch * +mgmt_trxn_cfg_batch_alloc(struct mgmt_trxn_ctxt *trxn, + enum mgmt_bcknd_client_id id, + struct mgmt_bcknd_client_adapter *bcknd_adptr) +{ + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + cfg_btch = XCALLOC(MTYPE_MGMTD_TRXN_CFG_BATCH, + sizeof(struct mgmt_trxn_bcknd_cfg_batch)); + assert(cfg_btch); + cfg_btch->bcknd_id = id; + + cfg_btch->trxn = trxn; + MGMTD_TRXN_LOCK(trxn); + assert(trxn->commit_cfg_req); + mgmt_trxn_batch_list_add_tail( + &trxn->commit_cfg_req->req.commit_cfg.curr_batches[id], + cfg_btch); + cfg_btch->bcknd_adptr = bcknd_adptr; + cfg_btch->buf_space_left = MGMTD_BCKND_CFGDATA_MAX_MSG_LEN; + if (bcknd_adptr) + mgmt_bcknd_adapter_lock(bcknd_adptr); + + trxn->commit_cfg_req->req.commit_cfg.last_bcknd_cfg_batch[id] = + cfg_btch; + if (!trxn->commit_cfg_req->req.commit_cfg.next_batch_id) + trxn->commit_cfg_req->req.commit_cfg.next_batch_id++; + cfg_btch->batch_id = + trxn->commit_cfg_req->req.commit_cfg.next_batch_id++; + hash_get(trxn->commit_cfg_req->req.commit_cfg.batches, cfg_btch, + hash_alloc_intern); + + return cfg_btch; +} + +static void +mgmt_trxn_cfg_batch_free(struct mgmt_trxn_bcknd_cfg_batch **cfg_btch) +{ + size_t indx; + struct mgmt_commit_cfg_req *cmtcfg_req; + + MGMTD_TRXN_DBG(" Batch: %p, Trxn: %p", *cfg_btch, (*cfg_btch)->trxn); + + assert((*cfg_btch)->trxn + && (*cfg_btch)->trxn->type == MGMTD_TRXN_TYPE_CONFIG); + + cmtcfg_req = &(*cfg_btch)->trxn->commit_cfg_req->req.commit_cfg; + hash_release(cmtcfg_req->batches, *cfg_btch); + mgmt_trxn_batch_list_del( + &cmtcfg_req->curr_batches[(*cfg_btch)->bcknd_id], *cfg_btch); + mgmt_trxn_batch_list_del( + &cmtcfg_req->next_batches[(*cfg_btch)->bcknd_id], *cfg_btch); + + if ((*cfg_btch)->bcknd_adptr) + mgmt_bcknd_adapter_unlock(&(*cfg_btch)->bcknd_adptr); + + 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_TRXN_UNLOCK(&(*cfg_btch)->trxn); + + XFREE(MTYPE_MGMTD_TRXN_CFG_BATCH, *cfg_btch); + *cfg_btch = NULL; +} + +static unsigned int mgmt_trxn_cfgbatch_hash_key(const void *data) +{ + const struct mgmt_trxn_bcknd_cfg_batch *batch = data; + + return jhash2((uint32_t *) &batch->batch_id, + sizeof(batch->batch_id) / sizeof(uint32_t), 0); +} + +static bool mgmt_trxn_cfgbatch_hash_cmp(const void *d1, const void *d2) +{ + const struct mgmt_trxn_bcknd_cfg_batch *batch1 = d1; + const struct mgmt_trxn_bcknd_cfg_batch *batch2 = d2; + + return (batch1->batch_id == batch2->batch_id); +} + +static void mgmt_trxn_cfgbatch_hash_free(void *data) +{ + struct mgmt_trxn_bcknd_cfg_batch *batch = data; + + mgmt_trxn_cfg_batch_free(&batch); +} + +static inline struct mgmt_trxn_bcknd_cfg_batch * +mgmt_trxn_cfgbatch_id2ctxt(struct mgmt_trxn_ctxt *trxn, uint64_t batch_id) +{ + struct mgmt_trxn_bcknd_cfg_batch key = {0}; + struct mgmt_trxn_bcknd_cfg_batch *batch; + + if (!trxn->commit_cfg_req) + return NULL; + + key.batch_id = batch_id; + batch = hash_lookup(trxn->commit_cfg_req->req.commit_cfg.batches, + &key); + + return batch; +} + +static void mgmt_trxn_cleanup_bcknd_cfg_batches(struct mgmt_trxn_ctxt *trxn, + enum mgmt_bcknd_client_id id) +{ + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_trxn_batch_list_head *list; + + list = &trxn->commit_cfg_req->req.commit_cfg.curr_batches[id]; + FOREACH_TRXN_CFG_BATCH_IN_LIST (list, cfg_btch) + mgmt_trxn_cfg_batch_free(&cfg_btch); + + mgmt_trxn_batch_list_fini(list); + + list = &trxn->commit_cfg_req->req.commit_cfg.next_batches[id]; + FOREACH_TRXN_CFG_BATCH_IN_LIST (list, cfg_btch) + mgmt_trxn_cfg_batch_free(&cfg_btch); + + mgmt_trxn_batch_list_fini(list); + + trxn->commit_cfg_req->req.commit_cfg.last_bcknd_cfg_batch[id] = NULL; +} + +static struct mgmt_trxn_req *mgmt_trxn_req_alloc(struct mgmt_trxn_ctxt *trxn, + uint64_t req_id, + enum mgmt_trxn_event req_event) +{ + struct mgmt_trxn_req *trxn_req; + enum mgmt_bcknd_client_id id; + + trxn_req = XCALLOC(MTYPE_MGMTD_TRXN_REQ, sizeof(struct mgmt_trxn_req)); + assert(trxn_req); + trxn_req->trxn = trxn; + trxn_req->req_id = req_id; + trxn_req->req_event = req_event; + trxn_req->pending_bknd_proc = false; + + switch (trxn_req->req_event) { + case MGMTD_TRXN_PROC_SETCFG: + trxn_req->req.set_cfg = + XCALLOC(MTYPE_MGMTD_TRXN_SETCFG_REQ, + sizeof(struct mgmt_set_cfg_req)); + assert(trxn_req->req.set_cfg); + mgmt_trxn_req_list_add_tail(&trxn->set_cfg_reqs, trxn_req); + MGMTD_TRXN_DBG( + "Added a new SETCFG Req: %p for Trxn: %p, Sessn: 0x%llx", + trxn_req, trxn, (unsigned long long)trxn->session_id); + break; + case MGMTD_TRXN_PROC_COMMITCFG: + trxn->commit_cfg_req = trxn_req; + MGMTD_TRXN_DBG( + "Added a new COMMITCFG Req: %p for Trxn: %p, Sessn: 0x%llx", + trxn_req, trxn, (unsigned long long)trxn->session_id); + + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + mgmt_trxn_batch_list_init( + &trxn_req->req.commit_cfg.curr_batches[id]); + mgmt_trxn_batch_list_init( + &trxn_req->req.commit_cfg.next_batches[id]); + } + + trxn_req->req.commit_cfg.batches = + hash_create(mgmt_trxn_cfgbatch_hash_key, + mgmt_trxn_cfgbatch_hash_cmp, + "MGMT Config Batches"); + break; + case MGMTD_TRXN_PROC_GETCFG: + trxn_req->req.get_data = + XCALLOC(MTYPE_MGMTD_TRXN_GETDATA_REQ, + sizeof(struct mgmt_get_data_req)); + assert(trxn_req->req.get_data); + mgmt_trxn_req_list_add_tail(&trxn->get_cfg_reqs, trxn_req); + MGMTD_TRXN_DBG( + "Added a new GETCFG Req: %p for Trxn: %p, Sessn: 0x%llx", + trxn_req, trxn, (unsigned long long)trxn->session_id); + break; + case MGMTD_TRXN_PROC_GETDATA: + trxn_req->req.get_data = + XCALLOC(MTYPE_MGMTD_TRXN_GETDATA_REQ, + sizeof(struct mgmt_get_data_req)); + assert(trxn_req->req.get_data); + mgmt_trxn_req_list_add_tail(&trxn->get_data_reqs, trxn_req); + MGMTD_TRXN_DBG( + "Added a new GETDATA Req: %p for Trxn: %p, Sessn: 0x%llx", + trxn_req, trxn, (unsigned long long)trxn->session_id); + break; + case MGMTD_TRXN_COMMITCFG_TIMEOUT: + case MGMTD_TRXN_CLEANUP: + break; + } + + MGMTD_TRXN_LOCK(trxn); + + return trxn_req; +} + +static void mgmt_trxn_req_free(struct mgmt_trxn_req **trxn_req) +{ + int indx; + struct mgmt_trxn_req_list_head *req_list = NULL; + struct mgmt_trxn_req_list_head *pending_list = NULL; + enum mgmt_bcknd_client_id id; + struct mgmt_bcknd_client_adapter *adptr; + + switch ((*trxn_req)->req_event) { + case MGMTD_TRXN_PROC_SETCFG: + for (indx = 0; indx < (*trxn_req)->req.set_cfg->num_cfg_changes; + indx++) { + if ((*trxn_req)->req.set_cfg->cfg_changes[indx].value) { + MGMTD_TRXN_DBG( + "Freeing value for %s at %p ==> '%s'", + (*trxn_req) + ->req.set_cfg->cfg_changes[indx] + .xpath, + (*trxn_req) + ->req.set_cfg->cfg_changes[indx] + .value, + (*trxn_req) + ->req.set_cfg->cfg_changes[indx] + .value); + free((void *)(*trxn_req) + ->req.set_cfg->cfg_changes[indx] + .value); + } + } + req_list = &(*trxn_req)->trxn->set_cfg_reqs; + MGMTD_TRXN_DBG("Deleting SETCFG Req: %p for Trxn: %p", + *trxn_req, (*trxn_req)->trxn); + XFREE(MTYPE_MGMTD_TRXN_SETCFG_REQ, (*trxn_req)->req.set_cfg); + break; + case MGMTD_TRXN_PROC_COMMITCFG: + MGMTD_TRXN_DBG("Deleting COMMITCFG Req: %p for Trxn: %p", + *trxn_req, (*trxn_req)->trxn); + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + /* + * Send TRXN_DELETE to cleanup state for this + * transaction on backend + */ + if ((*trxn_req)->req.commit_cfg.curr_phase + >= MGMTD_COMMIT_PHASE_TRXN_CREATE + && (*trxn_req)->req.commit_cfg.curr_phase + < MGMTD_COMMIT_PHASE_TRXN_DELETE + && (*trxn_req) + ->req.commit_cfg.subscr_info + .xpath_subscr[id] + .subscribed) { + adptr = mgmt_bcknd_get_adapter_by_id(id); + if (adptr) + mgmt_trxn_send_bcknd_trxn_delete( + (*trxn_req)->trxn, adptr); + } + + mgmt_trxn_cleanup_bcknd_cfg_batches((*trxn_req)->trxn, + id); + if ((*trxn_req)->req.commit_cfg.batches) { + hash_clean((*trxn_req)->req.commit_cfg.batches, + mgmt_trxn_cfgbatch_hash_free); + hash_free((*trxn_req)->req.commit_cfg.batches); + (*trxn_req)->req.commit_cfg.batches = NULL; + } + } + break; + case MGMTD_TRXN_PROC_GETCFG: + for (indx = 0; indx < (*trxn_req)->req.get_data->num_xpaths; + indx++) { + if ((*trxn_req)->req.get_data->xpaths[indx]) + free((void *)(*trxn_req) + ->req.get_data->xpaths[indx]); + } + req_list = &(*trxn_req)->trxn->get_cfg_reqs; + MGMTD_TRXN_DBG("Deleting GETCFG Req: %p for Trxn: %p", + *trxn_req, (*trxn_req)->trxn); + if ((*trxn_req)->req.get_data->reply) + XFREE(MTYPE_MGMTD_TRXN_GETDATA_REPLY, + (*trxn_req)->req.get_data->reply); + XFREE(MTYPE_MGMTD_TRXN_GETDATA_REQ, (*trxn_req)->req.get_data); + break; + case MGMTD_TRXN_PROC_GETDATA: + for (indx = 0; indx < (*trxn_req)->req.get_data->num_xpaths; + indx++) { + if ((*trxn_req)->req.get_data->xpaths[indx]) + free((void *)(*trxn_req) + ->req.get_data->xpaths[indx]); + } + pending_list = &(*trxn_req)->trxn->pending_get_datas; + req_list = &(*trxn_req)->trxn->get_data_reqs; + MGMTD_TRXN_DBG("Deleting GETDATA Req: %p for Trxn: %p", + *trxn_req, (*trxn_req)->trxn); + if ((*trxn_req)->req.get_data->reply) + XFREE(MTYPE_MGMTD_TRXN_GETDATA_REPLY, + (*trxn_req)->req.get_data->reply); + XFREE(MTYPE_MGMTD_TRXN_GETDATA_REQ, (*trxn_req)->req.get_data); + break; + case MGMTD_TRXN_COMMITCFG_TIMEOUT: + case MGMTD_TRXN_CLEANUP: + break; + } + + if ((*trxn_req)->pending_bknd_proc && pending_list) { + mgmt_trxn_req_list_del(pending_list, *trxn_req); + MGMTD_TRXN_DBG("Removed Req: %p from pending-list (left:%d)", + *trxn_req, + (int)mgmt_trxn_req_list_count(pending_list)); + } else if (req_list) { + mgmt_trxn_req_list_del(req_list, *trxn_req); + MGMTD_TRXN_DBG("Removed Req: %p from request-list (left:%d)", + *trxn_req, + (int)mgmt_trxn_req_list_count(req_list)); + } + + (*trxn_req)->pending_bknd_proc = false; + MGMTD_TRXN_UNLOCK(&(*trxn_req)->trxn); + XFREE(MTYPE_MGMTD_TRXN_REQ, (*trxn_req)); + *trxn_req = NULL; +} + +static void mgmt_trxn_process_set_cfg(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + struct mgmt_db_ctxt *db_ctxt; + 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; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + cmt_stats = mgmt_frntnd_get_sessn_commit_stats(trxn->session_id); + + MGMTD_TRXN_DBG( + "Processing %d SET_CONFIG requests for Trxn:%p Session:0x%llx", + (int)mgmt_trxn_req_list_count(&trxn->set_cfg_reqs), trxn, + (unsigned long long)trxn->session_id); + + FOREACH_TRXN_REQ_IN_LIST (&trxn->set_cfg_reqs, trxn_req) { + error = false; + assert(trxn_req->req_event == MGMTD_TRXN_PROC_SETCFG); + db_ctxt = trxn_req->req.set_cfg->db_ctxt; + if (!db_ctxt) { + mgmt_frntnd_send_set_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.set_cfg->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, "No such database!", + trxn_req->req.set_cfg->implicit_commit); + error = true; + goto mgmt_trxn_process_set_cfg_done; + } + + nb_config = mgmt_db_get_nb_config(db_ctxt); + if (!nb_config) { + mgmt_frntnd_send_set_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.set_cfg->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, + "Unable to retrieve DB Config Tree!", + trxn_req->req.set_cfg->implicit_commit); + error = true; + goto mgmt_trxn_process_set_cfg_done; + } + + error = false; + nb_candidate_edit_config_changes( + nb_config, trxn_req->req.set_cfg->cfg_changes, + (size_t)trxn_req->req.set_cfg->num_cfg_changes, NULL, + NULL, 0, err_buf, sizeof(err_buf), &error); + if (error) { + mgmt_frntnd_send_set_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.set_cfg->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, err_buf, + trxn_req->req.set_cfg->implicit_commit); + goto mgmt_trxn_process_set_cfg_done; + } + + if (trxn_req->req.set_cfg->implicit_commit) { + assert(mgmt_trxn_req_list_count(&trxn->set_cfg_reqs) + == 1); + assert(trxn_req->req.set_cfg->dst_db_ctxt); + + ret = mgmt_db_write_lock( + trxn_req->req.set_cfg->dst_db_ctxt); + if (ret != 0) { + MGMTD_TRXN_ERR( + "Failed to lock the DB %u for trxn: %p sessn 0x%llx, errstr %s!", + trxn_req->req.set_cfg->dst_db_id, trxn, + (unsigned long long)trxn->session_id, + strerror(ret)); + mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_DB_LOCK_FAILED, + "Lock running DB before implicit commit failed!"); + goto mgmt_trxn_process_set_cfg_done; + } + + mgmt_trxn_send_commit_config_req( + trxn->trxn_id, trxn_req->req_id, + trxn_req->req.set_cfg->db_id, + trxn_req->req.set_cfg->db_ctxt, + trxn_req->req.set_cfg->dst_db_id, + trxn_req->req.set_cfg->dst_db_ctxt, false, + false, true); + + if (mm->perf_stats_en) + gettimeofday(&cmt_stats->last_start, NULL); + cmt_stats->commit_cnt++; + } else if (mgmt_frntnd_send_set_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.set_cfg->db_id, + trxn_req->req_id, MGMTD_SUCCESS, NULL, false) + != 0) { + MGMTD_TRXN_ERR( + "Failed to send SET_CONFIG_REPLY for trxn %p sessn 0x%llx", + trxn, (unsigned long long)trxn->session_id); + error = true; + } + + mgmt_trxn_process_set_cfg_done: + + /* + * Note: The following will remove it from the list as well. + */ + mgmt_trxn_req_free(&trxn_req); + + num_processed++; + if (num_processed == MGMTD_TRXN_MAX_NUM_SETCFG_PROC) + break; + } + + left = mgmt_trxn_req_list_count(&trxn->set_cfg_reqs); + if (left) { + MGMTD_TRXN_DBG( + "Processed maximum number of Set-Config requests (%d/%d/%d). Rescheduling for rest.", + num_processed, MGMTD_TRXN_MAX_NUM_SETCFG_PROC, + (int)left); + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_SETCFG); + } +} + +static int mgmt_trxn_send_commit_cfg_reply(struct mgmt_trxn_ctxt *trxn, + enum mgmt_result result, + const char *error_if_any) +{ + int ret = 0; + bool success, create_cmt_info_rec; + + if (!trxn->commit_cfg_req) + return -1; + + success = result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES + ? true + : false; + if (!trxn->commit_cfg_req->req.commit_cfg.implicit && trxn->session_id + && mgmt_frntnd_send_commit_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn->commit_cfg_req->req.commit_cfg.src_db_id, + trxn->commit_cfg_req->req.commit_cfg.dst_db_id, + trxn->commit_cfg_req->req_id, + trxn->commit_cfg_req->req.commit_cfg.validate_only, + result, error_if_any) + != 0) { + MGMTD_TRXN_ERR( + "Failed to send COMMIT-CONFIG-REPLY for Trxn %p Sessn 0x%llx", + trxn, (unsigned long long)trxn->session_id); + } + + if (trxn->commit_cfg_req->req.commit_cfg.implicit && trxn->session_id + && mgmt_frntnd_send_set_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn->commit_cfg_req->req.commit_cfg.src_db_id, + trxn->commit_cfg_req->req_id, + success ? MGMTD_SUCCESS : MGMTD_INTERNAL_ERROR, + error_if_any, true) + != 0) { + MGMTD_TRXN_ERR( + "Failed to send SET-CONFIG-REPLY for Trxn %p Sessn 0x%llx", + trxn, (unsigned long long)trxn->session_id); + } + + if (success) { + /* Stop the commit-timeout timer */ + THREAD_OFF(trxn->comm_cfg_timeout); + + create_cmt_info_rec = + result != MGMTD_NO_CFG_CHANGES + && !trxn->commit_cfg_req->req.commit_cfg + .rollback + ? true + : false; + + /* + * Successful commit: Merge Src DB into Dst DB if and only if + * this was not a validate-only or abort request. + */ + if ((trxn->session_id + && !trxn->commit_cfg_req->req.commit_cfg.validate_only + && !trxn->commit_cfg_req->req.commit_cfg.abort) + || trxn->commit_cfg_req->req.commit_cfg.rollback) { + mgmt_db_copy_dbs(trxn->commit_cfg_req->req.commit_cfg + .src_db_ctxt, + trxn->commit_cfg_req->req.commit_cfg + .dst_db_ctxt, + create_cmt_info_rec); + } + + /* + * Restore Src DB back to Dest DB only through a commit abort + * request. + */ + if (trxn->session_id + && trxn->commit_cfg_req->req.commit_cfg.abort) + mgmt_db_copy_dbs(trxn->commit_cfg_req->req.commit_cfg + .dst_db_ctxt, + trxn->commit_cfg_req->req.commit_cfg + .src_db_ctxt, + false); + } else { + /* + * The commit has failied. For implicit commit requests restore + * back the contents of the candidate DB. + */ + if (trxn->commit_cfg_req->req.commit_cfg.implicit) + mgmt_db_copy_dbs(trxn->commit_cfg_req->req.commit_cfg + .dst_db_ctxt, + trxn->commit_cfg_req->req.commit_cfg + .src_db_ctxt, + false); + } + + if (trxn->commit_cfg_req->req.commit_cfg.rollback) { + ret = mgmt_db_unlock( + trxn->commit_cfg_req->req.commit_cfg.dst_db_ctxt); + if (ret != 0) + MGMTD_TRXN_ERR( + "Failed to unlock the dst DB during rollback : %s", + strerror(ret)); + } + + if (trxn->commit_cfg_req->req.commit_cfg.implicit) + if (mgmt_db_unlock( + trxn->commit_cfg_req->req.commit_cfg.dst_db_ctxt) + != 0) + MGMTD_TRXN_ERR( + "Failed to unlock the dst DB during implicit : %s", + strerror(ret)); + + trxn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL; + mgmt_trxn_req_free(&trxn->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 (!trxn->session_id) + mgmt_trxn_register_event(trxn, MGMTD_TRXN_CLEANUP); + + return 0; +} + +static void +mgmt_move_trxn_cfg_batch_to_next(struct mgmt_commit_cfg_req *cmtcfg_req, + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch, + struct mgmt_trxn_batch_list_head *src_list, + struct mgmt_trxn_batch_list_head *dst_list, + bool update_commit_phase, + enum mgmt_commit_phase to_phase) +{ + mgmt_trxn_batch_list_del(src_list, cfg_btch); + + if (update_commit_phase) { + MGMTD_TRXN_DBG( + "Move Trxn-Id %p Batch-Id %p from '%s' --> '%s'", + cfg_btch->trxn, cfg_btch, + mgmt_commit_phase2str(cfg_btch->comm_phase), + mgmt_trxn_commit_phase_str(cfg_btch->trxn, false)); + cfg_btch->comm_phase = to_phase; + } + + mgmt_trxn_batch_list_add_tail(dst_list, cfg_btch); +} + +static void mgmt_move_trxn_cfg_batches( + struct mgmt_trxn_ctxt *trxn, struct mgmt_commit_cfg_req *cmtcfg_req, + struct mgmt_trxn_batch_list_head *src_list, + struct mgmt_trxn_batch_list_head *dst_list, bool update_commit_phase, + enum mgmt_commit_phase to_phase) +{ + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + FOREACH_TRXN_CFG_BATCH_IN_LIST (src_list, cfg_btch) { + mgmt_move_trxn_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_trxn_ctxt *trxn, + struct mgmt_commit_cfg_req *cmtcfg_req) +{ + struct mgmt_trxn_batch_list_head *curr_list, *next_list; + enum mgmt_bcknd_client_id id; + + MGMTD_TRXN_DBG("Trxn-Id %p, Phase(current:'%s' next:'%s')", trxn, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, false)); + + /* + * Check if all clients has moved to next phase or not. + */ + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed + && mgmt_trxn_batch_list_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_TRXN_DBG("Move entire Trxn-Id %p from '%s' to '%s'", trxn, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, 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_TRXN_DBG( + "Move back all config batches for Trxn %p from next to current branch", + trxn); + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + curr_list = &cmtcfg_req->curr_batches[id]; + next_list = &cmtcfg_req->next_batches[id]; + mgmt_move_trxn_cfg_batches(trxn, cmtcfg_req, next_list, + curr_list, false, 0); + } + + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_COMMITCFG); + + return 0; +} + +static int +mgmt_move_bcknd_commit_to_next_phase(struct mgmt_trxn_ctxt *trxn, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_trxn_batch_list_head *curr_list, *next_list; + + if (trxn->type != MGMTD_TRXN_TYPE_CONFIG || !trxn->commit_cfg_req) + return -1; + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + + MGMTD_TRXN_DBG( + "Move Trxn-Id %p for '%s' Phase(current: '%s' next:'%s')", trxn, + adptr->name, mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, false)); + + MGMTD_TRXN_DBG( + "Move all config batches for '%s' from current to next list", + adptr->name); + curr_list = &cmtcfg_req->curr_batches[adptr->id]; + next_list = &cmtcfg_req->next_batches[adptr->id]; + mgmt_move_trxn_cfg_batches(trxn, cmtcfg_req, curr_list, next_list, true, + cmtcfg_req->next_phase); + + MGMTD_TRXN_DBG("Trxn-Id %p, Phase(current:'%s' next:'%s')", trxn, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, false)); + + /* + * Check if all clients has moved to next phase or not. + */ + mgmt_try_move_commit_to_next_phase(trxn, cmtcfg_req); + + return 0; +} + +static int mgmt_trxn_create_config_batches(struct mgmt_trxn_req *trxn_req, + struct nb_config_cbs *changes) +{ + struct nb_config_cb *cb, *nxt; + struct nb_config_change *chg; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_bcknd_client_subscr_info subscr_info; + char *xpath = NULL, *value = NULL; + char err_buf[1024]; + enum mgmt_bcknd_client_id id; + struct mgmt_bcknd_client_adapter *adptr; + struct mgmt_commit_cfg_req *cmtcfg_req; + bool found_validator; + int num_chgs = 0; + int xpath_len, value_len; + + cmtcfg_req = &trxn_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_trxn_send_commit_cfg_reply( + trxn_req->trxn, MGMTD_INTERNAL_ERROR, + "Internal error! Could not get Xpath from Db node!"); + goto mgmt_trxn_create_config_batches_failed; + } + + value = (char *)lyd_get_value(chg->cb.dnode); + if (!value) + value = (char *)MGMTD_BCKND_CONTAINER_NODE_VAL; + + MGMTD_TRXN_DBG("XPATH: %s, Value: '%s'", xpath, + value ? value : "NIL"); + + if (mgmt_bcknd_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_trxn_send_commit_cfg_reply( + trxn_req->trxn, MGMTD_INTERNAL_ERROR, err_buf); + goto mgmt_trxn_create_config_batches_failed; + } + + xpath_len = strlen(xpath) + 1; + value_len = strlen(value) + 1; + found_validator = false; + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + if (!subscr_info.xpath_subscr[id].validate_config + && !subscr_info.xpath_subscr[id].notify_config) + continue; + + adptr = mgmt_bcknd_get_adapter_by_id(id); + if (!adptr) + continue; + + cfg_btch = cmtcfg_req->last_bcknd_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_trxn_cfg_batch_alloc( + trxn_req->trxn, id, adptr); + } + + 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_TRXN_DBG( + " -- %s, {V:%d, N:%d}, Batch: %p, Item:%d", + adptr->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_TRXN_ERR("***** %s", err_buf); + } + } + + cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs; + if (!num_chgs) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn_req->trxn, MGMTD_NO_CFG_CHANGES, + "No changes found to commit!"); + goto mgmt_trxn_create_config_batches_failed; + } + + cmtcfg_req->next_phase = MGMTD_COMMIT_PHASE_TRXN_CREATE; + return 0; + +mgmt_trxn_create_config_batches_failed: + + if (xpath) + free(xpath); + + return -1; +} + +static int mgmt_trxn_prepare_config(struct mgmt_trxn_ctxt *trxn) +{ + struct nb_context nb_ctxt; + struct nb_config *nb_config; + char err_buf[1024] = {0}; + struct nb_config_cbs changes; + struct nb_config_cbs *cfg_chgs = NULL; + int ret; + bool del_cfg_chgs = false; + + ret = 0; + memset(&nb_ctxt, 0, sizeof(nb_ctxt)); + memset(&changes, 0, sizeof(changes)); + if (trxn->commit_cfg_req->req.commit_cfg.cfg_chgs) { + cfg_chgs = trxn->commit_cfg_req->req.commit_cfg.cfg_chgs; + del_cfg_chgs = true; + goto mgmt_trxn_prep_config_validation_done; + } + + if (trxn->commit_cfg_req->req.commit_cfg.src_db_id + != MGMTD_DB_CANDIDATE) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INVALID_PARAM, + "Source DB cannot be any other than CANDIDATE!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + if (trxn->commit_cfg_req->req.commit_cfg.dst_db_id + != MGMTD_DB_RUNNING) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INVALID_PARAM, + "Destination DB cannot be any other than RUNNING!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + if (!trxn->commit_cfg_req->req.commit_cfg.src_db_ctxt) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INVALID_PARAM, "No such source database!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + if (!trxn->commit_cfg_req->req.commit_cfg.dst_db_ctxt) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INVALID_PARAM, + "No such destination database!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + if (trxn->commit_cfg_req->req.commit_cfg.abort) { + /* + * This is a commit abort request. Return back success. + * That should trigger a restore of Candidate database to + * Running. + */ + (void)mgmt_trxn_send_commit_cfg_reply(trxn, MGMTD_SUCCESS, + NULL); + goto mgmt_trxn_prepare_config_done; + } + + nb_config = mgmt_db_get_nb_config( + trxn->commit_cfg_req->req.commit_cfg.src_db_ctxt); + if (!nb_config) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Unable to retrieve Commit DB Config Tree!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + /* + * Check for diffs from scratch buffer. If found empty + * get the diff from Candidate DB 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 DB from a file. Get the + * diff from a full comparison of the candidate and + * running DBs. + */ + nb_config_diff( + mgmt_db_get_nb_config(trxn->commit_cfg_req->req + .commit_cfg.dst_db_ctxt), + 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_trxn_send_commit_cfg_reply( + trxn, MGMTD_NO_CFG_CHANGES, + "No changes found to be committed!"); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + /* + * Validate YANG contents of the source DB and get the diff + * between source and destination DB contents. + */ + nb_ctxt.client = NB_CLIENT_MGMTD_SERVER; + nb_ctxt.user = (void *)trxn; + 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_trxn_send_commit_cfg_reply(trxn, MGMTD_INVALID_PARAM, + err_buf); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } +#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED + if (mm->perf_stats_en) + gettimeofday(&trxn->commit_cfg_req->req.commit_cfg.cmt_stats + ->validate_start, + NULL); + + /* + * 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_ctxt, 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_trxn_send_commit_cfg_reply(trxn, MGMTD_INVALID_PARAM, + err_buf); + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + if (trxn->commit_cfg_req->req.commit_cfg.validate_only) { + /* + * This was a validate-only COMMIT request return success. + */ + (void)mgmt_trxn_send_commit_cfg_reply(trxn, MGMTD_SUCCESS, + NULL); + goto mgmt_trxn_prepare_config_done; + } +#endif /* ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + +mgmt_trxn_prep_config_validation_done: + + if (mm->perf_stats_en) + gettimeofday(&trxn->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_trxn_create_config_batches(trxn->commit_cfg_req, cfg_chgs); + if (ret != 0) { + ret = -1; + goto mgmt_trxn_prepare_config_done; + } + + /* Move to the Transaction Create Phase */ + trxn->commit_cfg_req->req.commit_cfg.curr_phase = + MGMTD_COMMIT_PHASE_TRXN_CREATE; + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_COMMITCFG); + + /* + * Start the COMMIT Timeout Timer to abort Trxn if things get stuck at + * backend. + */ + mgmt_trxn_register_event(trxn, MGMTD_TRXN_COMMITCFG_TIMEOUT); +mgmt_trxn_prepare_config_done: + + if (cfg_chgs && del_cfg_chgs) + nb_config_diff_del_changes(cfg_chgs); + + return ret; +} + +static int mgmt_trxn_send_bcknd_trxn_create(struct mgmt_trxn_ctxt *trxn) +{ + enum mgmt_bcknd_client_id id; + struct mgmt_bcknd_client_adapter *adptr; + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG && trxn->commit_cfg_req); + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed) { + adptr = mgmt_bcknd_get_adapter_by_id(id); + if (mgmt_bcknd_create_trxn(adptr, trxn->trxn_id) + != 0) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Could not send TRXN_CREATE to backend adapter"); + return -1; + } + + FOREACH_TRXN_CFG_BATCH_IN_LIST ( + &trxn->commit_cfg_req->req.commit_cfg + .curr_batches[id], + cfg_btch) + cfg_btch->comm_phase = + MGMTD_COMMIT_PHASE_TRXN_CREATE; + } + } + + trxn->commit_cfg_req->req.commit_cfg.next_phase = + MGMTD_COMMIT_PHASE_SEND_CFG; + + /* + * Dont move the commit to next phase yet. Wait for the TRXN_REPLY to + * come back. + */ + + MGMTD_TRXN_DBG( + "Trxn:%p Session:0x%llx, Phase(Current:'%s', Next: '%s')", trxn, + (unsigned long long)trxn->session_id, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, false)); + + return 0; +} + +static int +mgmt_trxn_send_bcknd_cfg_data(struct mgmt_trxn_ctxt *trxn, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_bcknd_cfgreq cfg_req = {0}; + size_t num_batches, indx; + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG && trxn->commit_cfg_req); + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + assert(cmtcfg_req->subscr_info.xpath_subscr[adptr->id].subscribed); + + indx = 0; + num_batches = mgmt_trxn_batch_list_count( + &cmtcfg_req->curr_batches[adptr->id]); + FOREACH_TRXN_CFG_BATCH_IN_LIST (&cmtcfg_req->curr_batches[adptr->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_bcknd_send_cfg_data_create_req( + adptr, trxn->trxn_id, cfg_btch->batch_id, &cfg_req, + indx == num_batches ? true : false) + != 0) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Internal Error! Could not send config data to backend!"); + MGMTD_TRXN_ERR( + "Could not send CFGDATA_CREATE for Trxn %p Batch %p to client '%s", + trxn, cfg_btch, adptr->name); + return -1; + } + + cmtcfg_req->cmt_stats->last_num_cfgdata_reqs++; + mgmt_move_trxn_cfg_batch_to_next( + cmtcfg_req, cfg_btch, + &cmtcfg_req->curr_batches[adptr->id], + &cmtcfg_req->next_batches[adptr->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(trxn, cmtcfg_req); + + return 0; +} + +static int +mgmt_trxn_send_bcknd_trxn_delete(struct mgmt_trxn_ctxt *trxn, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG && trxn->commit_cfg_req); + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + if (cmtcfg_req->subscr_info.xpath_subscr[adptr->id].subscribed) { + adptr = mgmt_bcknd_get_adapter_by_id(adptr->id); + (void)mgmt_bcknd_destroy_trxn(adptr, trxn->trxn_id); + + FOREACH_TRXN_CFG_BATCH_IN_LIST ( + &trxn->commit_cfg_req->req.commit_cfg + .curr_batches[adptr->id], + cfg_btch) + cfg_btch->comm_phase = MGMTD_COMMIT_PHASE_TRXN_DELETE; + } + + return 0; +} + +static void mgmt_trxn_cfg_commit_timedout(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG); + + if (!trxn->commit_cfg_req) + return; + + MGMTD_TRXN_ERR( + "Backend operations for Config Trxn %p has timedout! Aborting commit!!", + trxn); + + /* + * Send a COMMIT_CONFIG_REPLY with failure. + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + mgmt_trxn_send_commit_cfg_reply( + trxn, 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_trxn_register_event() and mgmt_trxn_process_commit_cfg() + * for details. + */ +static int mgmt_trxn_send_bcknd_cfg_apply(struct mgmt_trxn_ctxt *trxn) +{ + enum mgmt_bcknd_client_id id; + struct mgmt_bcknd_client_adapter *adptr; + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_trxn_batch_list_head *btch_list; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG && trxn->commit_cfg_req); + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + if (cmtcfg_req->validate_only) { + /* + * If this was a validate-only COMMIT request return success. + */ + (void)mgmt_trxn_send_commit_cfg_reply(trxn, MGMTD_SUCCESS, + NULL); + return 0; + } + + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].notify_config) { + adptr = mgmt_bcknd_get_adapter_by_id(id); + if (!adptr) + return -1; + + btch_list = &cmtcfg_req->curr_batches[id]; + if (mgmt_bcknd_send_cfg_apply_req(adptr, trxn->trxn_id) + != 0) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Could not send CFG_APPLY_REQ to backend adapter"); + return -1; + } + cmtcfg_req->cmt_stats->last_num_apply_reqs++; + + UNSET_FLAG(adptr->flags, + MGMTD_BCKND_ADPTR_FLAGS_CFG_SYNCED); + + FOREACH_TRXN_CFG_BATCH_IN_LIST (btch_list, cfg_btch) + cfg_btch->comm_phase = + MGMTD_COMMIT_PHASE_APPLY_CFG; + } + } + + trxn->commit_cfg_req->req.commit_cfg.next_phase = + MGMTD_COMMIT_PHASE_TRXN_DELETE; + + /* + * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES + * to come back. + */ + + return 0; +} + +#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED +/* + * Send CFG_VALIDATE_REQs to all the backend client. + */ +static int mgmt_trxn_send_bcknd_cfg_validate(struct mgmt_trxn_ctxt *trxn) +{ + enum mgmt_bcknd_client_id id; + struct mgmt_bcknd_client_adapter *adptr; + struct mgmt_commit_cfg_req *cmtcfg_req; + uint64_t *batch_ids; + size_t indx, num_batches; + struct mgmt_trxn_batch_list_head *btch_list; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + + assert(trxn->type == MGMTD_TRXN_TYPE_CONFIG && trxn->commit_cfg_req); + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + FOREACH_MGMTD_BCKND_CLIENT_ID (id) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].validate_config) { + adptr = mgmt_bcknd_get_adapter_by_id(id); + if (!adptr) + return -1; + + btch_list = &cmtcfg_req->curr_batches[id]; + num_batches = mgmt_trxn_batch_list_count(btch_list); + batch_ids = (uint64_t *)calloc(num_batches, + sizeof(uint64_t)); + + indx = 0; + FOREACH_TRXN_CFG_BATCH_IN_LIST (btch_list, cfg_btch) { + batch_ids[indx] = cfg_btch->batch_id; + indx++; + assert(indx <= num_batches); + } + + if (mgmt_bcknd_send_cfg_validate_req( + adptr, trxn->trxn_id, batch_ids, indx) + != 0) { + (void)mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Could not send CFG_VALIDATE_REQ to backend adapter"); + return -1; + } + + FOREACH_TRXN_CFG_BATCH_IN_LIST (btch_list, cfg_btch) { + cfg_btch->comm_phase = + MGMTD_COMMIT_PHASE_VALIDATE_CFG; + } + + free(batch_ids); + } + } + + trxn->commit_cfg_req->req.commit_cfg.next_phase = + MGMTD_COMMIT_PHASE_APPLY_CFG; + + /* + * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES + * to come back. + */ + + return 0; +} +#endif /* iddef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + +static void mgmt_trxn_process_commit_cfg(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_commit_cfg_req *cmtcfg_req; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + + MGMTD_TRXN_DBG( + "Processing COMMIT_CONFIG for Trxn:%p Session:0x%llx, Phase(Current:'%s', Next: '%s')", + trxn, (unsigned long long)trxn->session_id, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, false)); + + assert(trxn->commit_cfg_req); + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + switch (cmtcfg_req->curr_phase) { + case MGMTD_COMMIT_PHASE_PREPARE_CFG: + mgmt_trxn_prepare_config(trxn); + break; + case MGMTD_COMMIT_PHASE_TRXN_CREATE: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->trxn_create_start, + NULL); + /* + * Send TRXN_CREATE_REQ to all Backend now. + */ + mgmt_trxn_send_bcknd_trxn_create(trxn); + 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_VALIDATE_CFG); + MGMTD_TRXN_DBG( + "Trxn:%p Session:0x%llx, trigger sending CFG_VALIDATE_REQ to all backend clients", + trxn, (unsigned long long)trxn->session_id); +#else /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + assert(cmtcfg_req->next_phase == MGMTD_COMMIT_PHASE_APPLY_CFG); + MGMTD_TRXN_DBG( + "Trxn:%p Session:0x%llx, trigger sending CFG_APPLY_REQ to all backend clients", + trxn, (unsigned long long)trxn->session_id); +#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + break; +#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED + case MGMTD_COMMIT_PHASE_VALIDATE_CFG: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->validate_start, + NULL); + /* + * We should have received successful CFFDATA_CREATE_REPLY from + * all concerned Backend Clients by now. Send out the + * CFG_VALIDATE_REQs now. + */ + mgmt_trxn_send_bcknd_cfg_validate(trxn); + break; +#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + 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_trxn_send_bcknd_cfg_apply(trxn); + break; + case MGMTD_COMMIT_PHASE_TRXN_DELETE: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->trxn_del_start, + NULL); + /* + * We would have sent TRXN_DELETE_REQ to all backend by now. + * Send a successful CONFIG_COMMIT_REPLY back to front-end. + * NOTE: This should also trigger DB merge/unlock and Trxn + * cleanup. Please see mgmt_frntnd_send_commit_cfg_reply() for + * more details. + */ + THREAD_OFF(trxn->comm_cfg_timeout); + mgmt_trxn_send_commit_cfg_reply(trxn, MGMTD_SUCCESS, NULL); + break; + case MGMTD_COMMIT_PHASE_MAX: + break; + } + + MGMTD_TRXN_DBG( + "Trxn:%p Session:0x%llx, Phase updated to (Current:'%s', Next: '%s')", + trxn, (unsigned long long)trxn->session_id, + mgmt_trxn_commit_phase_str(trxn, true), + mgmt_trxn_commit_phase_str(trxn, 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_trxn_send_getcfg_reply_data(struct mgmt_trxn_req *trxn_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_TRXN_DBG("Sending %d Get-Config/Data replies (next-idx:%lld)", + (int) data_reply->n_data, + (long long)data_reply->next_indx); + + switch (trxn_req->req_event) { + case MGMTD_TRXN_PROC_GETCFG: + if (mgmt_frntnd_send_get_cfg_reply( + trxn_req->trxn->session_id, trxn_req->trxn->trxn_id, + get_req->db_id, trxn_req->req_id, MGMTD_SUCCESS, + data_reply, NULL) + != 0) { + MGMTD_TRXN_ERR( + "Failed to send GET-CONFIG-REPLY for Trxn %p, Sessn: 0x%llx, Req: %llu", + trxn_req->trxn, + (unsigned long long)trxn_req->trxn->session_id, + (unsigned long long)trxn_req->req_id); + } + break; + case MGMTD_TRXN_PROC_GETDATA: + if (mgmt_frntnd_send_get_data_reply( + trxn_req->trxn->session_id, trxn_req->trxn->trxn_id, + get_req->db_id, trxn_req->req_id, MGMTD_SUCCESS, + data_reply, NULL) + != 0) { + MGMTD_TRXN_ERR( + "Failed to send GET-DATA-REPLY for Trxn %p, Sessn: 0x%llx, Req: %llu", + trxn_req->trxn, + (unsigned long long)trxn_req->trxn->session_id, + (unsigned long long)trxn_req->req_id); + } + break; + case MGMTD_TRXN_PROC_SETCFG: + case MGMTD_TRXN_PROC_COMMITCFG: + case MGMTD_TRXN_COMMITCFG_TIMEOUT: + case MGMTD_TRXN_CLEANUP: + MGMTD_TRXN_ERR("Invalid Trxn-Req-Event %u", + trxn_req->req_event); + break; + } + + /* + * Reset reply buffer for next reply. + */ + mgmt_reset_get_data_reply_buf(get_req); +} + +static void mgmt_trxn_iter_and_send_get_cfg_reply(struct mgmt_db_ctxt *db_ctxt, + char *xpath, + struct lyd_node *node, + struct nb_node *nb_node, + void *ctxt) +{ + struct mgmt_trxn_req *trxn_req; + struct mgmt_get_data_req *get_req; + struct mgmt_get_data_reply *get_reply; + Mgmtd__YangData *data; + Mgmtd__YangDataValue *data_value; + + trxn_req = (struct mgmt_trxn_req *)ctxt; + if (!trxn_req) + goto mgmtd_ignore_get_cfg_reply_data; + + if (!(node->schema->nodetype & LYD_NODE_TERM)) + goto mgmtd_ignore_get_cfg_reply_data; + + assert(trxn_req->req_event == MGMTD_TRXN_PROC_GETCFG + || trxn_req->req_event == MGMTD_TRXN_PROC_GETDATA); + + get_req = trxn_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_TRXN_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_trxn_send_getcfg_reply_data(trxn_req, get_req); + + return; + +mgmtd_ignore_get_cfg_reply_data: + if (xpath) + free(xpath); +} + +static int mgmt_trxn_get_config(struct mgmt_trxn_ctxt *trxn, + struct mgmt_trxn_req *trxn_req, + struct mgmt_db_ctxt *db_ctxt) +{ + struct mgmt_trxn_req_list_head *req_list = NULL; + struct mgmt_trxn_req_list_head *pending_list = NULL; + int indx; + struct mgmt_get_data_req *get_data; + struct mgmt_get_data_reply *get_reply; + + switch (trxn_req->req_event) { + case MGMTD_TRXN_PROC_GETCFG: + req_list = &trxn->get_cfg_reqs; + break; + case MGMTD_TRXN_PROC_GETDATA: + req_list = &trxn->get_data_reqs; + break; + case MGMTD_TRXN_PROC_SETCFG: + case MGMTD_TRXN_PROC_COMMITCFG: + case MGMTD_TRXN_COMMITCFG_TIMEOUT: + case MGMTD_TRXN_CLEANUP: + assert(!"Wrong trxn request type!"); + break; + } + + get_data = trxn_req->req.get_data; + + if (!get_data->reply) { + get_data->reply = XCALLOC(MTYPE_MGMTD_TRXN_GETDATA_REPLY, + sizeof(struct mgmt_get_data_reply)); + if (!get_data->reply) { + mgmt_frntnd_send_get_cfg_reply( + trxn->session_id, trxn->trxn_id, + get_data->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "Internal error: Unable to allocate reply buffers!"); + goto mgmt_trxn_get_config_failed; + } + } + + /* + * Read data contents from the DB 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_TRXN_DBG("Trying to get all data under '%s'", + get_data->xpaths[indx]); + mgmt_init_get_data_reply(get_reply); + if (mgmt_db_iter_data(get_data->db_ctxt, get_data->xpaths[indx], + mgmt_trxn_iter_and_send_get_cfg_reply, + (void *)trxn_req, true) + == -1) { + MGMTD_TRXN_DBG("Invalid Xpath '%s", + get_data->xpaths[indx]); + mgmt_frntnd_send_get_cfg_reply( + trxn->session_id, trxn->trxn_id, + get_data->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, "Invalid xpath"); + goto mgmt_trxn_get_config_failed; + } + MGMTD_TRXN_DBG("Got %d remaining data-replies for xpath '%s'", + get_reply->num_reply, get_data->xpaths[indx]); + get_reply->last_batch = true; + mgmt_trxn_send_getcfg_reply_data(trxn_req, get_data); + } + +mgmt_trxn_get_config_failed: + + if (pending_list) { + /* + * Move the transaction to corresponding pending list. + */ + if (req_list) + mgmt_trxn_req_list_del(req_list, trxn_req); + trxn_req->pending_bknd_proc = true; + mgmt_trxn_req_list_add_tail(pending_list, trxn_req); + MGMTD_TRXN_DBG( + "Moved Req: %p for Trxn: %p from Req-List to Pending-List", + trxn_req, trxn_req->trxn); + } else { + /* + * Delete the trxn request. It will also remove it from request + * list. + */ + mgmt_trxn_req_free(&trxn_req); + } + + return 0; +} + +static void mgmt_trxn_process_get_cfg(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + struct mgmt_db_ctxt *db_ctxt; + int num_processed = 0; + bool error; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + + MGMTD_TRXN_DBG( + "Processing %d GET_CONFIG requests for Trxn:%p Session:0x%llx", + (int)mgmt_trxn_req_list_count(&trxn->get_cfg_reqs), trxn, + (unsigned long long)trxn->session_id); + + FOREACH_TRXN_REQ_IN_LIST (&trxn->get_cfg_reqs, trxn_req) { + error = false; + assert(trxn_req->req_event == MGMTD_TRXN_PROC_GETCFG); + db_ctxt = trxn_req->req.get_data->db_ctxt; + if (!db_ctxt) { + mgmt_frntnd_send_get_cfg_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.get_data->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "No such database!"); + error = true; + goto mgmt_trxn_process_get_cfg_done; + } + + if (mgmt_trxn_get_config(trxn, trxn_req, db_ctxt) != 0) { + MGMTD_TRXN_ERR( + "Unable to retrieve Config from DB %d for Trxn %p, Sessn: 0x%llx, Req: %llu!", + trxn_req->req.get_data->db_id, trxn, + (unsigned long long)trxn->session_id, + (unsigned long long)trxn_req->req_id); + error = true; + } + + mgmt_trxn_process_get_cfg_done: + + if (error) { + /* + * Delete the trxn request. + * Note: The following will remove it from the list + * as well. + */ + mgmt_trxn_req_free(&trxn_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_TRXN_MAX_NUM_GETCFG_PROC) + break; + } + + if (mgmt_trxn_req_list_count(&trxn->get_cfg_reqs)) { + MGMTD_TRXN_DBG( + "Processed maximum number of Get-Config requests (%d/%d). Rescheduling for rest.", + num_processed, MGMTD_TRXN_MAX_NUM_GETCFG_PROC); + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_GETCFG); + } +} + +static void mgmt_trxn_process_get_data(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + struct mgmt_db_ctxt *db_ctxt; + int num_processed = 0; + bool error; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + + MGMTD_TRXN_DBG( + "Processing %d GET_DATA requests for Trxn:%p Session:0x%llx", + (int)mgmt_trxn_req_list_count(&trxn->get_data_reqs), trxn, + (unsigned long long)trxn->session_id); + + FOREACH_TRXN_REQ_IN_LIST (&trxn->get_data_reqs, trxn_req) { + error = false; + assert(trxn_req->req_event == MGMTD_TRXN_PROC_GETDATA); + db_ctxt = trxn_req->req.get_data->db_ctxt; + if (!db_ctxt) { + mgmt_frntnd_send_get_data_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.get_data->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "No such database!"); + error = true; + goto mgmt_trxn_process_get_data_done; + } + + if (mgmt_db_is_config(db_ctxt)) { + if (mgmt_trxn_get_config(trxn, trxn_req, db_ctxt) + != 0) { + MGMTD_TRXN_ERR( + "Unable to retrieve Config from DB %d for Trxn %p, Sessn: 0x%llx, Req: %llu!", + trxn_req->req.get_data->db_id, trxn, + (unsigned long long)trxn->session_id, + (unsigned long long)trxn_req->req_id); + error = true; + } + } else { + /* + * TODO: Trigger GET procedures for Backend + * For now return back error. + */ + mgmt_frntnd_send_get_data_reply( + trxn->session_id, trxn->trxn_id, + trxn_req->req.get_data->db_id, trxn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "GET-DATA on Oper DB is not supported yet!"); + error = true; + } + + mgmt_trxn_process_get_data_done: + + if (error) { + /* + * Delete the trxn request. + * Note: The following will remove it from the list + * as well. + */ + mgmt_trxn_req_free(&trxn_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_TRXN_MAX_NUM_GETDATA_PROC) + break; + } + + if (mgmt_trxn_req_list_count(&trxn->get_data_reqs)) { + MGMTD_TRXN_DBG( + "Processed maximum number of Get-Data requests (%d/%d). Rescheduling for rest.", + num_processed, MGMTD_TRXN_MAX_NUM_GETDATA_PROC); + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_GETDATA); + } +} + +static struct mgmt_trxn_ctxt * +mgmt_frntnd_find_trxn_by_session_id(struct mgmt_master *cm, uint64_t session_id, + enum mgmt_trxn_type type) +{ + struct mgmt_trxn_ctxt *trxn; + + FOREACH_TRXN_IN_LIST (cm, trxn) { + if (trxn->session_id == session_id && trxn->type == type) + return trxn; + } + + return NULL; +} + +static struct mgmt_trxn_ctxt *mgmt_trxn_create_new(uint64_t session_id, + enum mgmt_trxn_type type) +{ + struct mgmt_trxn_ctxt *trxn = NULL; + + /* + * For 'CONFIG' transaction check if one is already created + * or not. + */ + if (type == MGMTD_TRXN_TYPE_CONFIG && mgmt_trxn_mm->cfg_trxn) { + if (mgmt_config_trxn_in_progress() == session_id) + trxn = mgmt_trxn_mm->cfg_trxn; + goto mgmt_create_trxn_done; + } + + trxn = mgmt_frntnd_find_trxn_by_session_id(mgmt_trxn_mm, session_id, + type); + if (!trxn) { + trxn = XCALLOC(MTYPE_MGMTD_TRXN, sizeof(struct mgmt_trxn_ctxt)); + assert(trxn); + + trxn->session_id = session_id; + trxn->type = type; + mgmt_trxn_badptr_list_init(&trxn->bcknd_adptrs); + mgmt_trxn_list_add_tail(&mgmt_trxn_mm->trxn_list, trxn); + mgmt_trxn_req_list_init(&trxn->set_cfg_reqs); + mgmt_trxn_req_list_init(&trxn->get_cfg_reqs); + mgmt_trxn_req_list_init(&trxn->get_data_reqs); + mgmt_trxn_req_list_init(&trxn->pending_get_datas); + trxn->commit_cfg_req = NULL; + trxn->refcount = 0; + if (!mgmt_trxn_mm->next_trxn_id) + mgmt_trxn_mm->next_trxn_id++; + trxn->trxn_id = mgmt_trxn_mm->next_trxn_id++; + hash_get(mgmt_trxn_mm->trxn_hash, trxn, hash_alloc_intern); + + MGMTD_TRXN_DBG("Added new '%s' MGMTD Transaction '%p'", + mgmt_trxn_type2str(type), trxn); + + if (type == MGMTD_TRXN_TYPE_CONFIG) + mgmt_trxn_mm->cfg_trxn = trxn; + + MGMTD_TRXN_LOCK(trxn); + } + +mgmt_create_trxn_done: + return trxn; +} + +static void mgmt_trxn_delete(struct mgmt_trxn_ctxt **trxn) +{ + MGMTD_TRXN_UNLOCK(trxn); +} + +static unsigned int mgmt_trxn_hash_key(const void *data) +{ + const struct mgmt_trxn_ctxt *trxn = data; + + return jhash2((uint32_t *) &trxn->trxn_id, + sizeof(trxn->trxn_id) / sizeof(uint32_t), 0); +} + +static bool mgmt_trxn_hash_cmp(const void *d1, const void *d2) +{ + const struct mgmt_trxn_ctxt *trxn1 = d1; + const struct mgmt_trxn_ctxt *trxn2 = d2; + + return (trxn1->trxn_id == trxn2->trxn_id); +} + +static void mgmt_trxn_hash_free(void *data) +{ + struct mgmt_trxn_ctxt *trxn = data; + + mgmt_trxn_delete(&trxn); +} + +static void mgmt_trxn_hash_init(void) +{ + if (!mgmt_trxn_mm || mgmt_trxn_mm->trxn_hash) + return; + + mgmt_trxn_mm->trxn_hash = hash_create(mgmt_trxn_hash_key, + mgmt_trxn_hash_cmp, + "MGMT Transactions"); +} + +static void mgmt_trxn_hash_destroy(void) +{ + if (!mgmt_trxn_mm || !mgmt_trxn_mm->trxn_hash) + return; + + hash_clean(mgmt_trxn_mm->trxn_hash, + mgmt_trxn_hash_free); + hash_free(mgmt_trxn_mm->trxn_hash); + mgmt_trxn_mm->trxn_hash = NULL; +} + +static inline struct mgmt_trxn_ctxt * +mgmt_trxn_id2ctxt(uint64_t trxn_id) +{ + struct mgmt_trxn_ctxt key = {0}; + struct mgmt_trxn_ctxt *trxn; + + if (!mgmt_trxn_mm || !mgmt_trxn_mm->trxn_hash) + return NULL; + + key.trxn_id = trxn_id; + trxn = hash_lookup(mgmt_trxn_mm->trxn_hash, &key); + + return trxn; +} + +static void mgmt_trxn_lock(struct mgmt_trxn_ctxt *trxn, const char *file, + int line) +{ + trxn->refcount++; + MGMTD_TRXN_DBG("%s:%d --> Lock %s Trxn %p, Count: %d", file, line, + mgmt_trxn_type2str(trxn->type), trxn, trxn->refcount); +} + +static void mgmt_trxn_unlock(struct mgmt_trxn_ctxt **trxn, const char *file, + int line) +{ + assert(*trxn && (*trxn)->refcount); + + (*trxn)->refcount--; + MGMTD_TRXN_DBG("%s:%d --> Unlock %s Trxn %p, Count: %d", file, line, + mgmt_trxn_type2str((*trxn)->type), *trxn, + (*trxn)->refcount); + if (!(*trxn)->refcount) { + if ((*trxn)->type == MGMTD_TRXN_TYPE_CONFIG) + if (mgmt_trxn_mm->cfg_trxn == *trxn) + mgmt_trxn_mm->cfg_trxn = NULL; + THREAD_OFF((*trxn)->proc_get_cfg); + THREAD_OFF((*trxn)->proc_get_data); + THREAD_OFF((*trxn)->proc_comm_cfg); + THREAD_OFF((*trxn)->comm_cfg_timeout); + hash_release(mgmt_trxn_mm->trxn_hash, *trxn); + mgmt_trxn_list_del(&mgmt_trxn_mm->trxn_list, *trxn); + + MGMTD_TRXN_DBG("Deleted %s Trxn %p for Sessn: 0x%llx", + mgmt_trxn_type2str((*trxn)->type), *trxn, + (unsigned long long)(*trxn)->session_id); + + XFREE(MTYPE_MGMTD_TRXN, *trxn); + } + + *trxn = NULL; +} + +static void mgmt_trxn_cleanup_trxn(struct mgmt_trxn_ctxt **trxn) +{ + /* TODO: Any other cleanup applicable */ + + mgmt_trxn_delete(trxn); +} + +static void +mgmt_trxn_cleanup_all_trxns(void) +{ + struct mgmt_trxn_ctxt *trxn; + + if (!mgmt_trxn_mm || !mgmt_trxn_mm->trxn_hash) + return; + + FOREACH_TRXN_IN_LIST (mgmt_trxn_mm, trxn) + mgmt_trxn_cleanup_trxn(&trxn); +} + +static void mgmt_trxn_cleanup(struct thread *thread) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = (struct mgmt_trxn_ctxt *)THREAD_ARG(thread); + assert(trxn); + + mgmt_trxn_cleanup_trxn(&trxn); +} + +static void mgmt_trxn_register_event(struct mgmt_trxn_ctxt *trxn, + enum mgmt_trxn_event event) +{ + struct timeval tv = {.tv_sec = 0, + .tv_usec = MGMTD_TRXN_PROC_DELAY_USEC}; + + assert(mgmt_trxn_mm && mgmt_trxn_tm); + + switch (event) { + case MGMTD_TRXN_PROC_SETCFG: + thread_add_timer_tv(mgmt_trxn_tm, mgmt_trxn_process_set_cfg, + trxn, &tv, &trxn->proc_set_cfg); + assert(trxn->proc_set_cfg); + break; + case MGMTD_TRXN_PROC_COMMITCFG: + thread_add_timer_tv(mgmt_trxn_tm, mgmt_trxn_process_commit_cfg, + trxn, &tv, &trxn->proc_comm_cfg); + assert(trxn->proc_comm_cfg); + break; + case MGMTD_TRXN_PROC_GETCFG: + thread_add_timer_tv(mgmt_trxn_tm, mgmt_trxn_process_get_cfg, + trxn, &tv, &trxn->proc_get_cfg); + assert(trxn->proc_get_cfg); + break; + case MGMTD_TRXN_PROC_GETDATA: + thread_add_timer_tv(mgmt_trxn_tm, mgmt_trxn_process_get_data, + trxn, &tv, &trxn->proc_get_data); + assert(trxn->proc_get_data); + break; + case MGMTD_TRXN_COMMITCFG_TIMEOUT: + thread_add_timer_msec(mgmt_trxn_tm, + mgmt_trxn_cfg_commit_timedout, trxn, + MGMTD_TRXN_CFG_COMMIT_MAX_DELAY_MSEC, + &trxn->comm_cfg_timeout); + assert(trxn->comm_cfg_timeout); + break; + case MGMTD_TRXN_CLEANUP: + tv.tv_usec = MGMTD_TRXN_CLEANUP_DELAY_USEC; + thread_add_timer_tv(mgmt_trxn_tm, mgmt_trxn_cleanup, trxn, &tv, + &trxn->clnup); + assert(trxn->clnup); + } +} + +int mgmt_trxn_init(struct mgmt_master *mm, struct thread_master *tm) +{ + if (mgmt_trxn_mm || mgmt_trxn_tm) + assert(!"MGMTD TRXN: Call trxn_init() only once"); + + mgmt_trxn_mm = mm; + mgmt_trxn_tm = tm; + mgmt_trxn_list_init(&mm->trxn_list); + mgmt_trxn_hash_init(); + assert(!mm->cfg_trxn); + mm->cfg_trxn = NULL; + + return 0; +} + +void mgmt_trxn_destroy(void) +{ + mgmt_trxn_cleanup_all_trxns(); + mgmt_trxn_hash_destroy(); +} + +uint64_t mgmt_config_trxn_in_progress(void) +{ + if (mgmt_trxn_mm && mgmt_trxn_mm->cfg_trxn) + return mgmt_trxn_mm->cfg_trxn->session_id; + + return MGMTD_SESSION_ID_NONE; +} + +uint64_t mgmt_create_trxn(uint64_t session_id, enum mgmt_trxn_type type) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = mgmt_trxn_create_new(session_id, type); + return trxn ? trxn->trxn_id : MGMTD_TRXN_ID_NONE; +} + +bool mgmt_trxn_id_is_valid(uint64_t trxn_id) +{ + return mgmt_trxn_id2ctxt(trxn_id) ? true : false; +} + +void mgmt_destroy_trxn(uint64_t *trxn_id) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = mgmt_trxn_id2ctxt(*trxn_id); + if (!trxn) + return; + + mgmt_trxn_delete(&trxn); + *trxn_id = MGMTD_TRXN_ID_NONE; +} + +enum mgmt_trxn_type mgmt_get_trxn_type(uint64_t trxn_id) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return MGMTD_TRXN_TYPE_NONE; + + return trxn->type; +} + +int mgmt_trxn_send_set_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + Mgmtd__YangCfgDataReq **cfg_req, + size_t num_req, bool implicit_commit, + Mgmtd__DatabaseId dst_db_id, + struct mgmt_db_ctxt *dst_db_ctxt) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + size_t indx; + uint16_t *num_chgs; + struct nb_cfg_change *cfg_chg; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return -1; + + if (implicit_commit && mgmt_trxn_req_list_count(&trxn->set_cfg_reqs)) { + MGMTD_TRXN_ERR( + "For implicit commit config only one SETCFG-REQ can be allowed!"); + return -1; + } + + trxn_req = mgmt_trxn_req_alloc(trxn, req_id, MGMTD_TRXN_PROC_SETCFG); + trxn_req->req.set_cfg->db_id = db_id; + trxn_req->req.set_cfg->db_ctxt = db_ctxt; + num_chgs = &trxn_req->req.set_cfg->num_cfg_changes; + for (indx = 0; indx < num_req; indx++) { + cfg_chg = &trxn_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_db_find_data_node_by_xpath( + db_ctxt, cfg_req[indx]->data->xpath) + ? NB_OP_MODIFY + : NB_OP_CREATE; + else + continue; + + MGMTD_TRXN_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_TRXN_DBG("Allocated value at %p ==> '%s'", + cfg_chg->value, cfg_chg->value); + + (*num_chgs)++; + } + trxn_req->req.set_cfg->implicit_commit = implicit_commit; + trxn_req->req.set_cfg->dst_db_id = dst_db_id; + trxn_req->req.set_cfg->dst_db_ctxt = dst_db_ctxt; + trxn_req->req.set_cfg->setcfg_stats = + mgmt_frntnd_get_sessn_setcfg_stats(trxn->session_id); + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_SETCFG); + + return 0; +} + +int mgmt_trxn_send_commit_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId src_db_id, + struct mgmt_db_ctxt *src_db_ctxt, + Mgmtd__DatabaseId dst_db_id, + struct mgmt_db_ctxt *dst_db_ctxt, + bool validate_only, bool abort, + bool implicit) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return -1; + + if (trxn->commit_cfg_req) { + MGMTD_TRXN_ERR( + "A commit is already in-progress for Trxn %p, session 0x%llx. Cannot start another!", + trxn, (unsigned long long)trxn->session_id); + return -1; + } + + trxn_req = mgmt_trxn_req_alloc(trxn, req_id, MGMTD_TRXN_PROC_COMMITCFG); + trxn_req->req.commit_cfg.src_db_id = src_db_id; + trxn_req->req.commit_cfg.src_db_ctxt = src_db_ctxt; + trxn_req->req.commit_cfg.dst_db_id = dst_db_id; + trxn_req->req.commit_cfg.dst_db_ctxt = dst_db_ctxt; + trxn_req->req.commit_cfg.validate_only = validate_only; + trxn_req->req.commit_cfg.abort = abort; + trxn_req->req.commit_cfg.implicit = implicit; + trxn_req->req.commit_cfg.cmt_stats = + mgmt_frntnd_get_sessn_commit_stats(trxn->session_id); + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_COMMITCFG); + return 0; +} + +int mgmt_trxn_notify_bcknd_adapter_conn(struct mgmt_bcknd_client_adapter *adptr, + bool connect) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + struct mgmt_commit_cfg_req *cmtcfg_req; + static struct mgmt_commit_stats dummy_stats; + struct nb_config_cbs *adptr_cfgs = NULL; + + memset(&dummy_stats, 0, sizeof(dummy_stats)); + if (connect) { + /* Get config for this single backend client */ + mgmt_bcknd_get_adapter_config(adptr, mm->running_db, + &adptr_cfgs); + + if (!adptr_cfgs || RB_EMPTY(nb_config_cbs, adptr_cfgs)) { + SET_FLAG(adptr->flags, + MGMTD_BCKND_ADPTR_FLAGS_CFG_SYNCED); + return 0; + } + + /* + * Create a CONFIG transaction to push the config changes + * provided to the backend client. + */ + trxn = mgmt_trxn_create_new(0, MGMTD_TRXN_TYPE_CONFIG); + if (!trxn) { + MGMTD_TRXN_ERR( + "Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", + adptr->name); + return -1; + } + + /* + * Set the changeset for transaction to commit and trigger the + * commit request. + */ + trxn_req = + mgmt_trxn_req_alloc(trxn, 0, MGMTD_TRXN_PROC_COMMITCFG); + trxn_req->req.commit_cfg.src_db_id = MGMTD_DB_NONE; + trxn_req->req.commit_cfg.src_db_ctxt = 0; + trxn_req->req.commit_cfg.dst_db_id = MGMTD_DB_NONE; + trxn_req->req.commit_cfg.dst_db_ctxt = 0; + trxn_req->req.commit_cfg.validate_only = false; + trxn_req->req.commit_cfg.abort = false; + trxn_req->req.commit_cfg.cmt_stats = &dummy_stats; + trxn_req->req.commit_cfg.cfg_chgs = adptr_cfgs; + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_COMMITCFG); + + } else { + /* + * Check if any transaction is currently on-going that + * involves this backend client. If so, report the transaction + * has failed. + */ + FOREACH_TRXN_IN_LIST (mgmt_trxn_mm, trxn) { + if (trxn->type == MGMTD_TRXN_TYPE_CONFIG) { + cmtcfg_req = trxn->commit_cfg_req + ? &trxn->commit_cfg_req + ->req.commit_cfg + : NULL; + if (cmtcfg_req + && cmtcfg_req->subscr_info + .xpath_subscr[adptr->id] + .subscribed) { + mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Backend daemon disconnected while processing commit!"); + } + } + } + } + + return 0; +} + +int mgmt_trxn_notify_bcknd_trxn_reply(uint64_t trxn_id, bool create, + bool success, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn || trxn->type != MGMTD_TRXN_TYPE_CONFIG) + return -1; + + if (!create && !trxn->commit_cfg_req) + return 0; + + assert(trxn->commit_cfg_req); + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + if (create) { + if (success) { + /* + * Done with TRXN_CREATE. Move the backend client to + * next phase. + */ + assert(cmtcfg_req->curr_phase + == MGMTD_COMMIT_PHASE_TRXN_CREATE); + + /* + * Send CFGDATA_CREATE-REQs to the backend immediately. + */ + mgmt_trxn_send_bcknd_cfg_data(trxn, adptr); + } else { + mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Internal error! Failed to initiate transaction at backend!"); + } + } else { + /* + * Done with TRXN_DELETE. Move the backend client to next phase. + */ + if (false) + mgmt_move_bcknd_commit_to_next_phase(trxn, adptr); + } + + return 0; +} + +int mgmt_trxn_notify_bcknd_cfgdata_reply( + uint64_t trxn_id, uint64_t batch_id, bool success, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn || trxn->type != MGMTD_TRXN_TYPE_CONFIG) + return -1; + + if (!trxn->commit_cfg_req) + return -1; + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + + cfg_btch = mgmt_trxn_cfgbatch_id2ctxt(trxn, batch_id); + if (!cfg_btch || cfg_btch->trxn != trxn) + return -1; + + if (!success) { + MGMTD_TRXN_ERR( + "CFGDATA_CREATE_REQ sent to '%s' failed for Trxn %p, Batch %p, Err: %s", + adptr->name, trxn, cfg_btch, + error_if_any ? error_if_any : "None"); + mgmt_trxn_send_commit_cfg_reply( + trxn, MGMTD_INTERNAL_ERROR, + "Internal error! Failed to download config data to backend!"); + return 0; + } + + MGMTD_TRXN_DBG( + "CFGDATA_CREATE_REQ sent to '%s' was successful for Trxn %p, Batch %p, Err: %s", + adptr->name, trxn, cfg_btch, + error_if_any ? error_if_any : "None"); + mgmt_move_trxn_cfg_batch_to_next( + cmtcfg_req, cfg_btch, &cmtcfg_req->curr_batches[adptr->id], + &cmtcfg_req->next_batches[adptr->id], true, +#ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED + MGMTD_COMMIT_PHASE_VALIDATE_CFG); +#else /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + MGMTD_COMMIT_PHASE_APPLY_CFG); +#endif /* ifndef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + + mgmt_try_move_commit_to_next_phase(trxn, cmtcfg_req); + + return 0; +} + +int mgmt_trxn_notify_bcknd_cfg_validate_reply( + uint64_t trxn_id, bool success, uint64_t batch_ids[], + size_t num_batch_ids, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + size_t indx; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn || trxn->type != MGMTD_TRXN_TYPE_CONFIG) + return -1; + + assert(trxn->commit_cfg_req); + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + + if (!success) { + MGMTD_TRXN_ERR( + "CFGDATA_VALIDATE_REQ sent to '%s' failed for Trxn %p, Batches [0x%llx - 0x%llx], Err: %s", + adptr->name, trxn, (unsigned long long)batch_ids[0], + (unsigned long long)batch_ids[num_batch_ids - 1], + error_if_any ? error_if_any : "None"); + mgmt_trxn_send_commit_cfg_reply( + trxn, 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_trxn_cfgbatch_id2ctxt(trxn, batch_ids[indx]); + if (cfg_btch->trxn != trxn) + return -1; + mgmt_move_trxn_cfg_batch_to_next( + cmtcfg_req, cfg_btch, + &cmtcfg_req->curr_batches[adptr->id], + &cmtcfg_req->next_batches[adptr->id], true, + MGMTD_COMMIT_PHASE_APPLY_CFG); + } + + mgmt_try_move_commit_to_next_phase(trxn, cmtcfg_req); + + return 0; +} + +extern int +mgmt_trxn_notify_bcknd_cfg_apply_reply(uint64_t trxn_id, bool success, + uint64_t batch_ids[], + size_t num_batch_ids, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_bcknd_cfg_batch *cfg_btch; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + size_t indx; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn || trxn->type != MGMTD_TRXN_TYPE_CONFIG + || !trxn->commit_cfg_req) + return -1; + + cmtcfg_req = &trxn->commit_cfg_req->req.commit_cfg; + + if (!success) { + MGMTD_TRXN_ERR( + "CFGDATA_APPLY_REQ sent to '%s' failed for Trxn %p, Batches [0x%llx - 0x%llx], Err: %s", + adptr->name, trxn, (unsigned long long)batch_ids[0], + (unsigned long long)batch_ids[num_batch_ids - 1], + error_if_any ? error_if_any : "None"); + mgmt_trxn_send_commit_cfg_reply( + trxn, 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_trxn_cfgbatch_id2ctxt(trxn, batch_ids[indx]); + if (cfg_btch->trxn != trxn) + return -1; + mgmt_move_trxn_cfg_batch_to_next( + cmtcfg_req, cfg_btch, + &cmtcfg_req->curr_batches[adptr->id], + &cmtcfg_req->next_batches[adptr->id], true, + MGMTD_COMMIT_PHASE_TRXN_DELETE); + } + + if (!mgmt_trxn_batch_list_count(&cmtcfg_req->curr_batches[adptr->id])) { + /* + * All configuration for the specific backend has been applied. + * Send TRXN-DELETE to wrap up the transaction for this backend. + */ + SET_FLAG(adptr->flags, MGMTD_BCKND_ADPTR_FLAGS_CFG_SYNCED); + mgmt_trxn_send_bcknd_trxn_delete(trxn, adptr); + } + + mgmt_try_move_commit_to_next_phase(trxn, cmtcfg_req); + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL); + + return 0; +} + +int mgmt_trxn_send_commit_config_reply(uint64_t trxn_id, + enum mgmt_result result, + const char *error_if_any) +{ + struct mgmt_trxn_ctxt *trxn; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return -1; + + if (!trxn->commit_cfg_req) { + MGMTD_TRXN_ERR( + "NO commit in-progress for Trxn %p, session 0x%llx!", + trxn, (unsigned long long)trxn->session_id); + return -1; + } + + return mgmt_trxn_send_commit_cfg_reply(trxn, result, error_if_any); +} + +int mgmt_trxn_send_get_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + Mgmtd__YangGetDataReq **data_req, + size_t num_reqs) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + size_t indx; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return -1; + + trxn_req = mgmt_trxn_req_alloc(trxn, req_id, MGMTD_TRXN_PROC_GETCFG); + trxn_req->req.get_data->db_id = db_id; + trxn_req->req.get_data->db_ctxt = db_ctxt; + for (indx = 0; + indx < num_reqs && indx < MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH; + indx++) { + MGMTD_TRXN_DBG("XPath: '%s'", data_req[indx]->data->xpath); + trxn_req->req.get_data->xpaths[indx] = + strdup(data_req[indx]->data->xpath); + trxn_req->req.get_data->num_xpaths++; + } + + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_GETCFG); + + return 0; +} + +int mgmt_trxn_send_get_data_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + Mgmtd__YangGetDataReq **data_req, + size_t num_reqs) +{ + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_req; + size_t indx; + + trxn = mgmt_trxn_id2ctxt(trxn_id); + if (!trxn) + return -1; + + trxn_req = mgmt_trxn_req_alloc(trxn, req_id, MGMTD_TRXN_PROC_GETDATA); + trxn_req->req.get_data->db_id = db_id; + trxn_req->req.get_data->db_ctxt = db_ctxt; + for (indx = 0; + indx < num_reqs && indx < MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH; + indx++) { + MGMTD_TRXN_DBG("XPath: '%s'", data_req[indx]->data->xpath); + trxn_req->req.get_data->xpaths[indx] = + strdup(data_req[indx]->data->xpath); + trxn_req->req.get_data->num_xpaths++; + } + + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_GETDATA); + + return 0; +} + +void mgmt_trxn_status_write(struct vty *vty) +{ + struct mgmt_trxn_ctxt *trxn; + + vty_out(vty, "MGMTD Transactions\n"); + + FOREACH_TRXN_IN_LIST (mgmt_trxn_mm, trxn) { + vty_out(vty, " Trxn: \t\t\t%p\n", trxn); + vty_out(vty, " Trxn-Id: \t\t\t%llu\n", + (unsigned long long)trxn->trxn_id); + vty_out(vty, " Session-Id: \t\t%llu\n", + (unsigned long long)trxn->session_id); + vty_out(vty, " Type: \t\t\t%s\n", + mgmt_trxn_type2str(trxn->type)); + vty_out(vty, " Ref-Count: \t\t\t%d\n", trxn->refcount); + } + vty_out(vty, " Total: %d\n", + (int)mgmt_trxn_list_count(&mgmt_trxn_mm->trxn_list)); +} + +int mgmt_trxn_rollback_trigger_cfg_apply(struct mgmt_db_ctxt *src_db_ctxt, + struct mgmt_db_ctxt *dst_db_ctxt) +{ + static struct nb_config_cbs changes; + struct nb_config_cbs *cfg_chgs = NULL; + struct mgmt_trxn_ctxt *trxn; + struct mgmt_trxn_req *trxn_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 DB from a file. Get the + * diff from a full comparison of the candidate and + * running DBs. + */ + nb_config_diff(mgmt_db_get_nb_config(dst_db_ctxt), + mgmt_db_get_nb_config(src_db_ctxt), &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. + */ + trxn = mgmt_trxn_create_new(0, MGMTD_TRXN_TYPE_CONFIG); + if (!trxn) { + MGMTD_TRXN_ERR( + "Failed to create CONFIG Transaction for downloading CONFIGs"); + return -1; + } + + /* + * Set the changeset for transaction to commit and trigger the commit + * request. + */ + trxn_req = mgmt_trxn_req_alloc(trxn, 0, MGMTD_TRXN_PROC_COMMITCFG); + trxn_req->req.commit_cfg.src_db_id = MGMTD_DB_CANDIDATE; + trxn_req->req.commit_cfg.src_db_ctxt = src_db_ctxt; + trxn_req->req.commit_cfg.dst_db_id = MGMTD_DB_RUNNING; + trxn_req->req.commit_cfg.dst_db_ctxt = dst_db_ctxt; + trxn_req->req.commit_cfg.validate_only = false; + trxn_req->req.commit_cfg.abort = false; + trxn_req->req.commit_cfg.rollback = true; + trxn_req->req.commit_cfg.cmt_stats = &dummy_stats; + trxn_req->req.commit_cfg.cfg_chgs = cfg_chgs; + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_trxn_register_event(trxn, MGMTD_TRXN_PROC_COMMITCFG); + return 0; +} diff --git a/mgmtd/mgmt_trxn.h b/mgmtd/mgmt_trxn.h new file mode 100644 index 0000000000..50ea5d8ffe --- /dev/null +++ b/mgmtd/mgmt_trxn.h @@ -0,0 +1,279 @@ +/* + * MGMTD Transactions + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_MGMTD_TRXN_H_ +#define _FRR_MGMTD_TRXN_H_ + +#include "mgmtd/mgmt_bcknd_adapter.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_db.h" + +#define MGMTD_TRXN_PROC_DELAY_MSEC 5 +#define MGMTD_TRXN_PROC_DELAY_USEC 10 +#define MGMTD_TRXN_MAX_NUM_SETCFG_PROC 128 +#define MGMTD_TRXN_MAX_NUM_GETCFG_PROC 128 +#define MGMTD_TRXN_MAX_NUM_GETDATA_PROC 128 + +#define MGMTD_TRXN_SEND_CFGVALIDATE_DELAY_MSEC 100 +#define MGMTD_TRXN_SEND_CFGAPPLY_DELAY_MSEC 100 +#define MGMTD_TRXN_CFG_COMMIT_MAX_DELAY_MSEC 30000 /* 30 seconds */ + +#define MGMTD_TRXN_CLEANUP_DELAY_MSEC 100 +#define MGMTD_TRXN_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_trxn_list); + +struct mgmt_master; + +enum mgmt_trxn_type { + MGMTD_TRXN_TYPE_NONE = 0, + MGMTD_TRXN_TYPE_CONFIG, + MGMTD_TRXN_TYPE_SHOW +}; + +static inline const char *mgmt_trxn_type2str(enum mgmt_trxn_type type) +{ + switch (type) { + case MGMTD_TRXN_TYPE_NONE: + return "None"; + case MGMTD_TRXN_TYPE_CONFIG: + return "CONFIG"; + case MGMTD_TRXN_TYPE_SHOW: + return "SHOW"; + } + + return "Unknown"; +} + +/* Initialise transaction module. */ +extern int mgmt_trxn_init(struct mgmt_master *cm, struct thread_master *tm); + +/* Destroy the transaction module. */ +extern void mgmt_trxn_destroy(void); + +/* + * Check if transaction is in progress. + * + * Returns: + * session ID if in-progress, MGMTD_SESSION_ID_NONE otherwise. + */ +extern uint64_t mgmt_config_trxn_in_progress(void); + +/* + * Create transaction. + * + * session_id + * Session ID. + * + * type + * Transaction type (CONFIG/SHOW/NONE) + * + * Returns: + * transaction ID. + */ +extern uint64_t mgmt_create_trxn(uint64_t session_id, enum mgmt_trxn_type type); + +/* + * Destroy transaction. + * + * trxn_id + * Unique transaction identifier. + */ +extern void mgmt_destroy_trxn(uint64_t *trxn_id); + +/* + * Check if transaction is valid given an ID. + */ +extern bool mgmt_trxn_id_is_valid(uint64_t trxn_id); + +/* + * Returns the type of transaction given an ID. + */ +extern enum mgmt_trxn_type mgmt_get_trxn_type(uint64_t trxn_id); + +/* + * Send set-config request to be processed later in transaction. + * + * trxn_id + * Unique transaction identifier. + * + * req_id + * Unique transaction request identifier. + * + * db_id + * Database ID. + * + * db_hndl + * Database handle. + * + * cfg_req + * Config requests. + * + * num_req + * Number of config requests. + * + * implicit_commit + * TRUE if the commit is implicit, FALSE otherwise. + * + * dst_db_id + * Destination database ID. + * + * dst_db_handle + * Destination database handle. + * + * Returns: + * 0 on success, -1 on failures. + */ +extern int mgmt_trxn_send_set_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + Mgmtd__YangCfgDataReq **cfg_req, + size_t num_req, bool implicit_commit, + Mgmtd__DatabaseId dst_db_id, + struct mgmt_db_ctxt *dst_db_ctxt); + +/* + * Send commit-config request to be processed later in transaction. + * + * trxn_id + * Unique transaction identifier. + * + * req_id + * Unique transaction request identifier. + * + * src_db_id + * Source database ID. + * + * src_db_hndl + * Source Database handle. + * + * validate_only + * TRUE if commit request needs to be validated only, FALSE otherwise. + * + * abort + * TRUE if need to restore Src DB back to Dest DB, FALSE otherwise. + * + * implicit + * TRUE if the commit is implicit, FALSE otherwise. + * + * Returns: + * 0 on success, -1 on failures. + */ +extern int mgmt_trxn_send_commit_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId src_db_id, + struct mgmt_db_ctxt *dst_db_ctxt, + Mgmtd__DatabaseId dst_db_id, + struct mgmt_db_ctxt *src_db_ctxt, + bool validate_only, bool abort, + bool implicit); + +extern int mgmt_trxn_send_commit_config_reply(uint64_t trxn_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_trxn_send_get_config_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + 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_trxn_send_get_data_req(uint64_t trxn_id, uint64_t req_id, + Mgmtd__DatabaseId db_id, + struct mgmt_db_ctxt *db_ctxt, + Mgmtd__YangGetDataReq **data_req, + size_t num_reqs); + +/* + * Notifiy backend adapter on connection. + */ +extern int +mgmt_trxn_notify_bcknd_adapter_conn(struct mgmt_bcknd_client_adapter *adptr, + bool connect); + +/* + * Reply to backend adapter about transaction create/delete. + */ +extern int +mgmt_trxn_notify_bcknd_trxn_reply(uint64_t trxn_id, bool create, bool success, + struct mgmt_bcknd_client_adapter *adptr); + +/* + * Reply to backend adapater with config data create request. + */ +extern int +mgmt_trxn_notify_bcknd_cfgdata_reply(uint64_t trxn_id, uint64_t batch_id, + bool success, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr); + +/* + * Reply to backend adapater with config data validate request. + */ +extern int mgmt_trxn_notify_bcknd_cfg_validate_reply( + uint64_t trxn_id, bool success, uint64_t batch_ids[], + size_t num_batch_ids, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr); + +/* + * Reply to backend adapater with config data apply request. + */ +extern int +mgmt_trxn_notify_bcknd_cfg_apply_reply(uint64_t trxn_id, bool success, + uint64_t batch_ids[], + size_t num_batch_ids, char *error_if_any, + struct mgmt_bcknd_client_adapter *adptr); + +/* + * Dump transaction status to vty. + */ +extern void mgmt_trxn_status_write(struct vty *vty); + +/* + * Trigger rollback config apply. + * + * Creates a new transaction and commit request for rollback. + */ +extern int +mgmt_trxn_rollback_trigger_cfg_apply(struct mgmt_db_ctxt *src_db_ctxt, + struct mgmt_db_ctxt *dst_db_ctxt); +#endif /* _FRR_MGMTD_TRXN_H_ */ diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 03d2981a88..b15a597a65 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -34,6 +34,65 @@ #include "mgmtd/mgmt_vty_clippy.c" #endif +/* + * mgmt_enqueue_nb_command + * + * Add a config command from VTYSH for further processing. + * + * NOTE: This function is ALWAYS called from one of the + * command handlers installed on MGMTD daemon that is invoked + * by lib/vty.c on receiving a command from VTYSH. + */ +void mgmt_enqueue_vty_nb_command(struct vty *vty, const char *xpath, + enum nb_operation operation, const char *value) +{ + switch (operation) { + case NB_OP_CREATE: + case NB_OP_MODIFY: + case NB_OP_DESTROY: + case NB_OP_MOVE: + case NB_OP_PRE_VALIDATE: + /* Process on MGMTD daemon itself */ + nb_cli_enqueue_change(vty, xpath, operation, value); + break; + case NB_OP_APPLY_FINISH: + case NB_OP_GET_ELEM: + case NB_OP_GET_NEXT: + case NB_OP_GET_KEYS: + case NB_OP_LOOKUP_ENTRY: + case NB_OP_RPC: + /* To be sent to backend for processing */ + break; + } +} + +/* + * mgmt_apply_nb_commands + * + * Apply all config command enqueued from VTYSH so far for further + * processing. + * + * NOTE: This function is ALWAYS called from one of the + * command handlers installed on MGMTD daemon that is invoked + * by lib/vty.c on receiving a command from VTYSH. + */ +int mgmt_apply_vty_nb_commands(struct vty *vty, const char *xpath_base_fmt, ...) +{ + char xpath_base[XPATH_MAXLEN] = {}; + + /* Parse the base XPath format string. */ + if (xpath_base_fmt) { + va_list ap; + + va_start(ap, xpath_base_fmt); + vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap); + va_end(ap); + } + + vty_mgmt_send_config_data(vty); + return 0; +} + DEFPY(show_mgmt_bcknd_adapter, show_mgmt_bcknd_adapter_cmd, "show mgmt backend-adapter all", @@ -86,6 +145,45 @@ DEFPY(show_mgmt_frntnd_adapter_detail, 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_frntnd_adapter_perf_measurement(vty, false); + else + mgmt_frntnd_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_frntnd_adapter_reset_perf_stats(vty); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_trxn, + show_mgmt_trxn_cmd, + "show mgmt transaction all", + SHOW_STR + MGMTD_STR + MGMTD_TRXN_STR + "Display all Transactions\n") +{ + mgmt_trxn_status_write(vty); + + return CMD_SUCCESS; +} + DEFPY(show_mgmt_db_all, show_mgmt_db_all_cmd, "show mgmt database all", @@ -297,8 +395,6 @@ DEFPY(show_mgmt_get_data, return CMD_SUCCESS; } - - DEFPY(show_mgmt_dump_data, show_mgmt_dump_data_cmd, "show mgmt database-contents db-name WORD$dbname [xpath WORD$path] [file WORD$filepath] format WORD$format_str", @@ -471,6 +567,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_db_rollback_by_cmtid(vty, commit); + else + mgmt_db_rollback_commits(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) { if (mgmt_debug_bcknd && mgmt_debug_frntnd && mgmt_debug_db @@ -489,12 +622,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_bcknd, debug_mgmt_bcknd_cmd, @@ -590,6 +717,7 @@ void mgmt_vty_init(void) install_element(VIEW_NODE, &show_mgmt_bcknd_xpath_reg_cmd); install_element(VIEW_NODE, &show_mgmt_frntnd_adapter_cmd); install_element(VIEW_NODE, &show_mgmt_frntnd_adapter_detail_cmd); + install_element(VIEW_NODE, &show_mgmt_trxn_cmd); install_element(VIEW_NODE, &show_mgmt_db_all_cmd); install_element(VIEW_NODE, &show_mgmt_db_runn_cmd); install_element(VIEW_NODE, &show_mgmt_db_cand_cmd); @@ -598,6 +726,7 @@ void mgmt_vty_init(void) 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_apply_cmd); install_element(CONFIG_NODE, &mgmt_commit_abort_cmd); @@ -606,6 +735,7 @@ void mgmt_vty_init(void) 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_bcknd_cmd); install_element(CONFIG_NODE, &debug_mgmt_bcknd_cmd); @@ -618,7 +748,11 @@ void mgmt_vty_init(void) install_element(VIEW_NODE, &debug_mgmt_all_cmd); install_element(CONFIG_NODE, &debug_mgmt_all_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 2b74f62db9..2a5bd8e32d 100644 --- a/mgmtd/subdir.am +++ b/mgmtd/subdir.am @@ -27,6 +27,7 @@ mgmtd_libmgmtd_a_SOURCES = \ mgmtd/mgmt_bcknd_adapter.c \ mgmtd/mgmt_frntnd_server.c \ mgmtd/mgmt_frntnd_adapter.c \ + mgmtd/mgmt_trxn.c \ # end mgmtdheaderdir = $(pkgincludedir)/mgmtd @@ -43,6 +44,7 @@ noinst_HEADERS += \ mgmtd/mgmt_vty.h \ mgmtd/mgmt_frntnd_adapter.h \ mgmtd/mgmt_frntnd_server.h \ + mgmtd/mgmt_trxn.h \ # end sbin_PROGRAMS += mgmtd/mgmtd