]> git.proxmox.com Git - mirror_frr.git/blob - mgmtd/mgmt_history.c
Merge pull request #13177 from mjstapp/fix_ospf_supoort_typo
[mirror_frr.git] / mgmtd / mgmt_history.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2021 Vmware, Inc.
4 * Pushpasis Sarkar <spushpasis@vmware.com>
5 * Copyright (c) 2023, LabN Consulting, L.L.C.
6 */
7
8 #include <zebra.h>
9 #include "md5.h"
10 #include "frrevent.h"
11 #include "xref.h"
12
13 #include "mgmt_fe_client.h"
14 #include "mgmtd/mgmt.h"
15 #include "mgmtd/mgmt_ds.h"
16 #include "mgmtd/mgmt_history.h"
17
18 struct mgmt_cmt_info_t {
19 struct mgmt_cmt_infos_item cmts;
20
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];
24 };
25
26
27 DECLARE_DLIST(mgmt_cmt_infos, struct mgmt_cmt_info_t, cmts);
28
29 #define FOREACH_CMT_REC(mm, cmt_info) \
30 frr_each_safe (mgmt_cmt_infos, &mm->cmts, cmt_info)
31
32 /*
33 * The only instance of VTY session that has triggered an ongoing
34 * config rollback operation.
35 */
36 static struct vty *rollback_vty;
37
38 static bool mgmt_history_record_exists(char *file_path)
39 {
40 int exist;
41
42 exist = access(file_path, F_OK);
43 if (exist == 0)
44 return true;
45 else
46 return false;
47 }
48
49 static void mgmt_history_remove_file(char *name)
50 {
51 if (remove(name) == 0)
52 zlog_debug("Old commit info deletion succeeded");
53 else
54 zlog_err("Old commit info deletion failed");
55 }
56
57 static void mgmt_history_hash(const char *input_str, char *hash)
58 {
59 int i;
60 unsigned char digest[MGMTD_MD5_HASH_LEN];
61 MD5_CTX ctx;
62
63 memset(&ctx, 0, sizeof(ctx));
64 MD5Init(&ctx);
65 MD5Update(&ctx, input_str, strlen(input_str));
66 MD5Final(digest, &ctx);
67
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]);
71 }
72
73 static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
74 {
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;
79
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);
87
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;
91
92 if (last_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);
96 }
97 }
98
99 mgmt_cmt_infos_add_head(&mm->cmts, new);
100 return new;
101 }
102
103 static struct mgmt_cmt_info_t *
104 mgmt_history_find_cmt_record(const char *cmtid_str)
105 {
106 struct mgmt_cmt_info_t *cmt_info;
107
108 FOREACH_CMT_REC (mm, cmt_info) {
109 if (strncmp(cmt_info->cmtid_str, cmtid_str,
110 MGMTD_MD5_HASH_STR_HEX_LEN) == 0)
111 return cmt_info;
112 }
113
114 return NULL;
115 }
116
117 static bool mgmt_history_read_cmt_record_index(void)
118 {
119 FILE *fp;
120 struct mgmt_cmt_info_t cmt_info;
121 struct mgmt_cmt_info_t *new;
122 int cnt = 0;
123
124 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
125 if (!fp) {
126 zlog_err("Failed to open file %s rb mode",
127 MGMTD_COMMIT_INDEX_FILE_NAME);
128 return false;
129 }
130
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)) {
135 zlog_err(
136 "Commit record present in index_file, but commit file %s missing",
137 cmt_info.cmt_json_file);
138 continue;
139 }
140
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);
145 } else {
146 zlog_err("More records found in index file %s",
147 MGMTD_COMMIT_INDEX_FILE_NAME);
148 fclose(fp);
149 return false;
150 }
151
152 cnt++;
153 }
154
155 fclose(fp);
156 return true;
157 }
158
159 static bool mgmt_history_dump_cmt_record_index(void)
160 {
161 FILE *fp;
162 int ret = 0;
163 struct mgmt_cmt_info_t *cmt_info;
164 struct mgmt_cmt_info_t cmt_info_set[10];
165 int cnt = 0;
166
167 mgmt_history_remove_file((char *)MGMTD_COMMIT_INDEX_FILE_NAME);
168 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "ab");
169 if (!fp) {
170 zlog_err("Failed to open file %s ab mode",
171 MGMTD_COMMIT_INDEX_FILE_NAME);
172 return false;
173 }
174
175 FOREACH_CMT_REC (mm, cmt_info) {
176 memcpy(&cmt_info_set[cnt], cmt_info,
177 sizeof(struct mgmt_cmt_info_t));
178 cnt++;
179 }
180
181 if (!cnt) {
182 fclose(fp);
183 return false;
184 }
185
186 ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
187 fclose(fp);
188 if (ret != cnt) {
189 zlog_err("Write record failed");
190 return false;
191 } else {
192 return true;
193 }
194 }
195
196 static int mgmt_history_rollback_to_cmt(struct vty *vty,
197 struct mgmt_cmt_info_t *cmt_info,
198 bool skip_file_load)
199 {
200 struct mgmt_ds_ctx *src_ds_ctx;
201 struct mgmt_ds_ctx *dst_ds_ctx;
202 int ret = 0;
203
204 if (rollback_vty) {
205 vty_out(vty, "ERROR: Rollback already in progress!\n");
206 return -1;
207 }
208
209 src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
210 if (!src_ds_ctx) {
211 vty_out(vty, "ERROR: Couldnot access Candidate datastore!\n");
212 return -1;
213 }
214
215 /*
216 * Note: Write lock on src_ds is not required. This is already
217 * taken in 'conf te'.
218 */
219 dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
220 if (!dst_ds_ctx) {
221 vty_out(vty, "ERROR: Couldnot access Running datastore!\n");
222 return -1;
223 }
224
225 ret = mgmt_ds_write_lock(dst_ds_ctx);
226 if (ret != 0) {
227 vty_out(vty,
228 "Failed to lock the DS %u for rollback Reason: %s!\n",
229 MGMTD_DS_RUNNING, strerror(ret));
230 return -1;
231 }
232
233 if (!skip_file_load) {
234 ret = mgmt_ds_load_config_from_file(
235 src_ds_ctx, cmt_info->cmt_json_file, false);
236 if (ret != 0) {
237 mgmt_ds_unlock(dst_ds_ctx);
238 vty_out(vty,
239 "Error with parsing the file with error code %d\n",
240 ret);
241 return ret;
242 }
243 }
244
245 /* Internally trigger a commit-request. */
246 ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx);
247 if (ret != 0) {
248 mgmt_ds_unlock(dst_ds_ctx);
249 vty_out(vty,
250 "Error with creating commit apply txn with error code %d\n",
251 ret);
252 return ret;
253 }
254
255 mgmt_history_dump_cmt_record_index();
256
257 /*
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.
261 */
262 vty->mgmt_req_pending = true;
263 rollback_vty = vty;
264 return 0;
265 }
266
267 void mgmt_history_rollback_complete(bool success)
268 {
269 vty_mgmt_resume_response(rollback_vty, success);
270 rollback_vty = NULL;
271 }
272
273 int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str)
274 {
275 int ret = 0;
276 struct mgmt_cmt_info_t *cmt_info;
277
278 if (!mgmt_cmt_infos_count(&mm->cmts) ||
279 !mgmt_history_find_cmt_record(cmtid_str)) {
280 vty_out(vty, "Invalid commit Id\n");
281 return -1;
282 }
283
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,
288 false);
289 return ret;
290 }
291
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);
295 }
296
297 return 0;
298 }
299
300 int mgmt_history_rollback_n(struct vty *vty, int num_cmts)
301 {
302 int ret = 0;
303 int cnt = 0;
304 struct mgmt_cmt_info_t *cmt_info;
305 size_t cmts;
306
307 if (!num_cmts)
308 num_cmts = 1;
309
310 cmts = mgmt_cmt_infos_count(&mm->cmts);
311 if ((int)cmts < num_cmts) {
312 vty_out(vty,
313 "Number of commits found (%d) less than required to rollback\n",
314 (int)cmts);
315 return -1;
316 }
317
318 if ((int)cmts == 1 || (int)cmts == num_cmts) {
319 vty_out(vty,
320 "Number of commits found (%d), Rollback of last commit is not supported\n",
321 (int)cmts);
322 return -1;
323 }
324
325 FOREACH_CMT_REC (mm, cmt_info) {
326 if (cnt == num_cmts) {
327 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
328 false);
329 return ret;
330 }
331
332 cnt++;
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);
336 }
337
338 if (!mgmt_cmt_infos_count(&mm->cmts)) {
339 mgmt_ds_reset_candidate();
340 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true);
341 }
342
343 return ret;
344 }
345
346 void show_mgmt_cmt_history(struct vty *vty)
347 {
348 struct mgmt_cmt_info_t *cmt_info;
349 int slno = 0;
350
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,
355 cmt_info->time_str);
356 slno++;
357 }
358 }
359
360 void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
361 {
362 struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
363
364 mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
365 mgmt_history_dump_cmt_record_index();
366 }
367
368 void mgmt_history_init(void)
369 {
370 /* Create commit record for previously stored commit-apply */
371 mgmt_cmt_infos_init(&mm->cmts);
372 mgmt_history_read_cmt_record_index();
373 }
374
375 void mgmt_history_destroy(void)
376 {
377 struct mgmt_cmt_info_t *cmt_info;
378
379 FOREACH_CMT_REC(mm, cmt_info) {
380 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
381 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
382 }
383
384 mgmt_cmt_infos_fini(&mm->cmts);
385 }