diff options
Diffstat (limited to 'mgmtd/mgmt_history.c')
-rw-r--r-- | mgmtd/mgmt_history.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c new file mode 100644 index 0000000..d406932 --- /dev/null +++ b/mgmtd/mgmt_history.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar <spushpasis@vmware.com> + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include <zebra.h> +#include "md5.h" +#include "frrevent.h" +#include "xref.h" + +#include "mgmt_fe_client.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_history.h" + +struct mgmt_cmt_info_t { + struct mgmt_cmt_infos_item cmts; + + char cmtid_str[MGMT_SHORT_TIME_MAX_LEN]; + char time_str[MGMT_LONG_TIME_MAX_LEN]; + char cmt_json_file[PATH_MAX]; +}; + + +DECLARE_DLIST(mgmt_cmt_infos, struct mgmt_cmt_info_t, cmts); + +#define FOREACH_CMT_REC(mm, cmt_info) \ + frr_each_safe (mgmt_cmt_infos, &mm->cmts, cmt_info) + +/* + * The only instance of VTY session that has triggered an ongoing + * config rollback operation. + */ +static struct vty *rollback_vty; + +static bool file_exists(const char *path) +{ + return !access(path, F_OK); +} + +static void remove_file(const char *path) +{ + if (!file_exists(path)) + return; + if (unlink(path)) + zlog_err("Failed to remove commit history file %s: %s", path, + safe_strerror(errno)); +} + +static struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void) +{ + struct mgmt_cmt_info_t *new; + struct timespec tv; + struct tm tm; + + new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t)); + + clock_gettime(CLOCK_REALTIME, &tv); + localtime_r(&tv.tv_sec, &tm); + + mgmt_time_to_string(&tv, true, new->time_str, sizeof(new->time_str)); + mgmt_time_to_string(&tv, false, new->cmtid_str, sizeof(new->cmtid_str)); + snprintf(new->cmt_json_file, sizeof(new->cmt_json_file), + MGMTD_COMMIT_FILE_PATH, new->cmtid_str); + + return new; +} + +static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void) +{ + struct mgmt_cmt_info_t *new = mgmt_history_new_cmt_info(); + struct mgmt_cmt_info_t *cmt_info; + struct mgmt_cmt_info_t *last_cmt_info = NULL; + + if (mgmt_cmt_infos_count(&mm->cmts) == MGMTD_MAX_COMMIT_LIST) { + FOREACH_CMT_REC (mm, cmt_info) + last_cmt_info = cmt_info; + + if (last_cmt_info) { + remove_file(last_cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, last_cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info); + } + } + + mgmt_cmt_infos_add_head(&mm->cmts, new); + return new; +} + +static struct mgmt_cmt_info_t * +mgmt_history_find_cmt_record(const char *cmtid_str) +{ + struct mgmt_cmt_info_t *cmt_info; + + FOREACH_CMT_REC (mm, cmt_info) { + if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) + return cmt_info; + } + + return NULL; +} + +static bool mgmt_history_read_cmt_record_index(void) +{ + FILE *fp; + struct mgmt_cmt_info_t cmt_info; + struct mgmt_cmt_info_t *new; + int cnt = 0; + + if (!file_exists(MGMTD_COMMIT_FILE_PATH)) + return false; + + fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb"); + if (!fp) { + zlog_err("Failed to open commit history %s for reading: %s", + MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno)); + return false; + } + + while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) { + if (cnt < MGMTD_MAX_COMMIT_LIST) { + if (!file_exists(cmt_info.cmt_json_file)) { + zlog_err("Commit in index, but file %s missing", + cmt_info.cmt_json_file); + continue; + } + + new = XCALLOC(MTYPE_MGMTD_CMT_INFO, + sizeof(struct mgmt_cmt_info_t)); + memcpy(new, &cmt_info, sizeof(struct mgmt_cmt_info_t)); + mgmt_cmt_infos_add_tail(&mm->cmts, new); + } else { + zlog_warn( + "More records found in commit history file %s than expected", + MGMTD_COMMIT_INDEX_FILE_NAME); + fclose(fp); + return false; + } + + cnt++; + } + + fclose(fp); + return true; +} + +static bool mgmt_history_dump_cmt_record_index(void) +{ + FILE *fp; + int ret = 0; + struct mgmt_cmt_info_t *cmt_info; + struct mgmt_cmt_info_t cmt_info_set[10]; + int cnt = 0; + + fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "wb"); + if (!fp) { + zlog_err("Failed to open commit history %s for writing: %s", + MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno)); + 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("Failed to write full commit history, removing file"); + remove_file(MGMTD_COMMIT_INDEX_FILE_NAME); + return false; + } + return true; +} + +static int mgmt_history_rollback_to_cmt(struct vty *vty, + struct mgmt_cmt_info_t *cmt_info, + bool skip_file_load) +{ + struct mgmt_ds_ctx *src_ds_ctx; + struct mgmt_ds_ctx *dst_ds_ctx; + int ret = 0; + + if (rollback_vty) { + vty_out(vty, "ERROR: Rollback already in progress!\n"); + return -1; + } + + src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE); + dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); + assert(src_ds_ctx); + assert(dst_ds_ctx); + + ret = mgmt_ds_lock(src_ds_ctx, vty->mgmt_session_id); + if (ret != 0) { + vty_out(vty, + "Failed to lock the DS %u for rollback Reason: %s!\n", + MGMTD_DS_RUNNING, strerror(ret)); + return -1; + } + + ret = mgmt_ds_lock(dst_ds_ctx, vty->mgmt_session_id); + if (ret != 0) { + mgmt_ds_unlock(src_ds_ctx); + vty_out(vty, + "Failed to lock the DS %u for rollback Reason: %s!\n", + MGMTD_DS_RUNNING, strerror(ret)); + return -1; + } + + if (!skip_file_load) { + ret = mgmt_ds_load_config_from_file( + src_ds_ctx, cmt_info->cmt_json_file, false); + if (ret != 0) { + vty_out(vty, + "Error with parsing the file with error code %d\n", + ret); + goto failed_unlock; + } + } + + /* Internally trigger a commit-request. */ + ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx); + if (ret != 0) { + vty_out(vty, + "Error with creating commit apply txn with error code %d\n", + ret); + goto failed_unlock; + } + + mgmt_history_dump_cmt_record_index(); + + /* + * TODO: Cleanup: the generic TXN code currently checks for rollback + * and does the unlock when it completes. + */ + + /* + * Block the rollback command from returning till the rollback + * is completed. On rollback completion mgmt_history_rollback_complete() + * shall be called to resume the rollback command return to VTYSH. + */ + vty->mgmt_req_pending_cmd = "ROLLBACK"; + rollback_vty = vty; + return 0; + +failed_unlock: + mgmt_ds_unlock(src_ds_ctx); + mgmt_ds_unlock(dst_ds_ctx); + return ret; +} + +void mgmt_history_rollback_complete(bool success) +{ + vty_mgmt_resume_response(rollback_vty, success); + rollback_vty = NULL; +} + +int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str) +{ + int ret = 0; + struct mgmt_cmt_info_t *cmt_info; + + if (!mgmt_cmt_infos_count(&mm->cmts) || + !mgmt_history_find_cmt_record(cmtid_str)) { + vty_out(vty, "Invalid commit Id\n"); + return -1; + } + + FOREACH_CMT_REC (mm, cmt_info) { + if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) { + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, + false); + return ret; + } + + remove_file(cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + return 0; +} + +int mgmt_history_rollback_n(struct vty *vty, int num_cmts) +{ + int ret = 0; + int cnt = 0; + struct mgmt_cmt_info_t *cmt_info; + size_t cmts; + + if (!num_cmts) + num_cmts = 1; + + cmts = mgmt_cmt_infos_count(&mm->cmts); + if ((int)cmts < num_cmts) { + vty_out(vty, + "Number of commits found (%d) less than required to rollback\n", + (int)cmts); + return -1; + } + + if ((int)cmts == 1 || (int)cmts == num_cmts) { + vty_out(vty, + "Number of commits found (%d), Rollback of last commit is not supported\n", + (int)cmts); + return -1; + } + + FOREACH_CMT_REC (mm, cmt_info) { + if (cnt == num_cmts) { + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, + false); + return ret; + } + + cnt++; + remove_file(cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + if (!mgmt_cmt_infos_count(&mm->cmts)) { + mgmt_ds_reset_candidate(); + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true); + } + + return ret; +} + +void show_mgmt_cmt_history(struct vty *vty) +{ + struct mgmt_cmt_info_t *cmt_info; + int slno = 0; + + vty_out(vty, "Last 10 commit history:\n"); + vty_out(vty, "Slot Commit-ID Commit-Record-Time\n"); + FOREACH_CMT_REC (mm, cmt_info) { + vty_out(vty, "%4d %23s %s\n", slno, cmt_info->cmtid_str, + cmt_info->time_str); + slno++; + } +} + +void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx) +{ + struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec(); + + mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx); + mgmt_history_dump_cmt_record_index(); +} + +void mgmt_history_init(void) +{ + /* Create commit record for previously stored commit-apply */ + mgmt_cmt_infos_init(&mm->cmts); + mgmt_history_read_cmt_record_index(); +} + +void mgmt_history_destroy(void) +{ + struct mgmt_cmt_info_t *cmt_info; + + FOREACH_CMT_REC(mm, cmt_info) { + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + mgmt_cmt_infos_fini(&mm->cmts); +} |