From: Marcin Siodelski Date: Tue, 28 Aug 2018 09:56:19 +0000 (+0200) Subject: [#92,!13] New libkea-database library created. X-Git-Tag: gitlab116_base~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b2555aa662c828ed2cc94e99d2957426fd24ad3;p=thirdparty%2Fkea.git [#92,!13] New libkea-database library created. --- diff --git a/configure.ac b/configure.ac index 25323f8baf..bb8dc7f0f2 100644 --- a/configure.ac +++ b/configure.ac @@ -1534,6 +1534,8 @@ AC_CONFIG_FILES([Makefile src/lib/config/tests/testdata/Makefile src/lib/cryptolink/Makefile src/lib/cryptolink/tests/Makefile + src/lib/database/Makefile + src/lib/database/tests/Makefile src/lib/dhcp/Makefile src/lib/dhcp/tests/Makefile src/lib/dhcp_ddns/Makefile diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 9de3269c32..50ad1ec787 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -1,3 +1,4 @@ # The following build order must be maintained. -SUBDIRS = exceptions util log cryptolink dns asiolink cc testutils hooks dhcp \ - config stats asiodns dhcp_ddns eval dhcpsrv cfgrpt process http +SUBDIRS = exceptions util log cryptolink dns asiolink cc database testutils \ + hooks dhcp config stats asiodns dhcp_ddns eval dhcpsrv cfgrpt \ + process http diff --git a/src/lib/database/Makefile.am b/src/lib/database/Makefile.am new file mode 100644 index 0000000000..fac6488b2f --- /dev/null +++ b/src/lib/database/Makefile.am @@ -0,0 +1,30 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +lib_LTLIBRARIES = libkea-database.la +libkea_database_la_SOURCES = database_connection.cc database_connection.h +libkea_database_la_SOURCES += db_exceptions.h +libkea_database_la_SOURCES += db_log.cc db_log.h + +libkea_database_la_LIBADD = $(top_builddir)/src/lib/log/libkea-log.la +libkea_database_la_LIBADD += $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.la +libkea_database_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la +libkea_database_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_database_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libkea_database_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_database_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) + +libkea_database_la_LDFLAGS = -no-undefined -version-info 0:0:0 + +# The message file should be in the distribution. +#EXTRA_DIST = config_backend.dox + +CLEANFILES = *.gcno *.gcda + +# Specify the headers for copying into the installation directory tree. +#libkea_cb_includedir = $(pkgincludedir)/config +#libkea_cb_include_HEADERS = diff --git a/src/lib/database/database_connection.cc b/src/lib/database/database_connection.cc new file mode 100644 index 0000000000..2f736e2e42 --- /dev/null +++ b/src/lib/database/database_connection.cc @@ -0,0 +1,159 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; + +namespace isc { +namespace db { + +const time_t DatabaseConnection::MAX_DB_TIME = 2147483647; + +std::string +DatabaseConnection::getParameter(const std::string& name) const { + ParameterMap::const_iterator param = parameters_.find(name); + if (param == parameters_.end()) { + isc_throw(BadValue, "Parameter " << name << " not found"); + } + return (param->second); +} + +DatabaseConnection::ParameterMap +DatabaseConnection::parse(const std::string& dbaccess) { + DatabaseConnection::ParameterMap mapped_tokens; + + if (!dbaccess.empty()) { + vector tokens; + + // We need to pass a string to is_any_of, not just char*. Otherwise + // there are cryptic warnings on Debian6 running g++ 4.4 in + // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above + // array bounds" + boost::split(tokens, dbaccess, boost::is_any_of(string("\t "))); + BOOST_FOREACH(std::string token, tokens) { + size_t pos = token.find("="); + if (pos != string::npos) { + string name = token.substr(0, pos); + string value = token.substr(pos + 1); + mapped_tokens.insert(make_pair(name, value)); + } else { + DB_LOG_ERROR(DB_INVALID_ACCESS).arg(dbaccess); + isc_throw(InvalidParameter, "Cannot parse " << token + << ", expected format is name=value"); + } + } + } + + return (mapped_tokens); +} + +std::string +DatabaseConnection::redactedAccessString(const ParameterMap& parameters) { + // Reconstruct the access string: start of with an empty string, then + // work through all the parameters in the original string and add them. + std::string access; + for (DatabaseConnection::ParameterMap::const_iterator i = parameters.begin(); + i != parameters.end(); ++i) { + + // Separate second and subsequent tokens are preceded by a space. + if (!access.empty()) { + access += " "; + } + + // Append name of parameter... + access += i->first; + access += "="; + + // ... and the value, except in the case of the password, where a + // redacted value is appended. + if (i->first == std::string("password")) { + access += "*****"; + } else { + access += i->second; + } + } + + return (access); +} + +bool +DatabaseConnection::configuredReadOnly() const { + std::string readonly_value = "false"; + try { + readonly_value = getParameter("readonly"); + boost::algorithm::to_lower(readonly_value); + } catch (...) { + // Parameter "readonly" hasn't been specified so we simply use + // the default value of "false". + } + + if ((readonly_value != "false") && (readonly_value != "true")) { + isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value + << "' specified for boolean parameter 'readonly'"); + } + + return (readonly_value == "true"); +} + +ReconnectCtlPtr +DatabaseConnection::makeReconnectCtl() const { + ReconnectCtlPtr retry; + string type = "unknown"; + unsigned int retries = 0; + unsigned int interval = 0; + + // Assumes that parsing ensurse only valid values are present + try { + type = getParameter("type"); + } catch (...) { + // Wasn't specified so we'll use default of "unknown". + } + + std::string parm_str; + try { + parm_str = getParameter("max-reconnect-tries"); + retries = boost::lexical_cast(parm_str); + } catch (...) { + // Wasn't specified so we'll use default of 0; + } + + try { + parm_str = getParameter("reconnect-wait-time"); + interval = boost::lexical_cast(parm_str); + } catch (...) { + // Wasn't specified so we'll use default of 0; + } + + retry.reset(new ReconnectCtl(type, retries, interval)); + return (retry); +} + +bool +DatabaseConnection::invokeDbLostCallback() const { + if (DatabaseConnection::db_lost_callback) { + // Invoke the callback, passing in a new instance of ReconnectCtl + return (DatabaseConnection::db_lost_callback)(makeReconnectCtl()); + } + + return (false); +} + + +DatabaseConnection::DbLostCallback +DatabaseConnection::db_lost_callback = 0; + +}; +}; diff --git a/src/lib/database/database_connection.h b/src/lib/database/database_connection.h new file mode 100644 index 0000000000..515e20c5b5 --- /dev/null +++ b/src/lib/database/database_connection.h @@ -0,0 +1,236 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DATABASE_CONNECTION_H +#define DATABASE_CONNECTION_H + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace db { + +/// @brief Exception thrown if name of database is not specified +class NoDatabaseName : public Exception { +public: + NoDatabaseName(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Exception thrown on failure to open database +class DbOpenError : public Exception { +public: + DbOpenError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Exception thrown on failure to execute a database function +class DbOperationError : public Exception { +public: + DbOperationError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Invalid type exception +/// +/// Thrown when the factory doesn't recognize the type of the backend. +class InvalidType : public Exception { +public: + InvalidType(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Invalid Timeout +/// +/// Thrown when the timeout specified for the database connection is invalid. +class DbInvalidTimeout : public Exception { +public: + DbInvalidTimeout(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Invalid 'readonly' value specification. +/// +/// Thrown when the value of the 'readonly' boolean parameter is invalid. +class DbInvalidReadOnly : public Exception { +public: + DbInvalidReadOnly(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Warehouses DB reconnect control values +/// +/// When a DatabaseConnection loses connectivity to its backend, it +/// creates an instance of this class based on its configuration parameters and +/// passes the instance into connection's DB lost callback. This allows +/// the layer(s) above the connection to know how to proceed. +/// +class ReconnectCtl { +public: + /// @brief Constructor + /// @param backend_type type of the caller backend. + /// @param max_retries maximum number of reconnect attempts to make + /// @param retry_interval amount of time to between reconnect attempts + ReconnectCtl(const std::string& backend_type, unsigned int max_retries, + unsigned int retry_interval) + : backend_type_(backend_type), max_retries_(max_retries), + retries_left_(max_retries), retry_interval_(retry_interval) {} + + /// @brief Returns the type of the caller backend. + std::string backendType() const { + return (backend_type_); + } + + /// @brief Decrements the number of retries remaining + /// + /// Each call decrements the number of retries by one until zero is reached. + /// @return true the number of retries remaining is greater than zero. + bool checkRetries() { + return (retries_left_ ? --retries_left_ : false); + } + + /// @brief Returns the maximum number for retries allowed + unsigned int maxRetries() { + return (max_retries_); + } + + /// @brief Returns the number for retries remaining + unsigned int retriesLeft() { + return (retries_left_); + } + + /// @brief Returns the amount of time to wait between reconnect attempts + unsigned int retryInterval() { + return (retry_interval_); + } + +private: + /// @brief Caller backend type. + const std::string backend_type_; + + /// @brief Maximum number of retry attempts to make + unsigned int max_retries_; + + /// @brief Number of attempts remaining + unsigned int retries_left_; + + /// @brief The amount of time to wait between reconnect attempts + unsigned int retry_interval_; +}; + +/// @brief Pointer to an instance of ReconnectCtl +typedef boost::shared_ptr ReconnectCtlPtr; + +/// @brief Common database connection class. +/// +/// This class provides functions that are common for establishing +/// connection with different types of databases; enables operations +/// on access parameters strings. In particular, it provides a way +/// to parse parameters in key=value format. This class is expected +/// to be a base class for all @ref LeaseMgr and possibly +/// @ref BaseHostDataSource derived classes. +class DatabaseConnection : public boost::noncopyable { +public: + + /// @brief Defines maximum value for time that can be reliably stored. + /// + /// @todo: Is this common for MySQL and Postgres? Maybe we should have + /// specific values for each backend? + /// + /// If I'm still alive I'll be too old to care. You fix it. + static const time_t MAX_DB_TIME; + + /// @brief Database configuration parameter map + typedef std::map ParameterMap; + + /// @brief Constructor + /// + /// @param parameters A data structure relating keywords and values + /// concerned with the database. + DatabaseConnection(const ParameterMap& parameters) + :parameters_(parameters) { + } + + /// @brief Destructor + virtual ~DatabaseConnection(){}; + + /// @brief Instantiates a ReconnectCtl based on the connection's + /// reconnect parameters + /// @return pointer to the new ReconnectCtl object + virtual ReconnectCtlPtr makeReconnectCtl() const; + + /// @brief Returns value of a connection parameter. + /// + /// @param name Name of the parameter which value should be returned. + /// @return Value of one of the connection parameters. + /// @throw BadValue if parameter is not found + std::string getParameter(const std::string& name) const; + + /// @brief Parse database access string + /// + /// Parses the string of "keyword=value" pairs and separates them + /// out into the map. + /// + /// @param dbaccess Database access string. + /// + /// @return @ref ParameterMap of keyword/value pairs. + static ParameterMap parse(const std::string& dbaccess); + + /// @brief Redact database access string + /// + /// Takes the database parameters and returns a database access string + /// passwords replaced by asterisks. This string is used in log messages. + /// + /// @param parameters Database access parameters (output of "parse"). + /// + /// @return Redacted database access string. + static std::string redactedAccessString(const ParameterMap& parameters); + + /// @brief Convenience method checking if database should be opened with + /// read only access. + /// + /// @return true if "readonly" parameter is specified and set to true; + /// false if "readonly" parameter is not specified or it is specified + /// and set to false. + bool configuredReadOnly() const; + + /// @brief Defines a callback prototype for propogating events upward + typedef boost::function DbLostCallback; + + /// @brief Invokes the connection's lost connectivity callback + /// + /// This function may be called by derivations when the connectivity + /// to their data server is lost. If connectivity callback was specified, + /// this function will instantiate a ReconnectCtl and pass it to the + /// callback. + /// + /// @return Returns the result of the callback or false if there is no + /// callback. + bool invokeDbLostCallback() const; + + /// @brief Optional call back function to invoke if a successfully + /// open connection subsequently fails + static DbLostCallback db_lost_callback; + +private: + + /// @brief List of parameters passed in dbconfig + /// + /// That will be mostly used for storing database name, username, + /// password and other parameters required for DB access. It is not + /// intended to keep any DHCP-related parameters. + ParameterMap parameters_; + +}; + +}; // end of isc::db namespace +}; // end of isc namespace + +#endif // DATABASE_CONNECTION_H diff --git a/src/lib/dhcpsrv/db_exceptions.h b/src/lib/database/db_exceptions.h similarity index 98% rename from src/lib/dhcpsrv/db_exceptions.h rename to src/lib/database/db_exceptions.h index 1bf92a38bf..04b216bfc1 100644 --- a/src/lib/dhcpsrv/db_exceptions.h +++ b/src/lib/database/db_exceptions.h @@ -10,7 +10,7 @@ #include namespace isc { -namespace dhcp { +namespace db { /// @brief Database statement not applied /// @@ -75,6 +75,6 @@ public: }; } // namespace isc -} // namespace dhcp +} // namespace db #endif diff --git a/src/lib/dhcpsrv/db_log.cc b/src/lib/database/db_log.cc similarity index 88% rename from src/lib/dhcpsrv/db_log.cc rename to src/lib/database/db_log.cc index f2e3cd0a42..75ee637d2a 100644 --- a/src/lib/dhcpsrv/db_log.cc +++ b/src/lib/database/db_log.cc @@ -9,13 +9,14 @@ #include #include -#include -#include +#include using namespace isc::log; namespace isc { -namespace dhcp { +namespace db { + +DbLoggerStack db_logger_stack; const MessageID& DbLogger::translateMessage(const DbMessageID& id) const { @@ -32,5 +33,5 @@ void checkDbLoggerStack() { } } -} // namespace dhcp +} // namespace db } // namespace isc diff --git a/src/lib/dhcpsrv/db_log.h b/src/lib/database/db_log.h similarity index 99% rename from src/lib/dhcpsrv/db_log.h rename to src/lib/database/db_log.h index 4eb76d6a0a..de7bd9f93f 100644 --- a/src/lib/dhcpsrv/db_log.h +++ b/src/lib/database/db_log.h @@ -24,7 +24,7 @@ /// logger with mapped messages. namespace isc { -namespace dhcp { +namespace db { ///@{ /// @brief Database logging levels @@ -146,7 +146,7 @@ void checkDbLoggerStack(); ///@} -} // namespace dhcp +} // namespace db } // namespace isc #endif // DB_LOG_H diff --git a/src/lib/database/tests/Makefile.am b/src/lib/database/tests/Makefile.am new file mode 100644 index 0000000000..f38929b259 --- /dev/null +++ b/src/lib/database/tests/Makefile.am @@ -0,0 +1,39 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\" + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda + +TESTS_ENVIRONMENT = \ + $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST +TESTS += libdatabase_unittests + +libdatabase_unittests_SOURCES = database_connection_unittest.cc +libdatabase_unittests_SOURCES += run_unittests.cc + +libdatabase_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +libdatabase_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) + +libdatabase_unittests_LDADD = $(top_builddir)/src/lib/database/libkea-database.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libdatabase_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libdatabase_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD) + +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/database/tests/database_connection_unittest.cc b/src/lib/database/tests/database_connection_unittest.cc new file mode 100644 index 0000000000..a56469750f --- /dev/null +++ b/src/lib/database/tests/database_connection_unittest.cc @@ -0,0 +1,249 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include + +#include + +using namespace isc::db; + +/// @brief Test fixture for exercising DbLostCallback invocation +class DatabaseConnectionCallbackTest : public ::testing::Test { +public: + /// Constructor + DatabaseConnectionCallbackTest() + : db_reconnect_ctl_(0) { + } + + /// @brief Callback to register with a DatabaseConnection + /// + /// @param db_reconnect_ctl ReconnectCtl containing reconnect + /// parameters + bool dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) { + if (!db_reconnect_ctl) { + isc_throw(isc::BadValue, "db_reconnect_ctl should not be null"); + } + + db_reconnect_ctl_ = db_reconnect_ctl; + return (true); + } + + /// @brief Retainer for the control passed into the callback + ReconnectCtlPtr db_reconnect_ctl_; +}; + +/// @brief getParameter test +/// +/// This test checks if the LeaseMgr can be instantiated and that it +/// parses parameters string properly. +TEST(DatabaseConnectionTest, getParameter) { + + DatabaseConnection::ParameterMap pmap; + pmap[std::string("param1")] = std::string("value1"); + pmap[std::string("param2")] = std::string("value2"); + DatabaseConnection datasrc(pmap); + + EXPECT_EQ("value1", datasrc.getParameter("param1")); + EXPECT_EQ("value2", datasrc.getParameter("param2")); + EXPECT_THROW(datasrc.getParameter("param3"), isc::BadValue); +} + +/// @brief NoDbLostCallback +/// +/// This test verifies that DatabaseConnection::invokeDbLostCallback +/// returns a false if there is connection has no registered +/// DbLostCallback. +TEST_F(DatabaseConnectionCallbackTest, NoDbLostCallback) { + DatabaseConnection::ParameterMap pmap; + pmap[std::string("type")] = std::string("test"); + pmap[std::string("max-reconnect-tries")] = std::string("3"); + pmap[std::string("reconnect-wait-time")] = std::string("60"); + DatabaseConnection datasrc(pmap); + + bool ret = false; + ASSERT_NO_THROW(ret = datasrc.invokeDbLostCallback()); + EXPECT_FALSE(ret); + EXPECT_FALSE(db_reconnect_ctl_); +} + +/// @brief dbLostCallback +/// +/// This test verifies that DatabaseConnection::invokeDbLostCallback +/// safely invokes the registered DbLostCallback. It also tests +/// operation of DbReconnectCtl retry accounting methods. +TEST_F(DatabaseConnectionCallbackTest, dbLostCallback) { + /// Create a Database configuration that includes the reconnect + /// control parameters. + DatabaseConnection::ParameterMap pmap; + pmap[std::string("type")] = std::string("test"); + pmap[std::string("max-reconnect-tries")] = std::string("3"); + pmap[std::string("reconnect-wait-time")] = std::string("60"); + + /// Install the callback. + DatabaseConnection::db_lost_callback = + boost::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, _1); + /// Create the connection.. + DatabaseConnection datasrc(pmap); + + /// We should be able to invoke the callback and glean + /// the correct reconnect contorl parameters from it. + bool ret = false; + ASSERT_NO_THROW(ret = datasrc.invokeDbLostCallback()); + EXPECT_TRUE(ret); + ASSERT_TRUE(db_reconnect_ctl_); + ASSERT_EQ("test", db_reconnect_ctl_->backendType()); + ASSERT_EQ(3, db_reconnect_ctl_->maxRetries()); + ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft()); + EXPECT_EQ(60, db_reconnect_ctl_->retryInterval()); + + /// Verify that checkRetries() correctly decrements + /// down to zero, and that retriesLeft() returns + /// the correct value. + for (int i = 3; i > 1 ; --i) { + ASSERT_EQ(i, db_reconnect_ctl_->retriesLeft()); + ASSERT_TRUE(db_reconnect_ctl_->checkRetries()); + } + + /// Retries are exhausted, verify that's reflected. + EXPECT_FALSE(db_reconnect_ctl_->checkRetries()); + EXPECT_EQ(0, db_reconnect_ctl_->retriesLeft()); + EXPECT_EQ(3, db_reconnect_ctl_->maxRetries()); +} + +// This test checks that a database access string can be parsed correctly. +TEST(DatabaseConnectionTest, parse) { + + DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse( + "user=me password=forbidden name=kea somethingelse= type=mysql"); + + EXPECT_EQ(5, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("forbidden", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + EXPECT_EQ("", parameters["somethingelse"]); +} + +// This test checks that an invalid database access string behaves as expected. +TEST(DatabaseConnectionTest, parseInvalid) { + + // No tokens in the string, so we expect no parameters + std::string invalid = ""; + DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(invalid); + EXPECT_EQ(0, parameters.size()); + + // With spaces, there are some tokens so we expect invalid parameter + // as there are no equals signs. + invalid = " \t "; + EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter); + + invalid = " noequalshere "; + EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter); + + // A single "=" is valid string, but is placed here as the result is + // expected to be nothing. + invalid = "="; + parameters = DatabaseConnection::parse(invalid); + EXPECT_EQ(1, parameters.size()); + EXPECT_EQ("", parameters[""]); +} + +/// @brief redactConfigString test +/// +/// Checks that the redacted configuration string includes the password only +/// as a set of asterisks. +TEST(DatabaseConnectionTest, redactAccessString) { + + DatabaseConnection::ParameterMap parameters = + DatabaseConnection::parse("user=me password=forbidden name=kea type=mysql"); + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("forbidden", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + + // Redact the result. To check, break the redacted string down into its + // components. + std::string redacted = DatabaseConnection::redactedAccessString(parameters); + parameters = DatabaseConnection::parse(redacted); + + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("*****", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); +} + +/// @brief redactConfigString test - empty password +/// +/// Checks that the redacted configuration string includes the password only +/// as a set of asterisks, even if the password is null. +TEST(DatabaseConnectionTest, redactAccessStringEmptyPassword) { + + DatabaseConnection::ParameterMap parameters = + DatabaseConnection::parse("user=me name=kea type=mysql password="); + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + + // Redact the result. To check, break the redacted string down into its + // components. + std::string redacted = DatabaseConnection::redactedAccessString(parameters); + parameters = DatabaseConnection::parse(redacted); + + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("*****", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + + // ... and again to check that the position of the empty password in the + // string does not matter. + parameters = DatabaseConnection::parse("user=me password= name=kea type=mysql"); + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + + redacted = DatabaseConnection::redactedAccessString(parameters); + parameters = DatabaseConnection::parse(redacted); + + EXPECT_EQ(4, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("*****", parameters["password"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); +} + +/// @brief redactConfigString test - no password +/// +/// Checks that the redacted configuration string excludes the password if there +/// was no password to begin with. +TEST(DatabaseConnectionTest, redactAccessStringNoPassword) { + + DatabaseConnection::ParameterMap parameters = + DatabaseConnection::parse("user=me name=kea type=mysql"); + EXPECT_EQ(3, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); + + // Redact the result. To check, break the redacted string down into its + // components. + std::string redacted = DatabaseConnection::redactedAccessString(parameters); + parameters = DatabaseConnection::parse(redacted); + + EXPECT_EQ(3, parameters.size()); + EXPECT_EQ("me", parameters["user"]); + EXPECT_EQ("kea", parameters["name"]); + EXPECT_EQ("mysql", parameters["type"]); +} diff --git a/src/lib/database/tests/libdatabase_unittests b/src/lib/database/tests/libdatabase_unittests new file mode 100755 index 0000000000..aaba95c2f8 --- /dev/null +++ b/src/lib/database/tests/libdatabase_unittests @@ -0,0 +1,210 @@ +#! /bin/sh + +# libdatabase_unittests - temporary wrapper script for .libs/libdatabase_unittests +# Generated by libtool (GNU libtool) 2.4.6 +# +# The libdatabase_unittests program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Be Bourne compatible +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command="" + +# This environment variable determines our operation mode. +if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then + # install mode needs the following variables: + generated_by_libtool_version='2.4.6' + notinst_deplibs=' ../../../../src/lib/database/libkea-database.la /Users/marcin/devel/kea/src/lib/log/libkea-log.la /Users/marcin/devel/kea/src/lib/asiolink/libkea-asiolink.la ../../../../src/lib/log/libkea-log.la /Users/marcin/devel/kea/src/lib/util/threads/libkea-threads.la /Users/marcin/devel/kea/src/lib/util/libkea-util.la ../../../../src/lib/util/threads/libkea-threads.la ../../../../src/lib/util/libkea-util.la ../../../../src/lib/asiolink/libkea-asiolink.la /Users/marcin/devel/kea/src/lib/exceptions/libkea-exceptions.la ../../../../src/lib/exceptions/libkea-exceptions.la' +else + # When we are sourced in execute mode, $file and $ECHO are already set. + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + file="$0" + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + ECHO="printf %s\\n" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string --lt- +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's ../../../../libtool value, followed by no. +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=$0 + shift + for lt_opt + do + case "$lt_opt" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'` + test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=. + lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'` + cat "$lt_dump_D/$lt_dump_F" + exit 0 + ;; + --lt-*) + $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n "$lt_option_debug"; then + echo "libdatabase_unittests:libdatabase_unittests:$LINENO: libtool wrapper (GNU libtool) 2.4.6" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + $ECHO "libdatabase_unittests:libdatabase_unittests:$LINENO: newargv[$lt_dump_args_N]: $lt_arg" + lt_dump_args_N=`expr $lt_dump_args_N + 1` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ + + if test -n "$lt_option_debug"; then + $ECHO "libdatabase_unittests:libdatabase_unittests:$LINENO: newargv[0]: $progdir/$program" 1>&2 + func_lt_dump_args ${1+"$@"} 1>&2 + fi + exec "$progdir/$program" ${1+"$@"} + + $ECHO "$0: cannot exec $program $*" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from $@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case " $* " in + *\ --lt-*) + for lt_wr_arg + do + case $lt_wr_arg in + --lt-*) ;; + *) set x "$@" "$lt_wr_arg"; shift;; + esac + shift + done ;; + esac + func_exec_program_core ${1+"$@"} +} + + # Parse options + func_parse_lt_options "$0" ${1+"$@"} + + # Find the directory that this script lives in. + thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` + test "x$thisdir" = "x$file" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'` + while test -n "$file"; do + destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'` + + # If there was a directory component, then change thisdir. + if test "x$destdir" != "x$file"; then + case "$destdir" in + [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;; + *) thisdir="$thisdir/$destdir" ;; + esac + fi + + file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'` + file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no + if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then + # special case for '.' + if test "$thisdir" = "."; then + thisdir=`pwd` + fi + # remove .libs from thisdir + case "$thisdir" in + *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;; + .libs ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=`cd "$thisdir" && pwd` + test -n "$absdir" && thisdir="$absdir" + + program='libdatabase_unittests' + progdir="$thisdir/.libs" + + + if test -f "$progdir/$program"; then + # Add our own library path to DYLD_LIBRARY_PATH + DYLD_LIBRARY_PATH="/Users/marcin/devel/kea/src/lib/database/.libs:/Users/marcin/devel/kea/src/lib/log/.libs:/Users/marcin/devel/kea/src/lib/asiolink/.libs:/Users/marcin/devel/kea/src/lib/util/threads/.libs:/Users/marcin/devel/kea/src/lib/util/.libs:/Users/marcin/devel/kea/src/lib/exceptions/.libs:$DYLD_LIBRARY_PATH" + + # Some systems cannot cope with colon-terminated DYLD_LIBRARY_PATH + # The second colon is a workaround for a bug in BeOS R4 sed + DYLD_LIBRARY_PATH=`$ECHO "$DYLD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'` + + export DYLD_LIBRARY_PATH + + if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then + # Run the actual program with our arguments. + func_exec_program ${1+"$@"} + fi + else + # The program doesn't exist. + $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2 + $ECHO "This script is just a wrapper for $program." 1>&2 + $ECHO "See the libtool documentation for more information." 1>&2 + exit 1 + fi +fi diff --git a/src/lib/database/tests/run_unittests.cc b/src/lib/database/tests/run_unittests.cc new file mode 100644 index 0000000000..4e83d4bd6c --- /dev/null +++ b/src/lib/database/tests/run_unittests.cc @@ -0,0 +1,20 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::log::initLogger(); + + int result = RUN_ALL_TESTS(); + + return (result); +} diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 8432c7265a..77817aef12 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -115,9 +115,6 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h -libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h -libkea_dhcpsrv_la_SOURCES += db_exceptions.h -libkea_dhcpsrv_la_SOURCES += db_log.cc db_log.h libkea_dhcpsrv_la_SOURCES += db_type.h libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h @@ -220,6 +217,7 @@ libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la diff --git a/src/lib/dhcpsrv/dhcpsrv_db_log.cc b/src/lib/dhcpsrv/dhcpsrv_db_log.cc index 013d8c469d..4afff06545 100644 --- a/src/lib/dhcpsrv/dhcpsrv_db_log.cc +++ b/src/lib/dhcpsrv/dhcpsrv_db_log.cc @@ -11,6 +11,8 @@ #include #include +using namespace isc::db; + namespace isc { namespace dhcp { @@ -38,7 +40,7 @@ const DbLogger::MessageMap dhcpsrv_db_message_map = { DbLogger dhcpsrv_db_logger(dhcpsrv_logger, dhcpsrv_db_message_map); // Do this initialization here! -DbLoggerStack db_logger_stack = { dhcpsrv_db_logger }; +//DbLoggerStack db_logger_stack = { dhcpsrv_db_logger }; } // namespace dhcp diff --git a/src/lib/dhcpsrv/dhcpsrv_db_log.h b/src/lib/dhcpsrv/dhcpsrv_db_log.h index 5965350cfe..d5056243fd 100644 --- a/src/lib/dhcpsrv/dhcpsrv_db_log.h +++ b/src/lib/dhcpsrv/dhcpsrv_db_log.h @@ -7,18 +7,18 @@ #ifndef DHCPSRV_DB_LOG_H #define DHCPSRV_DB_LOG_H -#include +#include namespace isc { namespace dhcp { /// @brief DHCP server database message map -extern const DbLogger::MessageMap dhcpsrv_db_message_map; +extern const db::DbLogger::MessageMap dhcpsrv_db_message_map; /// @brief DHCP server database Logger /// /// It is the default database logger. -extern DbLogger dhcpsrv_db_logger; +extern db::DbLogger dhcpsrv_db_logger; } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index e5759973fd..167645ee32 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -9,12 +9,12 @@ #include #include +#include #include #include #include #include #include -#include #include #include diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc index a4de082a5f..7929aa3924 100644 --- a/src/lib/dhcpsrv/mysql_connection.cc +++ b/src/lib/dhcpsrv/mysql_connection.cc @@ -7,7 +7,7 @@ #include -#include +#include #include #include diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h index 88716d26cd..5ad549d61d 100644 --- a/src/lib/dhcpsrv/mysql_connection.h +++ b/src/lib/dhcpsrv/mysql_connection.h @@ -7,8 +7,8 @@ #ifndef MYSQL_CONNECTION_H #define MYSQL_CONNECTION_H -#include -#include +#include +#include #include #include #include @@ -117,7 +117,7 @@ public: /// @throw DbOpenError Unable to initialize MySql handle. MySqlHolder() : mysql_(mysql_init(NULL)) { if (mysql_ == NULL) { - isc_throw(DbOpenError, "unable to initialize MySQL"); + isc_throw(db::DbOpenError, "unable to initialize MySQL"); } } @@ -208,7 +208,7 @@ private: /// to the database and preparing compiled statements. Its fields are /// public, because they are used (both set and retrieved) in classes /// that use instances of MySqlConnection. -class MySqlConnection : public DatabaseConnection { +class MySqlConnection : public db::DatabaseConnection { public: /// @brief Constructor diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index fb6e8274a4..5e47c1dc12 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -7,8 +7,8 @@ #ifndef MYSQL_HOST_DATA_SOURCE_H #define MYSQL_HOST_DATA_SOURCE_H +#include #include -#include #include #include diff --git a/src/lib/dhcpsrv/pgsql_connection.h b/src/lib/dhcpsrv/pgsql_connection.h index 49fda1444e..081aeedd49 100644 --- a/src/lib/dhcpsrv/pgsql_connection.h +++ b/src/lib/dhcpsrv/pgsql_connection.h @@ -6,7 +6,7 @@ #ifndef PGSQL_CONNECTION_H #define PGSQL_CONNECTION_H -#include +#include #include #include