summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorAndrei Pavel <andrei@isc.org>2023-11-20 11:21:45 +0100
committerAndrei Pavel <andrei@isc.org>2024-02-22 08:57:35 +0100
commit8e2d02205c5aab819ef03b6e7c9fa15495a55821 (patch)
tree33c10277366f9b964171965757c54758f6d170b3 /src/lib
parent[#3130] trim trailing slash (diff)
downloadkea-8e2d02205c5aab819ef03b6e7c9fa15495a55821.tar.xz
kea-8e2d02205c5aab819ef03b6e7c9fa15495a55821.zip
[#3025] automatic init of mysql schema
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/database/database_connection.h8
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.cc44
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.h2
-rw-r--r--src/lib/mysql/Makefile.am4
-rw-r--r--src/lib/mysql/mysql_connection.cc102
-rw-r--r--src/lib/mysql/mysql_connection.h23
6 files changed, 158 insertions, 25 deletions
diff --git a/src/lib/database/database_connection.h b/src/lib/database/database_connection.h
index 3f36e963e4..f85d8a0b56 100644
--- a/src/lib/database/database_connection.h
+++ b/src/lib/database/database_connection.h
@@ -93,6 +93,14 @@ public:
isc::Exception(file, line, what) {}
};
+/// @brief Thrown when an initialization of the schema failed.
+class SchemaInitializationFailed: public Exception {
+public:
+ SchemaInitializationFailed(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
/// @brief Defines a callback prototype for propagating events upward
typedef std::function<bool (util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback;
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 1f95e41940..23ad6f2d76 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -2185,38 +2185,28 @@ MySqlLeaseMgr::MySqlLeaseTrackingContextAlloc::~MySqlLeaseTrackingContextAlloc()
// MySqlLeaseMgr Constructor and Destructor
MySqlLeaseMgr::MySqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
- : TrackingLeaseMgr(), parameters_(parameters), timer_name_("") {
+ : TrackingLeaseMgr(), parameters_(parameters) {
// Check if the extended info tables are enabled.
setExtendedInfoTablesEnabled(parameters);
+ // retry-on-startup?
+ bool const retry(parameters.count("retry-on-startup") &&
+ parameters.at("retry-on-startup") == "true");
+
+ // retry-on-startup disabled. Ensure schema version with empty timer name / no retry.
+ if (!retry) {
+ ensureSchemaVersion();
+ }
+
// Create unique timer name per instance.
timer_name_ = "MySqlLeaseMgr[";
timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
timer_name_ += "]DbReconnectTimer";
- // Validate schema version first.
- std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
- MYSQL_SCHEMA_VERSION_MINOR);
-
- std::string timer_name;
- bool retry = false;
- if (parameters.count("retry-on-startup")) {
- if (parameters.at("retry-on-startup") == "true") {
- retry = true;
- }
- }
+ // retry-on-startup enabled. Ensure schema version with timer name set / retries.
if (retry) {
- timer_name = timer_name_;
- }
-
- std::pair<uint32_t, uint32_t> db_version = getVersion(timer_name);
- if (code_version != db_version) {
- isc_throw(DbOpenError,
- "MySQL schema version mismatch: need version: "
- << code_version.first << "." << code_version.second
- << " found version: " << db_version.first << "."
- << db_version.second);
+ ensureSchemaVersion();
}
// Create an initial context.
@@ -3889,6 +3879,16 @@ MySqlLeaseMgr::getDescription() const {
return (std::string("MySQL Database"));
}
+void
+MySqlLeaseMgr::ensureSchemaVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_VERSION);
+
+ IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService));
+ DbCallback cb(&MySqlLeaseMgr::dbReconnect);
+
+ return (MySqlConnection::ensureSchemaVersion(parameters_, ac, cb, timer_name_));
+}
+
std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion(const string& timer_name) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_VERSION);
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index af1c964eaf..7f6cb14130 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -700,6 +700,8 @@ public:
/// @return Description of the backend.
virtual std::string getDescription() const override;
+ void ensureSchemaVersion() const;
+
/// @brief Returns backend version.
///
/// @param timer_name The DB reconnect timer name.
diff --git a/src/lib/mysql/Makefile.am b/src/lib/mysql/Makefile.am
index d3ff255fdb..f8a41b8009 100644
--- a/src/lib/mysql/Makefile.am
+++ b/src/lib/mysql/Makefile.am
@@ -1,6 +1,8 @@
SUBDIRS = . testutils tests
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS =
+AM_CPPFLAGS += -DKEA_ADMIN=\"@prefix@/sbin/kea-admin\"
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
AM_CXXFLAGS = $(KEA_CXXFLAGS)
diff --git a/src/lib/mysql/mysql_connection.cc b/src/lib/mysql/mysql_connection.cc
index 12234774b9..d3a13aec43 100644
--- a/src/lib/mysql/mysql_connection.cc
+++ b/src/lib/mysql/mysql_connection.cc
@@ -6,6 +6,8 @@
#include <config.h>
+#include <asiolink/io_service.h>
+#include <asiolink/process_spawn.h>
#include <database/database_connection.h>
#include <database/db_log.h>
#include <exceptions/exceptions.h>
@@ -15,11 +17,12 @@
#include <boost/lexical_cast.hpp>
#include <algorithm>
-#include <stdint.h>
-#include <string>
+#include <cstdint>
#include <limits>
+#include <string>
using namespace isc;
+using namespace isc::asiolink;
using namespace std;
namespace isc {
@@ -366,6 +369,101 @@ MySqlConnection::getVersion(const ParameterMap& parameters,
}
}
+void
+MySqlConnection::ensureSchemaVersion(const ParameterMap& parameters,
+ const IOServiceAccessorPtr& ac,
+ const DbCallback& cb,
+ const string& timer_name) {
+ pair<uint32_t, uint32_t> schema_version;
+ try {
+ schema_version = getVersion(parameters, ac, cb, timer_name);
+ } catch (DbOpenError const& exception) {
+ // Do nothing for open errors. We first need to establish a connection,
+ // and only afterwards can we initialize the schema if it still fails.
+ // Let it fail, or retry if retry is configured.
+ } catch (exception const& exception) {
+ // This may fail for a variety of reasons. We don't have to necessarily
+ // check for the error that is most common in situations where the
+ // database is not initialized which would sound something like
+ // "table schema_version does not exist". If the error had another
+ // cause, it will fail again during initialization or during the
+ // subsequent version retrieval and that is fine.
+ initializeSchema(parameters);
+
+ // Retrieve again because the initial retrieval failed.
+ schema_version = getVersion(parameters, ac, cb, timer_name);
+ }
+
+ // Check that the versions match.
+ pair<uint32_t, uint32_t> const expected_version(MYSQL_SCHEMA_VERSION_MAJOR,
+ MYSQL_SCHEMA_VERSION_MINOR);
+ if (schema_version != expected_version) {
+ isc_throw(DbOpenError, "MySQL schema version mismatch: expected version: "
+ << expected_version.first << "." << expected_version.second
+ << ", found version: " << schema_version.first << "."
+ << schema_version.second);
+ }
+}
+
+void
+MySqlConnection::initializeSchema(const ParameterMap& parameters) {
+ IOServicePtr io_service(new IOService());
+ ProcessSpawn kea_admin(io_service, KEA_ADMIN, kea_admin_parameters);
+ DB_LOG_INFO(MYSQL_INITIALIZE_SCHEMA).arg(kea_admin.getCommandLine());
+ pid_t const pid(kea_admin.spawn());
+ io_service->runOne();
+ if (kea_admin.isRunning(pid)) {
+ // TODO: implement synchronous process spawning. Otherwise kea-admin is not waited by the
+ // parent process, and it becomes a zombie, even though its work is finished. Uncomment the
+ // following throw when that is done.
+ // isc_throw(SchemaInitializationFailed, "kea-admin still running");
+ }
+ int const exit_code(kea_admin.getExitStatus(pid));
+ if (exit_code != 0) {
+ isc_throw(SchemaInitializationFailed, "Expected exit code 0. Got " << exit_code);
+ }
+}
+
+vector<string> MySqlConnection::toKeaAdminParameters(ParameterMap const& params) {
+ vector<string> result{"mysql"};
+ for (auto const& p : params) {
+ string const& keyword(p.first);
+ string const& value(p.second);
+
+ // These Kea parameters are the same as the kea-admin parameters.
+ if (keyword == "user" ||
+ keyword == "password" ||
+ keyword == "host" ||
+ keyword == "port" ||
+ keyword == "name"||
+ keyword == "connect-timeout") {
+ result.push_back("--" + keyword);
+ result.push_back(value);
+ continue;
+ }
+
+ // These Kea parameters do not have a direct kea-admin equivalent.
+ // But they do have a mariadb client flag equivalent.
+ // We pass them to kea-admin using the --extra flag.
+ static unordered_map<string, string> conversions{
+ {"cihper-list", "ssl-cipher"},
+ {"cert-file", "ssl-cert"},
+ {"key-file", "ssl-key"},
+ {"trust-anchor", "ssl-ca"},
+ };
+ bool extra_flag_added(false);
+ if (conversions.count(keyword)) {
+ if (!extra_flag_added) {
+ result.push_back("--extra");
+ extra_flag_added = true;
+ }
+ result.push_back("--" + conversions.at(keyword));
+ result.push_back(value);
+ }
+ }
+ return result;
+}
+
// Prepared statement setup. The textual form of an SQL statement is stored
// in a vector of strings (text_statements_) and is used in the output of
// error messages. The SQL statement is also compiled into a "prepared
diff --git a/src/lib/mysql/mysql_connection.h b/src/lib/mysql/mysql_connection.h
index bd70f411ea..f3188c5fe5 100644
--- a/src/lib/mysql/mysql_connection.h
+++ b/src/lib/mysql/mysql_connection.h
@@ -275,6 +275,29 @@ public:
const DbCallback& cb = DbCallback(),
const std::string& timer_name = std::string());
+ /// @brief Retrieve schema version, validate it against the hardcoded
+ /// version, and attempt to initialize the schema if there is an
+ /// error during retrieval.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::db::ScehamInitializationFailed if the initialization fails
+ static void
+ ensureSchemaVersion(const ParameterMap& parameters,
+ const IOServiceAccessorPtr& ac = IOServiceAccessorPtr(),
+ const DbCallback& cb = DbCallback(),
+ const std::string& timer_name = std::string());
+
+ /// @brief Initialize schema.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::db::ScehamInitializationFailed if the initialization fails
+ static void
+ initializeSchema(const ParameterMap& parameters);
+
/// @brief Prepare Single Statement
///
/// Creates a prepared statement from the text given and adds it to the