/src/frr/lib/northbound_db.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Copyright (C) 2018 NetDEF, Inc. |
4 | | * Renato Westphal |
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 | 0 | { |
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 | 0 | return NB_OK; |
50 | 0 | } |
51 | | |
52 | | int nb_db_transaction_save(const struct nb_transaction *transaction, |
53 | | uint32_t *transaction_id) |
54 | 0 | { |
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 | | |
76 | | client_name = nb_client_name(transaction->context.client); |
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 | | */ |
81 | | if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, |
82 | | LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL) |
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 | 0 | return NB_OK; |
132 | 0 | #endif /* HAVE_CONFIG_ROLLBACKS */ |
133 | 0 | } |
134 | | |
135 | | struct nb_config *nb_db_transaction_load(uint32_t transaction_id) |
136 | 0 | { |
137 | 0 | 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; |
142 | | LY_ERR err; |
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 | | |
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", |
168 | | __func__); |
169 | | else |
170 | | config = nb_config_new(dnode); |
171 | | |
172 | | exit: |
173 | | db_finalize(&ss); |
174 | | #endif /* HAVE_CONFIG_ROLLBACKS */ |
175 | |
|
176 | 0 | return config; |
177 | 0 | } |
178 | | |
179 | | int nb_db_clear_transactions(unsigned int n_oldest) |
180 | 0 | { |
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 | 0 | return NB_OK; |
200 | 0 | } |
201 | | |
202 | | int nb_db_set_max_transactions(unsigned int max) |
203 | 0 | { |
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 | 0 | return NB_OK; |
239 | 0 | } |
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 | 0 | { |
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 | 0 | return NB_OK; |
280 | 0 | } |