]>
Commit | Line | Data |
---|---|---|
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 | ||
18 | struct 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 | ||
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 | ||
1401ee8b PS |
32 | /* |
33 | * The only instance of VTY session that has triggered an ongoing | |
34 | * config rollback operation. | |
35 | */ | |
83b78f43 | 36 | static struct vty *rollback_vty; |
74335ceb | 37 | |
0030b582 | 38 | static bool file_exists(const char *path) |
74335ceb | 39 | { |
0030b582 | 40 | return !access(path, F_OK); |
74335ceb YR |
41 | } |
42 | ||
0030b582 | 43 | static 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 | 52 | static 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 | ||
71 | static 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 | 92 | static struct mgmt_cmt_info_t * |
93 | mgmt_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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 |
256 | void mgmt_history_rollback_complete(bool success) |
257 | { | |
258 | vty_mgmt_resume_response(rollback_vty, success); | |
259 | rollback_vty = NULL; | |
260 | } | |
261 | ||
74335ceb YR |
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) { | |
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 | ||
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) { | |
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 | ||
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"); | |
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 | ||
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(); | |
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 | ||
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 | } |