diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /lib/northbound_db.c | |
parent | Initial commit. (diff) | |
download | frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip |
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/northbound_db.c')
-rw-r--r-- | lib/northbound_db.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/lib/northbound_db.c b/lib/northbound_db.c new file mode 100644 index 0000000..74abcde --- /dev/null +++ b/lib/northbound_db.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include <zebra.h> + +#include "libfrr.h" +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "db.h" +#include "northbound.h" +#include "northbound_db.h" + +int nb_db_init(void) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* + * NOTE: the delete_tail SQL trigger is used to implement a ring buffer + * where only the last N transactions are recorded in the configuration + * log. + */ + if (db_execute( + "BEGIN TRANSACTION;\n" + " CREATE TABLE IF NOT EXISTS transactions(\n" + " client CHAR(32) NOT NULL,\n" + " date DATETIME DEFAULT CURRENT_TIMESTAMP,\n" + " comment CHAR(80) ,\n" + " configuration TEXT NOT NULL\n" + " );\n" + " CREATE TRIGGER IF NOT EXISTS delete_tail\n" + " AFTER INSERT ON transactions\n" + " FOR EACH ROW\n" + " BEGIN\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" + " END;\n" + "COMMIT;", + NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_transaction_save(const struct nb_transaction *transaction, + uint32_t *transaction_id) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + struct sqlite3_stmt *ss; + const char *client_name; + char *config_str = NULL; + int ret = NB_ERR; + + /* + * Use a transaction to ensure consistency between the INSERT and SELECT + * queries. + */ + if (db_execute("BEGIN TRANSACTION;") != 0) + return NB_ERR; + + ss = db_prepare( + "INSERT INTO transactions\n" + " (client, comment, configuration)\n" + "VALUES\n" + " (?, ?, ?);"); + if (!ss) + goto exit; + + client_name = nb_client_name(transaction->context.client); + /* + * Always record configurations in the XML format, save the default + * values too, as this covers the case where defaults may change. + */ + if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, + LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL) + != 0) + goto exit; + + if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name), + transaction->comment, strlen(transaction->comment), + config_str ? config_str : "", + config_str ? strlen(config_str) : 0) + != 0) + goto exit; + + if (db_run(ss) != SQLITE_OK) + goto exit; + + db_finalize(&ss); + + /* + * transaction_id is an optional output parameter that provides the ID + * of the recorded transaction. + */ + if (transaction_id) { + ss = db_prepare("SELECT last_insert_rowid();"); + if (!ss) + goto exit; + + if (db_run(ss) != SQLITE_ROW) + goto exit; + + if (db_loadf(ss, "%i", transaction_id) != 0) + goto exit; + + db_finalize(&ss); + } + + if (db_execute("COMMIT;") != 0) + goto exit; + + ret = NB_OK; + +exit: + if (config_str) + free(config_str); + if (ss) + db_finalize(&ss); + if (ret != NB_OK) + (void)db_execute("ROLLBACK TRANSACTION;"); + + return ret; +#else /* HAVE_CONFIG_ROLLBACKS */ + return NB_OK; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +struct nb_config *nb_db_transaction_load(uint32_t transaction_id) +{ + struct nb_config *config = NULL; +#ifdef HAVE_CONFIG_ROLLBACKS + struct lyd_node *dnode; + const char *config_str; + struct sqlite3_stmt *ss; + LY_ERR err; + + ss = db_prepare( + "SELECT\n" + " configuration\n" + "FROM\n" + " transactions\n" + "WHERE\n" + " rowid=?;"); + if (!ss) + return NULL; + + if (db_bindf(ss, "%d", transaction_id) != 0) + goto exit; + + if (db_run(ss) != SQLITE_ROW) + goto exit; + + if (db_loadf(ss, "%s", &config_str) != 0) + goto exit; + + err = lyd_parse_data_mem(ly_native_ctx, config_str, LYD_XML, + LYD_PARSE_STRICT | LYD_PARSE_NO_STATE, + LYD_VALIDATE_NO_STATE, &dnode); + if (err || !dnode) + flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_data_mem() failed", + __func__); + else + config = nb_config_new(dnode); + +exit: + db_finalize(&ss); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return config; +} + +int nb_db_clear_transactions(unsigned int n_oldest) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* Delete oldest N entries. */ + if (db_execute("DELETE\n" + "FROM\n" + " transactions\n" + "WHERE\n" + " ROWID IN (\n" + " SELECT\n" + " ROWID\n" + " FROM\n" + " transactions\n" + " ORDER BY ROWID ASC LIMIT %u\n" + " );", + n_oldest) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_set_max_transactions(unsigned int max) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* + * Delete old entries if necessary and update the SQL trigger that + * auto-deletes old entries. + */ + if (db_execute("BEGIN TRANSACTION;\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " ROWID IN (\n" + " SELECT\n" + " ROWID\n" + " FROM\n" + " transactions\n" + " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n" + " );\n" + " DROP TRIGGER delete_tail;\n" + " CREATE TRIGGER delete_tail\n" + " AFTER INSERT ON transactions\n" + " FOR EACH ROW\n" + " BEGIN\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" + " END;\n" + "COMMIT;", + max, max, max) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id, + const char *client_name, + const char *date, + const char *comment), + void *arg) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + struct sqlite3_stmt *ss; + + /* Send SQL query and parse the result. */ + ss = db_prepare( + "SELECT\n" + " rowid, client, date, comment\n" + "FROM\n" + " transactions\n" + "ORDER BY\n" + " rowid DESC;"); + if (!ss) + return NB_ERR; + + while (db_run(ss) == SQLITE_ROW) { + int transaction_id; + const char *client_name; + const char *date; + const char *comment; + int ret; + + ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name, + &date, &comment); + if (ret != 0) + continue; + + (*func)(arg, transaction_id, client_name, date, comment); + } + + db_finalize(&ss); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} |