diff options
author | Andrei Pavel <andrei@isc.org> | 2023-11-20 11:21:45 +0100 |
---|---|---|
committer | Andrei Pavel <andrei@isc.org> | 2024-02-22 08:57:35 +0100 |
commit | 8e2d02205c5aab819ef03b6e7c9fa15495a55821 (patch) | |
tree | 33c10277366f9b964171965757c54758f6d170b3 /src/lib | |
parent | [#3130] trim trailing slash (diff) | |
download | kea-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.h | 8 | ||||
-rw-r--r-- | src/lib/dhcpsrv/mysql_lease_mgr.cc | 44 | ||||
-rw-r--r-- | src/lib/dhcpsrv/mysql_lease_mgr.h | 2 | ||||
-rw-r--r-- | src/lib/mysql/Makefile.am | 4 | ||||
-rw-r--r-- | src/lib/mysql/mysql_connection.cc | 102 | ||||
-rw-r--r-- | src/lib/mysql/mysql_connection.h | 23 |
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 |