int64_t connect_timeout = 0;
int64_t read_timeout = 0;
int64_t write_timeout = 0;
+ int64_t tcp_user_timeout = 0;
int64_t port = 0;
int64_t max_reconnect_tries = 0;
int64_t reconnect_wait_time = 0;
values_copy[param.first] =
boost::lexical_cast<std::string>(write_timeout);
+ } else if (param.first == "tcp-user-timeout") {
+ tcp_user_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(tcp_user_timeout);
+
} else if (param.first == "max-reconnect-tries") {
max_reconnect_tries = param.second->intValue();
values_copy[param.first] =
<< std::numeric_limits<uint32_t>::max()
<< " (" << value->getPosition() << ")");
}
+ if ((tcp_user_timeout < 0) ||
+ (tcp_user_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("tcp-user-timeout");
+ isc_throw(DbConfigError, "tcp-user-timeout value: " << tcp_user_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
// e. Check that the port is within a reasonable range.
if ((port < 0) ||
(parameter != "connect-timeout") &&
(parameter != "read-timeout") &&
(parameter != "write-timeout") &&
+ (parameter != "tcp-user-timeout") &&
(parameter != "port") &&
(parameter != "max-row-errors") &&
(parameter != "readonly"));
EXPECT_THROW(parser.parse(json_elements), DbConfigError);
}
+// This test checks that the parser accepts the valid value of the
+// tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, validTcpUserTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "tcp-user-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
+// tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, negativeTcpUserTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "tcp-user-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 tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, largeTcpUserTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "tcp-user-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) {
-// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-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
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_TCP_USER_TIMEOUT = "tcp-user-timeout=8";
+const char* VALID_TCP_USER_TIMEOUT_ZERO = "tcp-user-timeout=0";
+const char* INVALID_TCP_USER_TIMEOUT_1 = "-7";
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_WRITE_TIMEOUT;
extern const char* VALID_WRITE_TIMEOUT_ZERO;
extern const char* INVALID_WRITE_TIMEOUT_1;
+extern const char* VALID_TCP_USER_TIMEOUT;
+extern const char* VALID_TCP_USER_TIMEOUT_ZERO;
+extern const char* INVALID_TCP_USER_TIMEOUT_1;
extern const char* VALID_READONLY_DB;
extern const char* INVALID_READONLY_DB;
extern const char* VALID_CERT;
}
unsigned int 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;
- }
+ setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
const char* user = NULL;
string suser;
// database, a zero timeout might signify something like "wait
// indefinitely".
setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+ // Other timeouts can be 0, meaning that the database client will follow a default
+ // behavior. Earlier MySQL versions didn't have these parameters, so we allow 0
+ // to skip setting them.
setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout);
setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout);
-// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-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
}
}
-void
-PgSqlConnection::openDatabase() {
+std::string
+PgSqlConnection::getConnParameters() {
string dbconnparameters;
string shost = "localhost";
try {
dbconnparameters += "host = '" + shost + "'" ;
- string sport;
- try {
- sport = getParameter("port");
- } catch (...) {
- // No port parameter, we are going to use the default port.
- sport = "";
- }
+ unsigned int port = 0;
+ setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
- if (sport.size() > 0) {
- unsigned int port = 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;
- }
-
- // Add it to connection parameters when not default.
- if (port > 0) {
- std::ostringstream oss;
- oss << port;
- dbconnparameters += " port = " + oss.str();
- }
+ // Add port to connection parameters when not default.
+ if (port > 0) {
+ std::ostringstream oss;
+ oss << port;
+ dbconnparameters += " port = " + oss.str();
}
string suser;
}
unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
- string stimeout;
+ unsigned int tcp_user_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);
+ // This timeout value can be 0, meaning that the database client will
+ // follow a default behavior. Earlier Postgres versions didn't have
+ // this parameter, so we allow 0 to skip setting them for these
+ // earlier versions.
+ setIntParameterValue("tcp-user-timeout", 0, numeric_limits<int>::max(), tcp_user_timeout);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidTimeout, ex.what());
}
+ // Append timeouts.
std::ostringstream oss;
- oss << connect_timeout;
- dbconnparameters += " connect_timeout = " + oss.str();
+ oss << " connect_timeout = " << connect_timeout;
+ if (tcp_user_timeout > 0) {
+ oss << " tcp_user_timeout = " << tcp_user_timeout * 1000;
+ }
+ dbconnparameters += oss.str();
+ return (dbconnparameters);
+}
+
+void
+PgSqlConnection::openDatabase() {
+ std::string dbconnparameters = getConnParameters();
// Connect to Postgres, saving the low level connection pointer
// in the holder object
PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
return (boost::lexical_cast<int>(PQcmdTuples(*result_set)));
}
+template<typename T>
+void
+PgSqlConnection::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);
+ }
+}
+
+
} // end of isc::db namespace
} // end of isc namespace
void prepareStatements(const PgSqlTaggedStatement* start_statement,
const PgSqlTaggedStatement* end_statement);
+ /// @brief Creates connection string from specified parameters.
+ ///
+ /// This function is called frin the @c openDatabase and from the unit
+ /// tests.
+ ///
+ /// @return connection string for @c openDatabase.
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbInvalidTimeout when the database timeout is wrong.
+ std::string getConnParameters();
+
/// @brief Open Database
///
/// Opens the database using the information supplied in the parameters
return (conn_);
}
+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 Accessor function which returns the IOService that can be used to
/// recover the connection.
///
-// Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2021-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
TestRowSet three_rows{{1, "one"}, {2, "two"}, {3, "three"}};
ASSERT_NO_THROW_LOG(testSelect(three_rows, 0, 10));
}
+
+// Tests that valid connection timeout is accepted.
+TEST_F(PgSqlConnectionTest, connectionTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("connect_timeout = 10") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid2) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_2);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid3) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_3);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that valid tcp user timeout is accepted.
+TEST_F(PgSqlConnectionTest, tcpUserTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("tcp_user_timeout = 8000") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+
+// Tests that a zero tcp user timeout is accepted.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutZero) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT_ZERO);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_FALSE(parameters.find("tcp_user_timeout") != std::string::npos)
+ << "parameter found in " << parameters << " but expected to be gone";
+}
+
+// Tests that an invalid tcp user timeout causes an error.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+
}; // namespace