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