New parameters have been added for MySQL connection.
-// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 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
DatabaseConnection::ParameterMap values_copy = values_;
int64_t lfc_interval = 0;
- int64_t timeout = 0;
+ int64_t connect_timeout = 0;
+ int64_t read_timeout = 0;
+ int64_t write_timeout = 0;
int64_t port = 0;
int64_t max_reconnect_tries = 0;
int64_t reconnect_wait_time = 0;
boost::lexical_cast<std::string>(lfc_interval);
} else if (param.first == "connect-timeout") {
- timeout = param.second->intValue();
+ connect_timeout = param.second->intValue();
values_copy[param.first] =
- boost::lexical_cast<std::string>(timeout);
+ boost::lexical_cast<std::string>(connect_timeout);
+
+ } else if (param.first == "read-timeout") {
+ read_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(read_timeout);
+
+ } else if (param.first == "write-timeout") {
+ write_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(write_timeout);
} else if (param.first == "max-reconnect-tries") {
max_reconnect_tries = param.second->intValue();
<< " (" << value->getPosition() << ")");
}
- // d. Check that the timeout is within a reasonable range.
- if ((timeout < 0) ||
- (timeout > std::numeric_limits<uint32_t>::max())) {
+ // d. Check that the timeouts are within a reasonable range.
+ if ((connect_timeout < 0) ||
+ (connect_timeout > std::numeric_limits<uint32_t>::max())) {
ConstElementPtr value = database_config->get("connect-timeout");
- isc_throw(DbConfigError, "connect-timeout value: " << timeout
+ isc_throw(DbConfigError, "connect-timeout value: " << connect_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if ((read_timeout < 0) ||
+ (read_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("read-timeout");
+ isc_throw(DbConfigError, "read-timeout value: " << read_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if ((write_timeout < 0) ||
+ (write_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("write-timeout");
+ isc_throw(DbConfigError, "write-timeout value: " << write_timeout
<< " is out of range, expected value: 0.."
<< std::numeric_limits<uint32_t>::max()
<< " (" << value->getPosition() << ")");
-// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 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
bool quoteValue(const std::string& parameter) const {
return ((parameter != "persist") && (parameter != "lfc-interval") &&
(parameter != "connect-timeout") &&
+ (parameter != "read-timeout") &&
+ (parameter != "write-timeout") &&
(parameter != "port") &&
(parameter != "max-row-errors") &&
(parameter != "readonly"));
}
// This test checks that the parser accepts the valid value of the
-// timeout parameter.
-TEST_F(DbAccessParserTest, validTimeout) {
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, validConnectTimeout) {
const char* config[] = {"type", "memfile",
"name", "/opt/var/lib/kea/kea-leases6.csv",
"connect-timeout", "3600",
}
// This test checks that the parser rejects the negative value of the
-// timeout parameter.
-TEST_F(DbAccessParserTest, negativeTimeout) {
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, negativeConnectTimeout) {
const char* config[] = {"type", "memfile",
"name", "/opt/var/lib/kea/kea-leases6.csv",
"connect-timeout", "-1",
}
// This test checks that the parser rejects a too large (greater than
-// the max uint32_t) value of the timeout parameter.
-TEST_F(DbAccessParserTest, largeTimeout) {
+// the max uint32_t) value of the connecttimeout parameter.
+TEST_F(DbAccessParserTest, largeConnectTimeout) {
const char* config[] = {"type", "memfile",
"name", "/opt/var/lib/kea/kea-leases6.csv",
"connect-timeout", "4294967296",
EXPECT_THROW(parser.parse(json_elements), DbConfigError);
}
+// This test checks that the parser accepts the valid value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, validReadTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "read-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid read timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, negativeReadTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "read-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the read-timeout parameter.
+TEST_F(DbAccessParserTest, largeReadTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "read-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, validWriteTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "write-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid write timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, negativeWriteTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "write-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the write-timeout parameter.
+TEST_F(DbAccessParserTest, largeWriteTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "write-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
// This test checks that the parser accepts the valid value of the
// port parameter.
TEST_F(DbAccessParserTest, validPort) {
const char* VALID_TIMEOUT = "connect-timeout=10";
const char* INVALID_TIMEOUT_1 = "connect-timeout=foo";
const char* INVALID_TIMEOUT_2 = "connect-timeout=-17";
+const char* INVALID_TIMEOUT_3 = "connect-timeout=0";
+const char* VALID_READ_TIMEOUT = "read-timeout=11";
+const char* VALID_READ_TIMEOUT_ZERO = "read-timeout=0";
+const char* INVALID_READ_TIMEOUT_1 = "read-timeout=bar";
+const char* VALID_WRITE_TIMEOUT = "write-timeout=12";
+const char* VALID_WRITE_TIMEOUT_ZERO = "write-timeout=0";
+const char* INVALID_WRITE_TIMEOUT_1 = "write-timeout=baz";
const char* VALID_READONLY_DB = "readonly=true";
const char* INVALID_READONLY_DB = "readonly=5";
const char* VALID_CERT = "cert-file=" TEST_CA_DIR "/kea-client.crt";
extern const char* VALID_TIMEOUT;
extern const char* INVALID_TIMEOUT_1;
extern const char* INVALID_TIMEOUT_2;
+extern const char* INVALID_TIMEOUT_3;
+extern const char* VALID_READ_TIMEOUT;
+extern const char* VALID_READ_TIMEOUT_ZERO;
+extern const char* INVALID_READ_TIMEOUT_1;
+extern const char* VALID_WRITE_TIMEOUT;
+extern const char* VALID_WRITE_TIMEOUT_ZERO;
+extern const char* INVALID_WRITE_TIMEOUT_1;
extern const char* VALID_READONLY_DB;
extern const char* INVALID_READONLY_DB;
extern const char* VALID_CERT;
-// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 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
}
unsigned int port = 0;
- string sport;
- try {
- sport = getParameter("port");
- } catch (...) {
- // No port parameter, we are going to use the default port.
- sport = "";
- }
-
- if (sport.size() > 0) {
- // Port was given, so try to convert it to an integer.
-
- try {
- port = boost::lexical_cast<unsigned int>(sport);
- } catch (...) {
- // Port given but could not be converted to an unsigned int.
- // Just fall back to the default value.
- port = 0;
- }
-
- // The port is only valid when it is in the 0..65535 range.
- // Again fall back to the default when the given value is invalid.
- if (port > numeric_limits<uint16_t>::max()) {
- port = 0;
- }
- }
+ setIntParameterValue("port", 0, numeric_limits<unsigned int>::max(), port);
+ // The port is only valid when it is in the 0..65535 range.
+ // Again fall back to the default when the given value is invalid.
+ if (port > numeric_limits<uint16_t>::max()) {
+ port = 0;
+ }
const char* user = NULL;
string suser;
}
unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT;
- string stimeout;
+ unsigned int read_timeout = 0;
+ unsigned int write_timeout = 0;
try {
- stimeout = getParameter("connect-timeout");
- } catch (...) {
- // No timeout parameter, we are going to use the default timeout.
- stimeout = "";
- }
-
- if (stimeout.size() > 0) {
- // Timeout was given, so try to convert it to an integer.
-
- try {
- connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
- } catch (...) {
- // Timeout given but could not be converted to an unsigned int. Set
- // the connection timeout to an invalid value to trigger throwing
- // of an exception.
- connect_timeout = 0;
- }
-
// The timeout is only valid if greater than zero, as depending on the
// database, a zero timeout might signify something like "wait
// indefinitely".
- //
- // The check below also rejects a value greater than the maximum
- // integer value. The lexical_cast operation used to obtain a numeric
- // value from a string can get confused if trying to convert a negative
- // integer to an unsigned int: instead of throwing an exception, it may
- // produce a large positive value.
- if ((connect_timeout == 0) ||
- (connect_timeout > numeric_limits<int>::max())) {
- isc_throw(DbInvalidTimeout, "database connection timeout (" <<
- stimeout << ") must be an integer greater than 0");
- }
+ setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+ setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout);
+ setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidTimeout, ex.what());
}
const char* ca_file(0);
mysql_error(mysql_));
}
+ // Set the read timeout if it has been specified. Otherwise, the timeout is
+ // not used.
+ if (read_timeout > 0) {
+ result = mysql_options(mysql_, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set database read timeout: " <<
+ mysql_error(mysql_));
+ }
+ }
+
+ // Set the write timeout if it has been specified. Otherwise, the timeout
+ // is not used.
+ if (write_timeout > 0) {
+ result = mysql_options(mysql_, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set database write timeout: " <<
+ mysql_error(mysql_));
+ }
+ }
+
// If TLS is enabled set it. If something should go wrong it will happen
// later at the mysql_real_connect call.
if (tls_) {
}
}
+template<typename T>
+void
+MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
+ string svalue;
+ try {
+ svalue = getParameter(name);
+ } catch (...) {
+ // Do nothing if the parameter is not present.
+ }
+ if (svalue.empty()) {
+ return;
+ }
+ try {
+ // Try to convert the value.
+ auto parsed_value = boost::lexical_cast<T>(svalue);
+ // Check if the value is within the specified range.
+ if ((parsed_value < min) || (parsed_value > max)) {
+ isc_throw(BadValue, "bad " << svalue << " value");
+ }
+ // Everything is fine. Return the parsed value.
+ value = parsed_value;
+
+ } catch (...) {
+ // We may end up here when lexical_cast fails or when the
+ // parsed value is not within the desired range. In both
+ // cases let's throw the same general error.
+ isc_throw(BadValue, name << " parameter (" <<
+ svalue << ") must be an integer between "
+ << min << " and " << max);
+ }
+}
+
} // namespace db
} // namespace isc
return (cipher ? std::string(cipher) : "");
}
+private:
+
+ /// @brief Convenience function parsing and setting an integer parameter,
+ /// if it exists.
+ ///
+ /// If the parameter is not present, this function doesn't change the @c value.
+ /// Otherwise, it tries to convert the parameter to the type @c T. Finally,
+ /// it checks if the converted number is within the specified range.
+ ///
+ /// @param name Parameter name.
+ /// @param min Expected minimal value.
+ /// @param max Expected maximal value.
+ /// @param [out] value Reference to a value returning the parsed parameter.
+ /// @tparam T Parameter type.
+ /// @throw BadValue if the parameter is not a valid number or if it is out
+ /// of range.
+ template<typename T>
+ void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
+
+public:
+
/// @brief Prepared statements
///
/// This field is public, because it is used heavily from MySqlConnection
-// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2023 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
EXPECT_THROW(conn_.rollback(), isc::Unexpected);
}
+// Tests that valid connection timeout is accepted.
+TEST_F(MySqlConnectionTest, connectionTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout));
+ EXPECT_EQ(10, timeout);
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid2) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_2);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid3) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_3);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a valid read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_READ_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+ EXPECT_EQ(11, timeout);
+}
+
+// Tests that a zero read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeoutZero) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_READ_TIMEOUT_ZERO);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+ EXPECT_EQ(0, timeout);
+}
+
+// Tests that an invalid read timeout causes an error.
+TEST_F(MySqlConnectionTest, readTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_READ_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a valid write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_WRITE_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+ EXPECT_EQ(12, timeout);
+}
+
+// Tests that a zero write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeoutZero) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_WRITE_TIMEOUT_ZERO);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+ EXPECT_EQ(0, timeout);
+}
+
+// Tests that an invalid write timeout causes an error.
+TEST_F(MySqlConnectionTest, writeTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_WRITE_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
TEST_F(MySqlConnectionWithPrimaryKeyTest, select) {
select();
}