]> git.proxmox.com Git - mirror_frr.git/blob - mgmtd/mgmt_history.c
Merge pull request #13435 from mjstapp/fix_pim_cpp_notice
[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[MGMT_SHORT_TIME_MAX_LEN];
22 char time_str[MGMT_LONG_TIME_MAX_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 struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void)
58 {
59 struct mgmt_cmt_info_t *new;
60 struct timespec tv;
61 struct tm tm;
62
63 new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t));
64
65 clock_gettime(CLOCK_REALTIME, &tv);
66 localtime_r(&tv.tv_sec, &tm);
67
68 mgmt_time_to_string(&tv, true, new->time_str, sizeof(new->time_str));
69 mgmt_time_to_string(&tv, false, new->cmtid_str, sizeof(new->cmtid_str));
70 snprintf(new->cmt_json_file, sizeof(new->cmt_json_file),
71 MGMTD_COMMIT_FILE_PATH, new->cmtid_str);
72
73 return new;
74 }
75
76 static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
77 {
78 struct mgmt_cmt_info_t *new = mgmt_history_new_cmt_info();
79 struct mgmt_cmt_info_t *cmt_info;
80 struct mgmt_cmt_info_t *last_cmt_info = NULL;
81
82 if (mgmt_cmt_infos_count(&mm->cmts) == MGMTD_MAX_COMMIT_LIST) {
83 FOREACH_CMT_REC (mm, cmt_info)
84 last_cmt_info = cmt_info;
85
86 if (last_cmt_info) {
87 mgmt_history_remove_file(last_cmt_info->cmt_json_file);
88 mgmt_cmt_infos_del(&mm->cmts, last_cmt_info);
89 XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info);
90 }
91 }
92
93 mgmt_cmt_infos_add_head(&mm->cmts, new);
94 return new;
95 }
96
97 static struct mgmt_cmt_info_t *
98 mgmt_history_find_cmt_record(const char *cmtid_str)
99 {
100 struct mgmt_cmt_info_t *cmt_info;
101
102 FOREACH_CMT_REC (mm, cmt_info) {
103 if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0)
104 return cmt_info;
105 }
106
107 return NULL;
108 }
109
110 static bool mgmt_history_read_cmt_record_index(void)
111 {
112 FILE *fp;
113 struct mgmt_cmt_info_t cmt_info;
114 struct mgmt_cmt_info_t *new;
115 int cnt = 0;
116
117 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
118 if (!fp) {
119 zlog_err("Failed to open file %s rb mode",
120 MGMTD_COMMIT_INDEX_FILE_NAME);
121 return false;
122 }
123
124 while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) {
125 if (cnt < MGMTD_MAX_COMMIT_LIST) {
126 if (!mgmt_history_record_exists(
127 cmt_info.cmt_json_file)) {
128 zlog_err(
129 "Commit record present in index_file, but commit file %s missing",
130 cmt_info.cmt_json_file);
131 continue;
132 }
133
134 new = XCALLOC(MTYPE_MGMTD_CMT_INFO,
135 sizeof(struct mgmt_cmt_info_t));
136 memcpy(new, &cmt_info, sizeof(struct mgmt_cmt_info_t));
137 mgmt_cmt_infos_add_tail(&mm->cmts, new);
138 } else {
139 zlog_err("More records found in index file %s",
140 MGMTD_COMMIT_INDEX_FILE_NAME);
141 fclose(fp);
142 return false;
143 }
144
145 cnt++;
146 }
147
148 fclose(fp);
149 return true;
150 }
151
152 static bool mgmt_history_dump_cmt_record_index(void)
153 {
154 FILE *fp;
155 int ret = 0;
156 struct mgmt_cmt_info_t *cmt_info;
157 struct mgmt_cmt_info_t cmt_info_set[10];
158 int cnt = 0;
159
160 mgmt_history_remove_file((char *)MGMTD_COMMIT_INDEX_FILE_NAME);
161 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "ab");
162 if (!fp) {
163 zlog_err("Failed to open file %s ab mode",
164 MGMTD_COMMIT_INDEX_FILE_NAME);
165 return false;
166 }
167
168 FOREACH_CMT_REC (mm, cmt_info) {
169 memcpy(&cmt_info_set[cnt], cmt_info,
170 sizeof(struct mgmt_cmt_info_t));
171 cnt++;
172 }
173
174 if (!cnt) {
175 fclose(fp);
176 return false;
177 }
178
179 ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
180 fclose(fp);
181 if (ret != cnt) {
182 zlog_err("Write record failed");
183 return false;
184 } else {
185 return true;
186 }
187 }
188
189 static int mgmt_history_rollback_to_cmt(struct vty *vty,
190 struct mgmt_cmt_info_t *cmt_info,
191 bool skip_file_load)
192 {
193 struct mgmt_ds_ctx *src_ds_ctx;
194 struct mgmt_ds_ctx *dst_ds_ctx;
195 int ret = 0;
196
197 if (rollback_vty) {
198 vty_out(vty, "ERROR: Rollback already in progress!\n");
199 return -1;
200 }
201
202 src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
203 if (!src_ds_ctx) {
204 vty_out(vty, "ERROR: Couldnot access Candidate datastore!\n");
205 return -1;
206 }
207
208 /*
209 * Note: Write lock on src_ds is not required. This is already
210 * taken in 'conf te'.
211 */
212 dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
213 if (!dst_ds_ctx) {
214 vty_out(vty, "ERROR: Couldnot access Running datastore!\n");
215 return -1;
216 }
217
218 ret = mgmt_ds_write_lock(dst_ds_ctx);
219 if (ret != 0) {
220 vty_out(vty,
221 "Failed to lock the DS %u for rollback Reason: %s!\n",
222 MGMTD_DS_RUNNING, strerror(ret));
223 return -1;
224 }
225
226 if (!skip_file_load) {
227 ret = mgmt_ds_load_config_from_file(
228 src_ds_ctx, cmt_info->cmt_json_file, false);
229 if (ret != 0) {
230 mgmt_ds_unlock(dst_ds_ctx);
231 vty_out(vty,
232 "Error with parsing the file with error code %d\n",
233 ret);
234 return ret;
235 }
236 }
237
238 /* Internally trigger a commit-request. */
239 ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx);
240 if (ret != 0) {
241 mgmt_ds_unlock(dst_ds_ctx);
242 vty_out(vty,
243 "Error with creating commit apply txn with error code %d\n",
244 ret);
245 return ret;
246 }
247
248 mgmt_history_dump_cmt_record_index();
249
250 /*
251 * Block the rollback command from returning till the rollback
252 * is completed. On rollback completion mgmt_history_rollback_complete()
253 * shall be called to resume the rollback command return to VTYSH.
254 */
255 vty->mgmt_req_pending = true;
256 rollback_vty = vty;
257 return 0;
258 }
259
260 void mgmt_history_rollback_complete(bool success)
261 {
262 vty_mgmt_resume_response(rollback_vty, success);
263 rollback_vty = NULL;
264 }
265
266 int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str)
267 {
268 int ret = 0;
269 struct mgmt_cmt_info_t *cmt_info;
270
271 if (!mgmt_cmt_infos_count(&mm->cmts) ||
272 !mgmt_history_find_cmt_record(cmtid_str)) {
273 vty_out(vty, "Invalid commit Id\n");
274 return -1;
275 }
276
277 FOREACH_CMT_REC (mm, cmt_info) {
278 if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) {
279 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
280 false);
281 return ret;
282 }
283
284 mgmt_history_remove_file(cmt_info->cmt_json_file);
285 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
286 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
287 }
288
289 return 0;
290 }
291
292 int mgmt_history_rollback_n(struct vty *vty, int num_cmts)
293 {
294 int ret = 0;
295 int cnt = 0;
296 struct mgmt_cmt_info_t *cmt_info;
297 size_t cmts;
298
299 if (!num_cmts)
300 num_cmts = 1;
301
302 cmts = mgmt_cmt_infos_count(&mm->cmts);
303 if ((int)cmts < num_cmts) {
304 vty_out(vty,
305 "Number of commits found (%d) less than required to rollback\n",
306 (int)cmts);
307 return -1;
308 }
309
310 if ((int)cmts == 1 || (int)cmts == num_cmts) {
311 vty_out(vty,
312 "Number of commits found (%d), Rollback of last commit is not supported\n",
313 (int)cmts);
314 return -1;
315 }
316
317 FOREACH_CMT_REC (mm, cmt_info) {
318 if (cnt == num_cmts) {
319 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
320 false);
321 return ret;
322 }
323
324 cnt++;
325 mgmt_history_remove_file(cmt_info->cmt_json_file);
326 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
327 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
328 }
329
330 if (!mgmt_cmt_infos_count(&mm->cmts)) {
331 mgmt_ds_reset_candidate();
332 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true);
333 }
334
335 return ret;
336 }
337
338 void show_mgmt_cmt_history(struct vty *vty)
339 {
340 struct mgmt_cmt_info_t *cmt_info;
341 int slno = 0;
342
343 vty_out(vty, "Last 10 commit history:\n");
344 vty_out(vty, "Slot Commit-ID Commit-Record-Time\n");
345 FOREACH_CMT_REC (mm, cmt_info) {
346 vty_out(vty, "%4d %23s %s\n", slno, cmt_info->cmtid_str,
347 cmt_info->time_str);
348 slno++;
349 }
350 }
351
352 void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
353 {
354 struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
355
356 mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
357 mgmt_history_dump_cmt_record_index();
358 }
359
360 void mgmt_history_init(void)
361 {
362 /* Create commit record for previously stored commit-apply */
363 mgmt_cmt_infos_init(&mm->cmts);
364 mgmt_history_read_cmt_record_index();
365 }
366
367 void mgmt_history_destroy(void)
368 {
369 struct mgmt_cmt_info_t *cmt_info;
370
371 FOREACH_CMT_REC(mm, cmt_info) {
372 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
373 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
374 }
375
376 mgmt_cmt_infos_fini(&mm->cmts);
377 }