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
[MGMTD_MD5_HASH_STR_HEX_LEN
];
22 char time_str
[MGMTD_COMMIT_TIME_STR_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 mgmt_history_record_exists(char *file_path
)
42 exist
= access(file_path
, F_OK
);
49 static void mgmt_history_remove_file(char *name
)
51 if (remove(name
) == 0)
52 zlog_debug("Old commit info deletion succeeded");
54 zlog_err("Old commit info deletion failed");
57 static void mgmt_history_hash(const char *input_str
, char *hash
)
60 unsigned char digest
[MGMTD_MD5_HASH_LEN
];
63 memset(&ctx
, 0, sizeof(ctx
));
65 MD5Update(&ctx
, input_str
, strlen(input_str
));
66 MD5Final(digest
, &ctx
);
68 for (i
= 0; i
< MGMTD_MD5_HASH_LEN
; i
++)
69 snprintf(&hash
[i
* 2], MGMTD_MD5_HASH_STR_HEX_LEN
, "%02x",
70 (unsigned int)digest
[i
]);
73 static struct mgmt_cmt_info_t
*mgmt_history_create_cmt_rec(void)
75 struct mgmt_cmt_info_t
*new;
76 struct mgmt_cmt_info_t
*cmt_info
;
77 struct mgmt_cmt_info_t
*last_cmt_info
= NULL
;
78 struct timeval cmt_recd_tv
;
80 new = XCALLOC(MTYPE_MGMTD_CMT_INFO
, sizeof(struct mgmt_cmt_info_t
));
81 gettimeofday(&cmt_recd_tv
, NULL
);
82 mgmt_realtime_to_string(&cmt_recd_tv
, new->time_str
,
83 sizeof(new->time_str
));
84 mgmt_history_hash(new->time_str
, new->cmtid_str
);
85 snprintf(new->cmt_json_file
, sizeof(new->cmt_json_file
) - 1,
86 MGMTD_COMMIT_FILE_PATH
, new->cmtid_str
);
88 if (mgmt_cmt_infos_count(&mm
->cmts
) == MGMTD_MAX_COMMIT_LIST
) {
89 FOREACH_CMT_REC (mm
, cmt_info
)
90 last_cmt_info
= cmt_info
;
93 mgmt_history_remove_file(last_cmt_info
->cmt_json_file
);
94 mgmt_cmt_infos_del(&mm
->cmts
, last_cmt_info
);
95 XFREE(MTYPE_MGMTD_CMT_INFO
, last_cmt_info
);
99 mgmt_cmt_infos_add_head(&mm
->cmts
, new);
103 static struct mgmt_cmt_info_t
*
104 mgmt_history_find_cmt_record(const char *cmtid_str
)
106 struct mgmt_cmt_info_t
*cmt_info
;
108 FOREACH_CMT_REC (mm
, cmt_info
) {
109 if (strncmp(cmt_info
->cmtid_str
, cmtid_str
,
110 MGMTD_MD5_HASH_STR_HEX_LEN
) == 0)
117 static bool mgmt_history_read_cmt_record_index(void)
120 struct mgmt_cmt_info_t cmt_info
;
121 struct mgmt_cmt_info_t
*new;
124 fp
= fopen(MGMTD_COMMIT_INDEX_FILE_NAME
, "rb");
126 zlog_err("Failed to open file %s rb mode",
127 MGMTD_COMMIT_INDEX_FILE_NAME
);
131 while ((fread(&cmt_info
, sizeof(cmt_info
), 1, fp
)) > 0) {
132 if (cnt
< MGMTD_MAX_COMMIT_LIST
) {
133 if (!mgmt_history_record_exists(
134 cmt_info
.cmt_json_file
)) {
136 "Commit record present in index_file, but commit file %s missing",
137 cmt_info
.cmt_json_file
);
141 new = XCALLOC(MTYPE_MGMTD_CMT_INFO
,
142 sizeof(struct mgmt_cmt_info_t
));
143 memcpy(new, &cmt_info
, sizeof(struct mgmt_cmt_info_t
));
144 mgmt_cmt_infos_add_tail(&mm
->cmts
, new);
146 zlog_err("More records found in index file %s",
147 MGMTD_COMMIT_INDEX_FILE_NAME
);
159 static bool mgmt_history_dump_cmt_record_index(void)
163 struct mgmt_cmt_info_t
*cmt_info
;
164 struct mgmt_cmt_info_t cmt_info_set
[10];
167 mgmt_history_remove_file((char *)MGMTD_COMMIT_INDEX_FILE_NAME
);
168 fp
= fopen(MGMTD_COMMIT_INDEX_FILE_NAME
, "ab");
170 zlog_err("Failed to open file %s ab mode",
171 MGMTD_COMMIT_INDEX_FILE_NAME
);
175 FOREACH_CMT_REC (mm
, cmt_info
) {
176 memcpy(&cmt_info_set
[cnt
], cmt_info
,
177 sizeof(struct mgmt_cmt_info_t
));
186 ret
= fwrite(&cmt_info_set
, sizeof(struct mgmt_cmt_info_t
), cnt
, fp
);
189 zlog_err("Write record failed");
196 static int mgmt_history_rollback_to_cmt(struct vty
*vty
,
197 struct mgmt_cmt_info_t
*cmt_info
,
200 struct mgmt_ds_ctx
*src_ds_ctx
;
201 struct mgmt_ds_ctx
*dst_ds_ctx
;
205 vty_out(vty
, "ERROR: Rollback already in progress!\n");
209 src_ds_ctx
= mgmt_ds_get_ctx_by_id(mm
, MGMTD_DS_CANDIDATE
);
211 vty_out(vty
, "ERROR: Couldnot access Candidate datastore!\n");
216 * Note: Write lock on src_ds is not required. This is already
217 * taken in 'conf te'.
219 dst_ds_ctx
= mgmt_ds_get_ctx_by_id(mm
, MGMTD_DS_RUNNING
);
221 vty_out(vty
, "ERROR: Couldnot access Running datastore!\n");
225 ret
= mgmt_ds_write_lock(dst_ds_ctx
);
228 "Failed to lock the DS %u for rollback Reason: %s!\n",
229 MGMTD_DS_RUNNING
, strerror(ret
));
233 if (!skip_file_load
) {
234 ret
= mgmt_ds_load_config_from_file(
235 src_ds_ctx
, cmt_info
->cmt_json_file
, false);
237 mgmt_ds_unlock(dst_ds_ctx
);
239 "Error with parsing the file with error code %d\n",
245 /* Internally trigger a commit-request. */
246 ret
= mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx
, dst_ds_ctx
);
248 mgmt_ds_unlock(dst_ds_ctx
);
250 "Error with creating commit apply txn with error code %d\n",
255 mgmt_history_dump_cmt_record_index();
258 * Block the rollback command from returning till the rollback
259 * is completed. On rollback completion mgmt_history_rollback_complete()
260 * shall be called to resume the rollback command return to VTYSH.
262 vty
->mgmt_req_pending
= true;
267 void mgmt_history_rollback_complete(bool success
)
269 vty_mgmt_resume_response(rollback_vty
, success
);
273 int mgmt_history_rollback_by_id(struct vty
*vty
, const char *cmtid_str
)
276 struct mgmt_cmt_info_t
*cmt_info
;
278 if (!mgmt_cmt_infos_count(&mm
->cmts
) ||
279 !mgmt_history_find_cmt_record(cmtid_str
)) {
280 vty_out(vty
, "Invalid commit Id\n");
284 FOREACH_CMT_REC (mm
, cmt_info
) {
285 if (strncmp(cmt_info
->cmtid_str
, cmtid_str
,
286 MGMTD_MD5_HASH_STR_HEX_LEN
) == 0) {
287 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
,
292 mgmt_history_remove_file(cmt_info
->cmt_json_file
);
293 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
294 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
300 int mgmt_history_rollback_n(struct vty
*vty
, int num_cmts
)
304 struct mgmt_cmt_info_t
*cmt_info
;
310 cmts
= mgmt_cmt_infos_count(&mm
->cmts
);
311 if ((int)cmts
< num_cmts
) {
313 "Number of commits found (%d) less than required to rollback\n",
318 if ((int)cmts
== 1 || (int)cmts
== num_cmts
) {
320 "Number of commits found (%d), Rollback of last commit is not supported\n",
325 FOREACH_CMT_REC (mm
, cmt_info
) {
326 if (cnt
== num_cmts
) {
327 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
,
333 mgmt_history_remove_file(cmt_info
->cmt_json_file
);
334 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
335 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
338 if (!mgmt_cmt_infos_count(&mm
->cmts
)) {
339 mgmt_ds_reset_candidate();
340 ret
= mgmt_history_rollback_to_cmt(vty
, cmt_info
, true);
346 void show_mgmt_cmt_history(struct vty
*vty
)
348 struct mgmt_cmt_info_t
*cmt_info
;
351 vty_out(vty
, "Last 10 commit history:\n");
352 vty_out(vty
, " Sl.No\tCommit-ID(HEX)\t\t\t Commit-Record-Time\n");
353 FOREACH_CMT_REC (mm
, cmt_info
) {
354 vty_out(vty
, " %d\t%s %s\n", slno
, cmt_info
->cmtid_str
,
360 void mgmt_history_new_record(struct mgmt_ds_ctx
*ds_ctx
)
362 struct mgmt_cmt_info_t
*cmt_info
= mgmt_history_create_cmt_rec();
364 mgmt_ds_dump_ds_to_file(cmt_info
->cmt_json_file
, ds_ctx
);
365 mgmt_history_dump_cmt_record_index();
368 void mgmt_history_init(void)
370 /* Create commit record for previously stored commit-apply */
371 mgmt_cmt_infos_init(&mm
->cmts
);
372 mgmt_history_read_cmt_record_index();
375 void mgmt_history_destroy(void)
377 struct mgmt_cmt_info_t
*cmt_info
;
379 FOREACH_CMT_REC(mm
, cmt_info
) {
380 mgmt_cmt_infos_del(&mm
->cmts
, cmt_info
);
381 XFREE(MTYPE_MGMTD_CMT_INFO
, cmt_info
);
384 mgmt_cmt_infos_fini(&mm
->cmts
);