]>
Commit | Line | Data |
---|---|---|
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 | ||
17 | int 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 | ||
52 | int 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 | ||
13d6b9c1 | 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 | ||
121 | exit: | |
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 | ||
135 | struct 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 | ||
172 | exit: | |
173 | db_finalize(&ss); | |
174 | #endif /* HAVE_CONFIG_ROLLBACKS */ | |
175 | ||
176 | return config; | |
177 | } | |
178 | ||
179 | int 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 | ||
202 | int 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 | ||
241 | int 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 | } |