]>
Commit | Line | Data |
---|---|---|
1c2facd1 RW |
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 | ||
13d6b9c1 | 89 | client_name = nb_client_name(transaction->context->client); |
af1b88e9 CH |
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 | */ | |
1c2facd1 | 94 | if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, |
af1b88e9 | 95 | LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL) |
1c2facd1 RW |
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; | |
af1b88e9 | 155 | LY_ERR err; |
1c2facd1 RW |
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 | ||
af1b88e9 CH |
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", | |
1c2facd1 RW |
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 | } |