1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2021 Vmware, Inc.
4 * Pushpasis Sarkar <spushpasis@vmware.com>
5 * Copyright (c) 2023, LabN Consulting, L.L.C.
13 #include "mgmt_fe_client.h"
14 #include "mgmtd/mgmt.h"
15 #include "mgmtd/mgmt_ds.h"
16 #include "mgmtd/mgmt_history.h"
18 struct mgmt_cmt_info_t
{
19 struct mgmt_cmt_infos_item cmts
;
21 char cmtid_str
[MGMT_SHORT_TIME_MAX_LEN
];
22 char time_str
[MGMT_LONG_TIME_MAX_LEN
];
23 char cmt_json_file
[PATH_MAX
];
27 DECLARE_DLIST(mgmt_cmt_infos
, struct mgmt_cmt_info_t
, cmts
);
29 #define FOREACH_CMT_REC(mm, cmt_info) \
30 frr_each_safe (mgmt_cmt_infos, &mm->cmts, cmt_info)
33 * The only instance of VTY session that has triggered an ongoing
34 * config rollback operation.
36 static struct vty
*rollback_vty
;
38 static bool file_exists(const char *path
)
40 return !access(path
, F_OK
);
43 static void remove_file(const char *path
)
45 if (!file_exists(path
))
48 zlog_err("Failed to remove commit history file %s: %s", path
,
49 safe_strerror(errno
));
52 static struct mgmt_cmt_info_t
*mgmt_history_new_cmt_info(void)
54 struct mgmt_cmt_info_t
*new;
58 new = XCALLOC(MTYPE_MGMTD_CMT_INFO
, sizeof(struct mgmt_cmt_info_t
));
60 clock_gettime(CLOCK_REALTIME
, &tv
);
61 localtime_r(&tv
.tv_sec
, &tm
);
63 mgmt_time_to_string(&tv
, true, new->time_str
, sizeof(new->time_str
));
64 mgmt_time_to_string(&tv
, false, new->cmtid_str
, sizeof(new->cmtid_str
));
65 snprintf(new->cmt_json_file
, sizeof(new->cmt_json_file
),
66 MGMTD_COMMIT_FILE_PATH
, new->cmtid_str
);
71 static struct mgmt_cmt_info_t
*mgmt_history_create_cmt_rec(void)
73 struct mgmt_cmt_info_t
*new = mgmt_history_new_cmt_info();
74 struct mgmt_cmt_info_t
*cmt_info
;
75 struct mgmt_cmt_info_t
*last_cmt_info
= NULL
;
77 if (mgmt_cmt_infos_count(&mm
->cmts
) == MGMTD_MAX_COMMIT_LIST
) {
78 FOREACH_CMT_REC (mm
, cmt_info
)
79 last_cmt_info
= cmt_info
;
82 remove_file(last_cmt_info
->cmt_json_file
);
83 mgmt_cmt_infos_del(&mm
->cmts
, last_cmt_info
);
84 XFREE(MTYPE_MGMTD_CMT_INFO
, last_cmt_info
);
88 mgmt_cmt_infos_add_head(&mm
->cmts
, new);
92 static struct mgmt_cmt_info_t
*
93 mgmt_history_find_cmt_record(const char *cmtid_str
)
95 struct mgmt_cmt_info_t
*cmt_info
;
97 FOREACH_CMT_REC (mm
, cmt_info
) {
98 if (strcmp(cmt_info
->cmtid_str
, cmtid_str
) == 0)
105 static bool mgmt_history_read_cmt_record_index(void)
108 struct mgmt_cmt_info_t cmt_info
;
109 struct mgmt_cmt_info_t
*new;
112 if (!file_exists(MGMTD_COMMIT_FILE_PATH
))
115 fp
= fopen(MGMTD_COMMIT_INDEX_FILE_NAME
, "rb");
117 zlog_err("Failed to open commit history %s for reading: %s",
118 MGMTD_COMMIT_INDEX_FILE_NAME
, safe_strerror(errno
));
122 while ((fread(&cmt_info
, sizeof(cmt_info
), 1, fp
)) > 0) {
123 if (cnt
< MGMTD_MAX_COMMIT_LIST
) {
124 if (!file_exists(cmt_info
.cmt_json_file
)) {
125 zlog_err("Commit in index, but file %s missing",
126 cmt_info
.cmt_json_file
);
130 new = XCALLOC(MTYPE_MGMTD_CMT_INFO
,
131 sizeof(struct mgmt_cmt_info_t
));
132 memcpy(new, &cmt_info
, sizeof(struct mgmt_cmt_info_t
));
133 mgmt_cmt_infos_add_tail(&mm
->cmts
, new);
136 "More records found in commit history file %s than expected",
137 MGMTD_COMMIT_INDEX_FILE_NAME
);
149 static bool mgmt_history_dump_cmt_record_index(void)
153 struct mgmt_cmt_info_t
*cmt_info
;
154 struct mgmt_cmt_info_t cmt_info_set
[10];
157 fp
= fopen(MGMTD_COMMIT_INDEX_FILE_NAME
, "wb");
159 zlog_err("Failed to open commit history %s for writing: %s",
160 MGMTD_COMMIT_INDEX_FILE_NAME
, safe_strerror(errno
));
164 FOREACH_CMT_REC (mm
, cmt_info
) {
165 memcpy(&cmt_info_set
[cnt
], cmt_info
,
166 sizeof(struct mgmt_cmt_info_t
));
175 ret
= fwrite(&cmt_info_set
, sizeof(struct mgmt_cmt_info_t
), cnt
, fp
);
178 zlog_err("Failed to write full commit history, removing file");
179 remove_file(MGMTD_COMMIT_INDEX_FILE_NAME
);
185 static int mgmt_history_rollback_to_cmt(struct vty
*vty
,
186 struct mgmt_cmt_info_t
*cmt_info
,
189 struct mgmt_ds_ctx
*src_ds_ctx
;
190 struct mgmt_ds_ctx
*dst_ds_ctx
;
194 vty_out(vty
, "ERROR: Rollback already in progress!\n");
198 src_ds_ctx
= mgmt_ds_get_ctx_by_id(mm
, MGMTD_DS_CANDIDATE
);
200 vty_out(vty
, "ERROR: Couldnot access Candidate datastore!\n");
205 * Note: Write lock on src_ds is not required. This is already
206 * taken in 'conf te'.
208 dst_ds_ctx
= mgmt_ds_get_ctx_by_id(mm
, MGMTD_DS_RUNNING
);
210 vty_out(vty
, "ERROR: Couldnot access Running datastore!\n");
214 ret
= mgmt_ds_write_lock(dst_ds_ctx
);
217 "Failed to lock the DS %u for rollback Reason: %s!\n",
218 MGMTD_DS_RUNNING
, strerror(ret
));
222 if (!skip_file_load
) {
223 ret
= mgmt_ds_load_config_from_file(
224 src_ds_ctx
, cmt_info
->cmt_json_file
, false);
226 mgmt_ds_unlock(dst_ds_ctx
);
228 "Error with parsing the file with error code %d\n",
234 /* Internally trigger a commit-request. */
235 ret
= mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx
, dst_ds_ctx
);
237 mgmt_ds_unlock(dst_ds_ctx
);
239 "Error with creating commit apply txn with error code %d\n",
244 mgmt_history_dump_cmt_record_index();
247 * Block the rollback command from returning till the rollback
248 * is completed. On rollback completion mgmt_history_rollback_complete()
249 * shall be called to resume the rollback command return to VTYSH.
251 vty
->mgmt_req_pending
= true;
256 void mgmt_history_rollback_complete(bool success
)
258 vty_mgmt_resume_response(rollback_vty
, success
);
262 int mgmt_history_rollback_by_id(struct vty
*vty
, const char *cmtid_str
)
265 struct mgmt_cmt_info_t
*cmt_info
;
267 if (!mgmt_cmt_infos_count(&mm
->cmts
) ||
268 !mgmt_history_find_cmt_record(cmtid_str
)) {
269 vty_out(vty
, "Invalid commit Id\n");
273 FOREACH_CMT_REC (mm
, cmt_info
) {
274 if (strcmp(cmt_info
->cmtid_str
, cmtid_str
) == 0) {
275 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
,
280 remove_file(cmt_info
->cmt_json_file
);
281 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
282 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
288 int mgmt_history_rollback_n(struct vty
*vty
, int num_cmts
)
292 struct mgmt_cmt_info_t
*cmt_info
;
298 cmts
= mgmt_cmt_infos_count(&mm
->cmts
);
299 if ((int)cmts
< num_cmts
) {
301 "Number of commits found (%d) less than required to rollback\n",
306 if ((int)cmts
== 1 || (int)cmts
== num_cmts
) {
308 "Number of commits found (%d), Rollback of last commit is not supported\n",
313 FOREACH_CMT_REC (mm
, cmt_info
) {
314 if (cnt
== num_cmts
) {
315 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
,
321 remove_file(cmt_info
->cmt_json_file
);
322 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
323 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
326 if (!mgmt_cmt_infos_count(&mm
->cmts
)) {
327 mgmt_ds_reset_candidate();
328 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
, true);
334 void show_mgmt_cmt_history(struct vty
*vty
)
336 struct mgmt_cmt_info_t
*cmt_info
;
339 vty_out(vty
, "Last 10 commit history:\n");
340 vty_out(vty
, "Slot Commit-ID Commit-Record-Time\n");
341 FOREACH_CMT_REC (mm
, cmt_info
) {
342 vty_out(vty
, "%4d %23s %s\n", slno
, cmt_info
->cmtid_str
,
348 void mgmt_history_new_record(struct mgmt_ds_ctx
*ds_ctx
)
350 struct mgmt_cmt_info_t
*cmt_info
= mgmt_history_create_cmt_rec();
352 mgmt_ds_dump_ds_to_file(cmt_info
->cmt_json_file
, ds_ctx
);
353 mgmt_history_dump_cmt_record_index();
356 void mgmt_history_init(void)
358 /* Create commit record for previously stored commit-apply */
359 mgmt_cmt_infos_init(&mm
->cmts
);
360 mgmt_history_read_cmt_record_index();
363 void mgmt_history_destroy(void)
365 struct mgmt_cmt_info_t
*cmt_info
;
367 FOREACH_CMT_REC(mm
, cmt_info
) {
368 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
369 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
372 mgmt_cmt_infos_fini(&mm
->cmts
);