]> git.proxmox.com Git - mirror_frr.git/blob - mgmtd/mgmt_history.c
mgmtd: Add MGMT Transaction Framework
[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 "thread.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[MGMTD_MD5_HASH_STR_HEX_LEN];
22 char time_str[MGMTD_COMMIT_TIME_STR_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
34 static bool mgmt_history_record_exists(char *file_path)
35 {
36 int exist;
37
38 exist = access(file_path, F_OK);
39 if (exist == 0)
40 return true;
41 else
42 return false;
43 }
44
45 static void mgmt_history_remove_file(char *name)
46 {
47 if (remove(name) == 0)
48 zlog_debug("Old commit info deletion succeeded");
49 else
50 zlog_err("Old commit info deletion failed");
51 }
52
53 static void mgmt_history_hash(const char *input_str, char *hash)
54 {
55 int i;
56 unsigned char digest[MGMTD_MD5_HASH_LEN];
57 MD5_CTX ctx;
58
59 memset(&ctx, 0, sizeof(ctx));
60 MD5Init(&ctx);
61 MD5Update(&ctx, input_str, strlen(input_str));
62 MD5Final(digest, &ctx);
63
64 for (i = 0; i < MGMTD_MD5_HASH_LEN; i++)
65 snprintf(&hash[i * 2], MGMTD_MD5_HASH_STR_HEX_LEN, "%02x",
66 (unsigned int)digest[i]);
67 }
68
69 static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void)
70 {
71 struct mgmt_cmt_info_t *new;
72 struct mgmt_cmt_info_t *cmt_info;
73 struct mgmt_cmt_info_t *last_cmt_info = NULL;
74 struct timeval cmt_recd_tv;
75
76 new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t));
77 gettimeofday(&cmt_recd_tv, NULL);
78 mgmt_realtime_to_string(&cmt_recd_tv, new->time_str,
79 sizeof(new->time_str));
80 mgmt_history_hash(new->time_str, new->cmtid_str);
81 snprintf(new->cmt_json_file, sizeof(new->cmt_json_file),
82 MGMTD_COMMIT_FILE_PATH, new->cmtid_str);
83
84 if (mgmt_cmt_infos_count(&mm->cmts) == MGMTD_MAX_COMMIT_LIST) {
85 FOREACH_CMT_REC (mm, cmt_info)
86 last_cmt_info = cmt_info;
87
88 if (last_cmt_info) {
89 mgmt_history_remove_file(last_cmt_info->cmt_json_file);
90 mgmt_cmt_infos_del(&mm->cmts, last_cmt_info);
91 XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info);
92 }
93 }
94
95 mgmt_cmt_infos_add_head(&mm->cmts, new);
96 return new;
97 }
98
99 static struct mgmt_cmt_info_t *mgmt_history_find_cmt_record(const char *cmtid_str)
100 {
101 struct mgmt_cmt_info_t *cmt_info;
102
103 FOREACH_CMT_REC (mm, cmt_info) {
104 if (strncmp(cmt_info->cmtid_str, cmtid_str,
105 MGMTD_MD5_HASH_STR_HEX_LEN) == 0)
106 return cmt_info;
107 }
108
109 return NULL;
110 }
111
112 static bool mgmt_history_read_cmt_record_index(void)
113 {
114 FILE *fp;
115 struct mgmt_cmt_info_t cmt_info;
116 struct mgmt_cmt_info_t *new;
117 int cnt = 0;
118
119 fp = fopen(MGMTD_COMMIT_INDEX_FILE_NAME, "rb");
120 if (!fp) {
121 zlog_err("Failed to open file %s rb mode",
122 MGMTD_COMMIT_INDEX_FILE_NAME);
123 return false;
124 }
125
126 while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) {
127 if (cnt < MGMTD_MAX_COMMIT_LIST) {
128 if (!mgmt_history_record_exists(cmt_info.cmt_json_file)) {
129 zlog_err(
130 "Commit record present in index_file, but commit file %s missing",
131 cmt_info.cmt_json_file);
132 continue;
133 }
134
135 new = XCALLOC(MTYPE_MGMTD_CMT_INFO,
136 sizeof(struct mgmt_cmt_info_t));
137 memcpy(new, &cmt_info, sizeof(struct mgmt_cmt_info_t));
138 mgmt_cmt_infos_add_tail(&mm->cmts, new);
139 } else {
140 zlog_err("More records found in index file %s",
141 MGMTD_COMMIT_INDEX_FILE_NAME);
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 src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE);
198 if (!src_ds_ctx) {
199 vty_out(vty, "ERROR: Couldnot access Candidate datastore!\n");
200 return -1;
201 }
202
203 /*
204 * Note: Write lock on src_ds is not required. This is already
205 * taken in 'conf te'.
206 */
207 dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
208 if (!dst_ds_ctx) {
209 vty_out(vty, "ERROR: Couldnot access Running datastore!\n");
210 return -1;
211 }
212
213 ret = mgmt_ds_write_lock(dst_ds_ctx);
214 if (ret != 0) {
215 vty_out(vty,
216 "Failed to lock the DS %u for rollback Reason: %s!\n",
217 MGMTD_DS_RUNNING, strerror(ret));
218 return -1;
219 }
220
221 if (!skip_file_load) {
222 ret = mgmt_ds_load_config_from_file(
223 src_ds_ctx, cmt_info->cmt_json_file, false);
224 if (ret != 0) {
225 mgmt_ds_unlock(dst_ds_ctx);
226 vty_out(vty,
227 "Error with parsing the file with error code %d\n",
228 ret);
229 return ret;
230 }
231 }
232
233 /* Internally trigger a commit-request. */
234 ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx);
235 if (ret != 0) {
236 mgmt_ds_unlock(dst_ds_ctx);
237 vty_out(vty,
238 "Error with creating commit apply txn with error code %d\n",
239 ret);
240 return ret;
241 }
242
243 mgmt_history_dump_cmt_record_index();
244 return 0;
245 }
246
247 int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str)
248 {
249 int ret = 0;
250 struct mgmt_cmt_info_t *cmt_info;
251
252 if (!mgmt_cmt_infos_count(&mm->cmts) ||
253 !mgmt_history_find_cmt_record(cmtid_str)) {
254 vty_out(vty, "Invalid commit Id\n");
255 return -1;
256 }
257
258 FOREACH_CMT_REC (mm, cmt_info) {
259 if (strncmp(cmt_info->cmtid_str, cmtid_str,
260 MGMTD_MD5_HASH_STR_HEX_LEN) == 0) {
261 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, false);
262 return ret;
263 }
264
265 mgmt_history_remove_file(cmt_info->cmt_json_file);
266 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
267 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
268 }
269
270 return 0;
271 }
272
273 int mgmt_history_rollback_n(struct vty *vty, int num_cmts)
274 {
275 int ret = 0;
276 int cnt = 0;
277 struct mgmt_cmt_info_t *cmt_info;
278 size_t cmts;
279
280 if (!num_cmts)
281 num_cmts = 1;
282
283 cmts = mgmt_cmt_infos_count(&mm->cmts);
284 if ((int)cmts < num_cmts) {
285 vty_out(vty,
286 "Number of commits found (%d) less than required to rollback\n",
287 (int)cmts);
288 return -1;
289 }
290
291 if ((int)cmts == 1 || (int)cmts == num_cmts) {
292 vty_out(vty,
293 "Number of commits found (%d), Rollback of last commit is not supported\n",
294 (int)cmts);
295 return -1;
296 }
297
298 FOREACH_CMT_REC (mm, cmt_info) {
299 if (cnt == num_cmts) {
300 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, false);
301 return ret;
302 }
303
304 cnt++;
305 mgmt_history_remove_file(cmt_info->cmt_json_file);
306 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
307 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
308 }
309
310 if (!mgmt_cmt_infos_count(&mm->cmts)) {
311 mgmt_ds_reset_candidate();
312 ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true);
313 }
314
315 return ret;
316 }
317
318 void show_mgmt_cmt_history(struct vty *vty)
319 {
320 struct mgmt_cmt_info_t *cmt_info;
321 int slno = 0;
322
323 vty_out(vty, "Last 10 commit history:\n");
324 vty_out(vty, " Sl.No\tCommit-ID(HEX)\t\t\t Commit-Record-Time\n");
325 FOREACH_CMT_REC (mm, cmt_info) {
326 vty_out(vty, " %d\t%s %s\n", slno, cmt_info->cmtid_str,
327 cmt_info->time_str);
328 slno++;
329 }
330 }
331
332 void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx)
333 {
334 struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec();
335 mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx);
336 mgmt_history_dump_cmt_record_index();
337 }
338
339 void mgmt_history_init(void)
340 {
341 /* Create commit record for previously stored commit-apply */
342 mgmt_cmt_infos_init(&mm->cmts);
343 mgmt_history_read_cmt_record_index();
344 }
345
346 void mgmt_history_destroy(void)
347 {
348 struct mgmt_cmt_info_t *cmt_info;
349
350 FOREACH_CMT_REC(mm, cmt_info) {
351 mgmt_cmt_infos_del(&mm->cmts, cmt_info);
352 XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info);
353 }
354
355 mgmt_cmt_infos_fini(&mm->cmts);
356 }