]>
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 | ||
89 | client_name = nb_client_name(transaction->client); | |
90 | /* Always record configurations in the XML format. */ | |
91 | if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, | |
92 | LYP_FORMAT | LYP_WITHSIBLINGS) | |
93 | != 0) | |
94 | goto exit; | |
95 | ||
96 | if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name), | |
97 | transaction->comment, strlen(transaction->comment), | |
98 | config_str ? config_str : "", | |
99 | config_str ? strlen(config_str) : 0) | |
100 | != 0) | |
101 | goto exit; | |
102 | ||
103 | if (db_run(ss) != SQLITE_OK) | |
104 | goto exit; | |
105 | ||
106 | db_finalize(&ss); | |
107 | ||
108 | /* | |
109 | * transaction_id is an optional output parameter that provides the ID | |
110 | * of the recorded transaction. | |
111 | */ | |
112 | if (transaction_id) { | |
113 | ss = db_prepare("SELECT last_insert_rowid();"); | |
114 | if (!ss) | |
115 | goto exit; | |
116 | ||
117 | if (db_run(ss) != SQLITE_ROW) | |
118 | goto exit; | |
119 | ||
120 | if (db_loadf(ss, "%i", transaction_id) != 0) | |
121 | goto exit; | |
122 | ||
123 | db_finalize(&ss); | |
124 | } | |
125 | ||
126 | if (db_execute("COMMIT;") != 0) | |
127 | goto exit; | |
128 | ||
129 | ret = NB_OK; | |
130 | ||
131 | exit: | |
132 | if (config_str) | |
133 | free(config_str); | |
134 | if (ss) | |
135 | db_finalize(&ss); | |
136 | if (ret != NB_OK) | |
137 | (void)db_execute("ROLLBACK TRANSACTION;"); | |
138 | ||
139 | return ret; | |
140 | #else /* HAVE_CONFIG_ROLLBACKS */ | |
141 | return NB_OK; | |
142 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
143 | } | |
144 | ||
145 | struct nb_config *nb_db_transaction_load(uint32_t transaction_id) | |
146 | { | |
147 | struct nb_config *config = NULL; | |
148 | #ifdef HAVE_CONFIG_ROLLBACKS | |
149 | struct lyd_node *dnode; | |
150 | const char *config_str; | |
151 | struct sqlite3_stmt *ss; | |
152 | ||
153 | ss = db_prepare( | |
154 | "SELECT\n" | |
155 | " configuration\n" | |
156 | "FROM\n" | |
157 | " transactions\n" | |
158 | "WHERE\n" | |
159 | " rowid=?;"); | |
160 | if (!ss) | |
161 | return NULL; | |
162 | ||
163 | if (db_bindf(ss, "%d", transaction_id) != 0) | |
164 | goto exit; | |
165 | ||
166 | if (db_run(ss) != SQLITE_ROW) | |
167 | goto exit; | |
168 | ||
169 | if (db_loadf(ss, "%s", &config_str) != 0) | |
170 | goto exit; | |
171 | ||
172 | dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML, | |
173 | LYD_OPT_CONFIG); | |
174 | if (!dnode) | |
175 | flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed", | |
176 | __func__); | |
177 | else | |
178 | config = nb_config_new(dnode); | |
179 | ||
180 | exit: | |
181 | db_finalize(&ss); | |
182 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
183 | ||
184 | return config; | |
185 | } | |
186 | ||
187 | int nb_db_clear_transactions(unsigned int n_oldest) | |
188 | { | |
189 | #ifdef HAVE_CONFIG_ROLLBACKS | |
190 | /* Delete oldest N entries. */ | |
191 | if (db_execute("DELETE\n" | |
192 | "FROM\n" | |
193 | " transactions\n" | |
194 | "WHERE\n" | |
195 | " ROWID IN (\n" | |
196 | " SELECT\n" | |
197 | " ROWID\n" | |
198 | " FROM\n" | |
199 | " transactions\n" | |
200 | " ORDER BY ROWID ASC LIMIT %u\n" | |
201 | " );", | |
202 | n_oldest) | |
203 | != 0) | |
204 | return NB_ERR; | |
205 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
206 | ||
207 | return NB_OK; | |
208 | } | |
209 | ||
210 | int nb_db_set_max_transactions(unsigned int max) | |
211 | { | |
212 | #ifdef HAVE_CONFIG_ROLLBACKS | |
213 | /* | |
214 | * Delete old entries if necessary and update the SQL trigger that | |
215 | * auto-deletes old entries. | |
216 | */ | |
217 | if (db_execute("BEGIN TRANSACTION;\n" | |
218 | " DELETE\n" | |
219 | " FROM\n" | |
220 | " transactions\n" | |
221 | " WHERE\n" | |
222 | " ROWID IN (\n" | |
223 | " SELECT\n" | |
224 | " ROWID\n" | |
225 | " FROM\n" | |
226 | " transactions\n" | |
227 | " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n" | |
228 | " );\n" | |
229 | " DROP TRIGGER delete_tail;\n" | |
230 | " CREATE TRIGGER delete_tail\n" | |
231 | " AFTER INSERT ON transactions\n" | |
232 | " FOR EACH ROW\n" | |
233 | " BEGIN\n" | |
234 | " DELETE\n" | |
235 | " FROM\n" | |
236 | " transactions\n" | |
237 | " WHERE\n" | |
238 | " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" | |
239 | " END;\n" | |
240 | "COMMIT;", | |
241 | max, max, max) | |
242 | != 0) | |
243 | return NB_ERR; | |
244 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
245 | ||
246 | return NB_OK; | |
247 | } | |
248 | ||
249 | int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id, | |
250 | const char *client_name, | |
251 | const char *date, | |
252 | const char *comment), | |
253 | void *arg) | |
254 | { | |
255 | #ifdef HAVE_CONFIG_ROLLBACKS | |
256 | struct sqlite3_stmt *ss; | |
257 | ||
258 | /* Send SQL query and parse the result. */ | |
259 | ss = db_prepare( | |
260 | "SELECT\n" | |
261 | " rowid, client, date, comment\n" | |
262 | "FROM\n" | |
263 | " transactions\n" | |
264 | "ORDER BY\n" | |
265 | " rowid DESC;"); | |
266 | if (!ss) | |
267 | return NB_ERR; | |
268 | ||
269 | while (db_run(ss) == SQLITE_ROW) { | |
270 | int transaction_id; | |
271 | const char *client_name; | |
272 | const char *date; | |
273 | const char *comment; | |
274 | int ret; | |
275 | ||
276 | ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name, | |
277 | &date, &comment); | |
278 | if (ret != 0) | |
279 | continue; | |
280 | ||
281 | (*func)(arg, transaction_id, client_name, date, comment); | |
282 | } | |
283 | ||
284 | db_finalize(&ss); | |
285 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
286 | ||
287 | return NB_OK; | |
288 | } |