]>
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 YR |
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 | ||
d31d24c4 | 57 | static struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void) |
74335ceb | 58 | { |
d31d24c4 CH |
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; | |
74335ceb YR |
74 | } |
75 | ||
76 | static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void) | |
77 | { | |
d31d24c4 | 78 | struct mgmt_cmt_info_t *new = mgmt_history_new_cmt_info(); |
74335ceb YR |
79 | struct mgmt_cmt_info_t *cmt_info; |
80 | struct mgmt_cmt_info_t *last_cmt_info = NULL; | |
74335ceb YR |
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 | ||
83b78f43 | 97 | static struct mgmt_cmt_info_t * |
98 | mgmt_history_find_cmt_record(const char *cmtid_str) | |
74335ceb YR |
99 | { |
100 | struct mgmt_cmt_info_t *cmt_info; | |
101 | ||
102 | FOREACH_CMT_REC (mm, cmt_info) { | |
d31d24c4 | 103 | if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) |
74335ceb YR |
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) { | |
83b78f43 | 126 | if (!mgmt_history_record_exists( |
127 | cmt_info.cmt_json_file)) { | |
74335ceb YR |
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); | |
b69017f4 | 141 | fclose(fp); |
74335ceb YR |
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 | ||
1401ee8b PS |
197 | if (rollback_vty) { |
198 | vty_out(vty, "ERROR: Rollback already in progress!\n"); | |
199 | return -1; | |
200 | } | |
201 | ||
74335ceb YR |
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(); | |
1401ee8b PS |
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; | |
74335ceb YR |
257 | return 0; |
258 | } | |
259 | ||
1401ee8b PS |
260 | void mgmt_history_rollback_complete(bool success) |
261 | { | |
262 | vty_mgmt_resume_response(rollback_vty, success); | |
263 | rollback_vty = NULL; | |
264 | } | |
265 | ||
74335ceb YR |
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) { | |
d31d24c4 | 278 | if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) { |
83b78f43 | 279 | ret = mgmt_history_rollback_to_cmt(vty, cmt_info, |
280 | false); | |
74335ceb YR |
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) { | |
83b78f43 | 319 | ret = mgmt_history_rollback_to_cmt(vty, cmt_info, |
320 | false); | |
74335ceb YR |
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"); | |
d31d24c4 | 344 | vty_out(vty, "Slot Commit-ID Commit-Record-Time\n"); |
74335ceb | 345 | FOREACH_CMT_REC (mm, cmt_info) { |
d31d24c4 | 346 | vty_out(vty, "%4d %23s %s\n", slno, cmt_info->cmtid_str, |
74335ceb YR |
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(); | |
83b78f43 | 355 | |
74335ceb YR |
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 | } |