mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 15:30:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			289 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2018  NetDEF, Inc.
 | 
						|
 *                     Renato Westphal
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify it
 | 
						|
 * under the terms of the GNU General Public License as published by the Free
 | 
						|
 * Software Foundation; either version 2 of the License, or (at your option)
 | 
						|
 * any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
						|
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
						|
 * more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License along
 | 
						|
 * with this program; see the file COPYING; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | 
						|
 */
 | 
						|
 | 
						|
#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->client);
 | 
						|
	/* Always record configurations in the XML format. */
 | 
						|
	if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
 | 
						|
			  LYP_FORMAT | LYP_WITHSIBLINGS)
 | 
						|
	    != 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;
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
	dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML,
 | 
						|
			      LYD_OPT_CONFIG);
 | 
						|
	if (!dnode)
 | 
						|
		flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_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;
 | 
						|
}
 |