]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_db.c
Merge pull request #13649 from donaldsharp/unlock_the_node_or_else
[mirror_frr.git] / lib / northbound_db.c
CommitLineData
acddc0ed 1// SPDX-License-Identifier: GPL-2.0-or-later
1c2facd1
RW
2/*
3 * Copyright (C) 2018 NetDEF, Inc.
4 * Renato Westphal
1c2facd1
RW
5 */
6
7#include <zebra.h>
8
9#include "libfrr.h"
10#include "log.h"
11#include "lib_errors.h"
12#include "command.h"
13#include "db.h"
14#include "northbound.h"
15#include "northbound_db.h"
16
17int nb_db_init(void)
18{
19#ifdef HAVE_CONFIG_ROLLBACKS
20 /*
21 * NOTE: the delete_tail SQL trigger is used to implement a ring buffer
22 * where only the last N transactions are recorded in the configuration
23 * log.
24 */
25 if (db_execute(
26 "BEGIN TRANSACTION;\n"
27 " CREATE TABLE IF NOT EXISTS transactions(\n"
28 " client CHAR(32) NOT NULL,\n"
29 " date DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
30 " comment CHAR(80) ,\n"
31 " configuration TEXT NOT NULL\n"
32 " );\n"
33 " CREATE TRIGGER IF NOT EXISTS delete_tail\n"
34 " AFTER INSERT ON transactions\n"
35 " FOR EACH ROW\n"
36 " BEGIN\n"
37 " DELETE\n"
38 " FROM\n"
39 " transactions\n"
40 " WHERE\n"
41 " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
42 " END;\n"
43 "COMMIT;",
44 NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS)
45 != 0)
46 return NB_ERR;
47#endif /* HAVE_CONFIG_ROLLBACKS */
48
49 return NB_OK;
50}
51
52int nb_db_transaction_save(const struct nb_transaction *transaction,
53 uint32_t *transaction_id)
54{
55#ifdef HAVE_CONFIG_ROLLBACKS
56 struct sqlite3_stmt *ss;
57 const char *client_name;
58 char *config_str = NULL;
59 int ret = NB_ERR;
60
61 /*
62 * Use a transaction to ensure consistency between the INSERT and SELECT
63 * queries.
64 */
65 if (db_execute("BEGIN TRANSACTION;") != 0)
66 return NB_ERR;
67
68 ss = db_prepare(
69 "INSERT INTO transactions\n"
70 " (client, comment, configuration)\n"
71 "VALUES\n"
72 " (?, ?, ?);");
73 if (!ss)
74 goto exit;
75
41ef7327 76 client_name = nb_client_name(transaction->context.client);
af1b88e9
CH
77 /*
78 * Always record configurations in the XML format, save the default
79 * values too, as this covers the case where defaults may change.
80 */
1c2facd1 81 if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
af1b88e9 82 LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
1c2facd1
RW
83 != 0)
84 goto exit;
85
86 if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name),
87 transaction->comment, strlen(transaction->comment),
88 config_str ? config_str : "",
89 config_str ? strlen(config_str) : 0)
90 != 0)
91 goto exit;
92
93 if (db_run(ss) != SQLITE_OK)
94 goto exit;
95
96 db_finalize(&ss);
97
98 /*
99 * transaction_id is an optional output parameter that provides the ID
100 * of the recorded transaction.
101 */
102 if (transaction_id) {
103 ss = db_prepare("SELECT last_insert_rowid();");
104 if (!ss)
105 goto exit;
106
107 if (db_run(ss) != SQLITE_ROW)
108 goto exit;
109
110 if (db_loadf(ss, "%i", transaction_id) != 0)
111 goto exit;
112
113 db_finalize(&ss);
114 }
115
116 if (db_execute("COMMIT;") != 0)
117 goto exit;
118
119 ret = NB_OK;
120
121exit:
122 if (config_str)
123 free(config_str);
124 if (ss)
125 db_finalize(&ss);
126 if (ret != NB_OK)
127 (void)db_execute("ROLLBACK TRANSACTION;");
128
129 return ret;
130#else /* HAVE_CONFIG_ROLLBACKS */
131 return NB_OK;
132#endif /* HAVE_CONFIG_ROLLBACKS */
133}
134
135struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
136{
137 struct nb_config *config = NULL;
138#ifdef HAVE_CONFIG_ROLLBACKS
139 struct lyd_node *dnode;
140 const char *config_str;
141 struct sqlite3_stmt *ss;
af1b88e9 142 LY_ERR err;
1c2facd1
RW
143
144 ss = db_prepare(
145 "SELECT\n"
146 " configuration\n"
147 "FROM\n"
148 " transactions\n"
149 "WHERE\n"
150 " rowid=?;");
151 if (!ss)
152 return NULL;
153
154 if (db_bindf(ss, "%d", transaction_id) != 0)
155 goto exit;
156
157 if (db_run(ss) != SQLITE_ROW)
158 goto exit;
159
160 if (db_loadf(ss, "%s", &config_str) != 0)
161 goto exit;
162
af1b88e9
CH
163 err = lyd_parse_data_mem(ly_native_ctx, config_str, LYD_XML,
164 LYD_PARSE_STRICT | LYD_PARSE_NO_STATE,
165 LYD_VALIDATE_NO_STATE, &dnode);
166 if (err || !dnode)
167 flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_data_mem() failed",
1c2facd1
RW
168 __func__);
169 else
170 config = nb_config_new(dnode);
171
172exit:
173 db_finalize(&ss);
174#endif /* HAVE_CONFIG_ROLLBACKS */
175
176 return config;
177}
178
179int nb_db_clear_transactions(unsigned int n_oldest)
180{
181#ifdef HAVE_CONFIG_ROLLBACKS
182 /* Delete oldest N entries. */
183 if (db_execute("DELETE\n"
184 "FROM\n"
185 " transactions\n"
186 "WHERE\n"
187 " ROWID IN (\n"
188 " SELECT\n"
189 " ROWID\n"
190 " FROM\n"
191 " transactions\n"
192 " ORDER BY ROWID ASC LIMIT %u\n"
193 " );",
194 n_oldest)
195 != 0)
196 return NB_ERR;
197#endif /* HAVE_CONFIG_ROLLBACKS */
198
199 return NB_OK;
200}
201
202int nb_db_set_max_transactions(unsigned int max)
203{
204#ifdef HAVE_CONFIG_ROLLBACKS
205 /*
206 * Delete old entries if necessary and update the SQL trigger that
207 * auto-deletes old entries.
208 */
209 if (db_execute("BEGIN TRANSACTION;\n"
210 " DELETE\n"
211 " FROM\n"
212 " transactions\n"
213 " WHERE\n"
214 " ROWID IN (\n"
215 " SELECT\n"
216 " ROWID\n"
217 " FROM\n"
218 " transactions\n"
219 " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n"
220 " );\n"
221 " DROP TRIGGER delete_tail;\n"
222 " CREATE TRIGGER delete_tail\n"
223 " AFTER INSERT ON transactions\n"
224 " FOR EACH ROW\n"
225 " BEGIN\n"
226 " DELETE\n"
227 " FROM\n"
228 " transactions\n"
229 " WHERE\n"
230 " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
231 " END;\n"
232 "COMMIT;",
233 max, max, max)
234 != 0)
235 return NB_ERR;
236#endif /* HAVE_CONFIG_ROLLBACKS */
237
238 return NB_OK;
239}
240
241int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id,
242 const char *client_name,
243 const char *date,
244 const char *comment),
245 void *arg)
246{
247#ifdef HAVE_CONFIG_ROLLBACKS
248 struct sqlite3_stmt *ss;
249
250 /* Send SQL query and parse the result. */
251 ss = db_prepare(
252 "SELECT\n"
253 " rowid, client, date, comment\n"
254 "FROM\n"
255 " transactions\n"
256 "ORDER BY\n"
257 " rowid DESC;");
258 if (!ss)
259 return NB_ERR;
260
261 while (db_run(ss) == SQLITE_ROW) {
262 int transaction_id;
263 const char *client_name;
264 const char *date;
265 const char *comment;
266 int ret;
267
268 ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name,
269 &date, &comment);
270 if (ret != 0)
271 continue;
272
273 (*func)(arg, transaction_id, client_name, date, comment);
274 }
275
276 db_finalize(&ss);
277#endif /* HAVE_CONFIG_ROLLBACKS */
278
279 return NB_OK;
280}