]> git.proxmox.com Git - mirror_frr.git/blame - mgmtd/mgmt_history.c
Merge pull request #13553 from LabNConsulting/chopps/fixwarn+msg
[mirror_frr.git] / mgmtd / mgmt_history.c
CommitLineData
74335ceb
YR
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"
24a58196 10#include "frrevent.h"
74335ceb
YR
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
18struct mgmt_cmt_info_t {
19 struct mgmt_cmt_infos_item cmts;
20
d31d24c4
CH
21 char cmtid_str[MGMT_SHORT_TIME_MAX_LEN];
22 char time_str[MGMT_LONG_TIME_MAX_LEN];
74335ceb
YR
23 char cmt_json_file[PATH_MAX];
24};
25
26
27DECLARE_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
1401ee8b
PS
32/*
33 * The only instance of VTY session that has triggered an ongoing
34 * config rollback operation.
35 */
83b78f43 36static struct vty *rollback_vty;
74335ceb 37
0030b582 38static bool file_exists(const char *path)
74335ceb 39{
0030b582 40 return !access(path, F_OK);
74335ceb
YR
41}
42
0030b582 43static void remove_file(const char *path)
74335ceb 44{
0030b582
CH
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));
74335ceb
YR
50}
51
d31d24c4 52static struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void)
74335ceb 53{
d31d24c4
CH
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;
74335ceb
YR
69}
70
71static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
72{
d31d24c4 73 struct mgmt_cmt_info_t *new = mgmt_history_new_cmt_info();
74335ceb
YR
74 struct mgmt_cmt_info_t *cmt_info;
75 struct mgmt_cmt_info_t *last_cmt_info = NULL;
74335ceb
YR
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) {
0030b582 82 remove_file(last_cmt_info->cmt_json_file);
74335ceb
YR
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
83b78f43 92static struct mgmt_cmt_info_t *
93mgmt_history_find_cmt_record(const char *cmtid_str)
74335ceb
YR
94{
95 struct mgmt_cmt_info_t *cmt_info;
96
97 FOREACH_CMT_REC (mm, cmt_info) {
d31d24c4 98 if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0)
74335ceb
YR
99 return cmt_info;
100 }
101
102 return NULL;
103}
104
105static 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
0030b582
CH
112 if (!file_exists(MGMTD_COMMIT_FILE_PATH))
113 return false;
114
74335ceb
YR
115 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
116 if (!fp) {
0030b582
CH
117 zlog_err("Failed to open commit history %s for reading: %s",
118 MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno));
74335ceb
YR
119 return false;
120 }
121
122 while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) {
123 if (cnt < MGMTD_MAX_COMMIT_LIST) {
0030b582
CH
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);
74335ceb
YR
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 {
0030b582
CH
135 zlog_warn(
136 "More records found in commit history file %s than expected",
137 MGMTD_COMMIT_INDEX_FILE_NAME);
b69017f4 138 fclose(fp);
74335ceb
YR
139 return false;
140 }
141
142 cnt++;
143 }
144
145 fclose(fp);
146 return true;
147}
148
149static 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
0030b582 157 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "wb");
74335ceb 158 if (!fp) {
0030b582
CH
159 zlog_err("Failed to open commit history %s for writing: %s",
160 MGMTD_COMMIT_INDEX_FILE_NAME, safe_strerror(errno));
74335ceb
YR
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) {
0030b582
CH
178 zlog_err("Failed to write full commit history, removing file");
179 remove_file(MGMTD_COMMIT_INDEX_FILE_NAME);
74335ceb 180 return false;
74335ceb 181 }
0030b582 182 return true;
74335ceb
YR
183}
184
185static 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
1401ee8b
PS
193 if (rollback_vty) {
194 vty_out(vty, "ERROR: Rollback already in progress!\n");
195 return -1;
196 }
197
74335ceb
YR
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();
1401ee8b
PS
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;
74335ceb
YR
253 return 0;
254}
255
1401ee8b
PS
256void mgmt_history_rollback_complete(bool success)
257{
258 vty_mgmt_resume_response(rollback_vty, success);
259 rollback_vty = NULL;
260}
261
74335ceb
YR
262int 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) {
d31d24c4 274 if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) {
83b78f43 275 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
276 false);
74335ceb
YR
277 return ret;
278 }
279
0030b582 280 remove_file(cmt_info->cmt_json_file);
74335ceb
YR
281 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
282 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
283 }
284
285 return 0;
286}
287
288int 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) {
83b78f43 315 ret = mgmt_history_rollback_to_cmt(vty, cmt_info,
316 false);
74335ceb
YR
317 return ret;
318 }
319
320 cnt++;
0030b582 321 remove_file(cmt_info->cmt_json_file);
74335ceb
YR
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
334void 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");
d31d24c4 340 vty_out(vty, "Slot Commit-ID Commit-Record-Time\n");
74335ceb 341 FOREACH_CMT_REC (mm, cmt_info) {
d31d24c4 342 vty_out(vty, "%4d %23s %s\n", slno, cmt_info->cmtid_str,
74335ceb
YR
343 cmt_info->time_str);
344 slno++;
345 }
346}
347
348void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
349{
350 struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
83b78f43 351
74335ceb
YR
352 mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
353 mgmt_history_dump_cmt_record_index();
354}
355
356void 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
363void 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}