Line data Source code
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 12 : 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 12 : return NB_OK;
63 : }
64 :
65 50 : 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 50 : return NB_OK;
145 : #endif /* HAVE_CONFIG_ROLLBACKS */
146 : }
147 :
148 0 : struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
149 : {
150 0 : 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 0 : return config;
190 : }
191 :
192 0 : 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 0 : return NB_OK;
213 : }
214 :
215 0 : 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 0 : return NB_OK;
252 : }
253 :
254 0 : 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 0 : return NB_OK;
293 : }
|