summaryrefslogtreecommitdiffstats
path: root/lib/northbound_db.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /lib/northbound_db.c
parentInitial commit. (diff)
downloadfrr-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.c280
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;
+}