]> git.proxmox.com Git - mirror_frr.git/blob - mgmtd/mgmt_history.c
Merge pull request #13553 from LabNConsulting/chopps/fixwarn+msg
[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 file_exists(const char *path)
39 {
40 return !access(path, F_OK);
41 }
42
43 static void remove_file(const char *path)
44 {
45 if (!file_exists(path))
46 return;
47 if (unlink(path))
48 zlog_err("Failed to remove commit history file %s: %s", path,
49 safe_strerror(errno));
50 }
51
52 static struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void)
53 {
54 struct mgmt_cmt_info_t *new;
55 struct timespec tv;
56 struct tm tm;
57
58 new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t));
59
60 clock_gettime(CLOCK_REALTIME, &tv);
61 localtime_r(&tv.tv_sec, &tm);
62
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);
67
68 return new;
69 }
70
71 static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
72 {
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;
76
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;
80
81 if (last_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);
85 }
86 }
87
88 mgmt_cmt_infos_add_head(&mm->cmts, new);
89 return new;
90 }
91
92 static struct mgmt_cmt_info_t *
93 mgmt_history_find_cmt_record(const char *cmtid_str)
94 {
95 struct mgmt_cmt_info_t *cmt_info;
96
97 FOREACH_CMT_REC (mm, cmt_info) {
98 if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0)
99 return cmt_info;
100 }
101
102 return NULL;
103 }
104
105 static bool mgmt_history_read_cmt_record_index(void)
106 {
107 FILE *fp;
108 struct mgmt_cmt_info_t cmt_info;
109 struct mgmt_cmt_info_t *new;
110 int cnt = 0;
111
112 if (!file_exists(MGMTD_COMMIT_FILE_PATH))
113 return false;
114
115 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
116 if (!fp) {
117 zlog_err("Failed to open commit history %s for reading: %s",
118 MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno));
119 return false;
120 }
121
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);
127 continue;
128 }
129
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);
134 } else {
135 zlog_warn(
136 "More records found in commit history file %s than expected",
137 MGMTD_COMMIT_INDEX_FILE_NAME);
138 fclose(fp);
139 return false;
140 }
141
142 cnt++;
143 }
144
145 fclose(fp);
146 return true;
147 }
148
149 static bool mgmt_history_dump_cmt_record_index(void)
150 {
151 FILE *fp;
152 int ret = 0;
153 struct mgmt_cmt_info_t *cmt_info;
154 struct mgmt_cmt_info_t cmt_info_set[10];
155 int cnt = 0;
156
157 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "wb");
158 if (!fp) {
159 zlog_err("Failed to open commit history %s for writing: %s",
160 MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno));
161 return false;
162 }
163
164 FOREACH_CMT_REC (mm, cmt_info) {
165 memcpy(&cmt_info_set[cnt], cmt_info,
166 sizeof(struct mgmt_cmt_info_t));
167 cnt++;
168 }
169
170 if (!cnt) {
171 fclose(fp);
172 return false;
173 }
174
175 ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
176 fclose(fp);
177 if (ret != cnt) {
178 zlog_err("Failed to write full commit history, removing file");
179 remove_file(MGMTD_COMMIT_INDEX_FILE_NAME);
180 return false;
181 }
182 return true;
183 }
184
185 static int mgmt_history_rollback_to_cmt(struct vty *vty,
186 struct mgmt_cmt_info_t *cmt_info,
187 bool skip_file_load)
188 {
189 struct mgmt_ds_ctx *src_ds_ctx;
190 struct mgmt_ds_ctx *dst_ds_ctx;
191 int ret = 0;
192
193 if (rollback_vty) {
194 vty_out(vty, "ERROR: Rollback already in progress!\n");
195 return -1;
196 }
197
198 src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
199 if (!src_ds_ctx) {
200 vty_out(vty, "ERROR: Couldnot access Candidate datastore!\n");
201 return -1;
202 }
203
204 /*
205 * Note: Write lock on src_ds is not required. This is already
206 * taken in 'conf te'.
207 */
208 dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
209 if (!dst_ds_ctx) {
210 vty_out(vty, "ERROR: Couldnot access Running datastore!\n");
211 return -1;
212 }
213
214 ret = mgmt_ds_write_lock(dst_ds_ctx);
215 if (ret != 0) {
216 vty_out(vty,
217 "Failed to lock the DS %u for rollback Reason: %s!\n",
218 MGMTD_DS_RUNNING, strerror(ret));
219 return -1;
220 }
221
222 if (!skip_file_load) {
223 ret = mgmt_ds_load_config_from_file(
224 src_ds_ctx, cmt_info->cmt_json_file, false);
225 if (ret != 0) {
226 mgmt_ds_unlock(dst_ds_ctx);
227 vty_out(vty,
228 "Error with parsing the file with error code %d\n",
229 ret);
230 return ret;
231 }
232 }
233
234 /* Internally trigger a commit-request. */
235 ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx);
236 if (ret != 0) {
237 mgmt_ds_unlock(dst_ds_ctx);
238 vty_out(vty,
239 "Error with creating commit apply txn with error code %d\n",
240 ret);
241 return ret;
242 }
243
244 mgmt_history_dump_cmt_record_index();
245
246 /*
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.
250 */
251 vty->mgmt_req_pending = true;
252 rollback_vty = vty;
253 return 0;
254 }
255
256 void mgmt_history_rollback_complete(bool success)
257 {
258 vty_mgmt_resume_response(rollback_vty, success);
259 rollback_vty = NULL;
260 }
261
262 int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str)
263 {
264 int ret = 0;
265 struct mgmt_cmt_info_t *cmt_info;
266
267 if (!mgmt_cmt_infos_count(&mm->cmts) ||
268 !mgmt_history_find_cmt_record(cmtid_str)) {
269 vty_out(vty, "Invalid commit Id\n");
270 return -1;
271 }
272
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,
276 false);
277 return ret;
278 }
279
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);
283 }
284
285 return 0;
286 }
287
288 int mgmt_history_rollback_n(struct vty *vty, int num_cmts)
289 {
290 int ret = 0;
291 int cnt = 0;
292 struct mgmt_cmt_info_t *cmt_info;
293 size_t cmts;
294
295 if (!num_cmts)
296 num_cmts = 1;
297
298 cmts = mgmt_cmt_infos_count(&mm->cmts);
299 if ((int)cmts < num_cmts) {
300 vty_out(vty,
301 "Number of commits found (%d) less than required to rollback\n",
302 (int)cmts);
303 return -1;
304 }
305
306 if ((int)cmts == 1 || (int)cmts == num_cmts) {
307 vty_out(vty,
308 "Number of commits found (%d), Rollback of last commit is not supported\n",
309 (int)cmts);
310 return -1;
311 }
312
313 FOREACH_CMT_REC (mm, cmt_info) {
314 if (cnt == num_cmts) {
315 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
316 false);
317 return ret;
318 }
319
320 cnt++;
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);
324 }
325
326 if (!mgmt_cmt_infos_count(&mm->cmts)) {
327 mgmt_ds_reset_candidate();
328 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true);
329 }
330
331 return ret;
332 }
333
334 void show_mgmt_cmt_history(struct vty *vty)
335 {
336 struct mgmt_cmt_info_t *cmt_info;
337 int slno = 0;
338
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,
343 cmt_info->time_str);
344 slno++;
345 }
346 }
347
348 void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
349 {
350 struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
351
352 mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
353 mgmt_history_dump_cmt_record_index();
354 }
355
356 void mgmt_history_init(void)
357 {
358 /* Create commit record for previously stored commit-apply */
359 mgmt_cmt_infos_init(&mm->cmts);
360 mgmt_history_read_cmt_record_index();
361 }
362
363 void mgmt_history_destroy(void)
364 {
365 struct mgmt_cmt_info_t *cmt_info;
366
367 FOREACH_CMT_REC(mm, cmt_info) {
368 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
369 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
370 }
371
372 mgmt_cmt_infos_fini(&mm->cmts);
373 }