ControlledDhcpv4Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
bool reopened = false;
- // Re-open lease and host database with new parameters.
+ // We lost at least one of them, Reopen all of them (lease, host, and CB databases)
try {
CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
cfg_db->createManagers();
+
+ auto ctl_info = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo();
+ if (ctl_info) {
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+ server_->getCBControl()->databaseConfigConnect(srv_cfg);
+ }
+
reopened = true;
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_FAILED).arg(ex.what());
server_->getCBControl()->databaseConfigFetch(srv_cfg,
CBControlDHCPv4::FetchMode::FETCH_UPDATE);
(*failure_count) = 0;
-
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL)
.arg(ex.what());
-
// We allow at most 10 consecutive failures after which we stop
// making further attempts to fetch the configuration updates.
// Let's return without re-scheduling the timer.
isc::Exception(file, line, what) {}
};
+/// @brief Exception thrown when a specific connection has been rendered unusable
+/// either through loss of connectivity or API lib error
+class DbConnectionUnusable: public Exception {
+public:
+ DbConnectionUnusable(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.
/// @param parameters A data structure relating keywords and values
/// concerned with the database.
DatabaseConnection(const ParameterMap& parameters)
- :parameters_(parameters) {
+ :parameters_(parameters), unusable_(false) {
}
/// @brief Destructor
/// open connection subsequently fails
static DbLostCallback db_lost_callback;
+ /// @brief Throws an exception if the connection is not usable.
+ void checkUnusable() {
+ if (unusable_) {
+ isc_throw (DbConnectionUnusable, "Attempt to use an invalid connection");
+ }
+ }
+
+protected:
+ /// @brief Sets the usable flag to the given value.
+ /// @param usable new value for the flag.
+ void markUnusable() { unusable_ = true; }
+
private:
/// @brief List of parameters passed in dbconfig
/// intended to keep any DHCP-related parameters.
ParameterMap parameters_;
+ /// @brief Indicates if the connection can no longer be used for normal
+ /// operations. Typically a connection is marked unusable after an unrecoverable
+ /// DB error. There may be a time during which the connection exists after
+ /// such an every during which it cannot be used for anything beyond checking
+ /// parameters and error information. This flag can be used as a guard in
+ /// code to prevent inadvertant use of a broken connection.
+ bool unusable_;
};
} // namespace db
// Now close the sql socket out from under backend client
ASSERT_EQ(0, close(sql_socket));
- // A query should fail with DbOperationError.
+ // A query should fail with DbConnectionUnusable.
ASSERT_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")),
- DbOperationError);
+ DbConnectionUnusable);
// Our lost connectivity callback should have been invoked.
EXPECT_TRUE(callback_called_);
-// Copyright (C) 2014-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 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
/// valid query. Next it simulates connectivity lost by identifying and
/// closing the socket connection to the host backend. It then reissues
/// the query and verifies that:
- /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The Query throws DbConnectionUnusable (rather than exiting)
/// -# The registered DbLostCallback was invoked
void testDbLostCallback();
#endif
// Now close the sql socket out from under backend client
ASSERT_FALSE(close(sql_socket)) << "failed to close socket";
- // A query should fail with DbOperationError.
+ // A query should fail with DbConnectionUnusable.
ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
- DbOperationError);
+ DbConnectionUnusable);
// Our lost connectivity callback should have been invoked.
EXPECT_TRUE(callback_called_);
void
MySqlConnection::prepareStatement(uint32_t index, const char* text) {
+ checkUnusable();
// Validate that there is space for the statement in the statements array
// and that nothing has been placed there before.
if ((index >= statements_.size()) || (statements_[index] != NULL)) {
void
MySqlConnection::prepareStatements(const TaggedStatement* start_statement,
const TaggedStatement* end_statement) {
+ checkUnusable();
// Created the MySQL prepared statements for each DML statement.
for (const TaggedStatement* tagged_statement = start_statement;
tagged_statement != end_statement; ++tagged_statement) {
void
MySqlConnection::startTransaction() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION);
+ checkUnusable();
// We create prepared statements for all other queries, but MySQL
// don't support prepared statements for START TRANSACTION.
int status = mysql_query(mysql_, "START TRANSACTION");
void
MySqlConnection::commit() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT);
+ checkUnusable();
if (mysql_commit(mysql_) != 0) {
isc_throw(DbOperationError, "commit failed: "
<< mysql_error(mysql_));
void
MySqlConnection::rollback() {
DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK);
+ checkUnusable();
if (mysql_rollback(mysql_) != 0) {
isc_throw(DbOperationError, "rollback failed: "
<< mysql_error(mysql_));
const MySqlBindingCollection& in_bindings,
MySqlBindingCollection& out_bindings,
ConsumeResultFun process_result) {
+ checkUnusable();
// Extract native input bindings.
std::vector<MYSQL_BIND> in_bind_vec;
for (MySqlBindingPtr in_binding : in_bindings) {
template<typename StatementIndex>
void insertQuery(const StatementIndex& index,
const MySqlBindingCollection& in_bindings) {
+ checkUnusable();
std::vector<MYSQL_BIND> in_bind_vec;
for (MySqlBindingPtr in_binding : in_bindings) {
in_bind_vec.push_back(in_binding->getMySqlBinding());
template<typename StatementIndex>
uint64_t updateDeleteQuery(const StatementIndex& index,
const MySqlBindingCollection& in_bindings) {
+ checkUnusable();
std::vector<MYSQL_BIND> in_bind_vec;
for (MySqlBindingPtr in_binding : in_bindings) {
in_bind_vec.push_back(in_binding->getMySqlBinding());
/// failed.
template<typename StatementIndex>
void checkError(const int status, const StatementIndex& index,
- const char* what) const {
+ const char* what) {
if (status != 0) {
switch(mysql_errno(mysql_)) {
// These are the ones we consider fatal. Remember this method is
.arg(mysql_error(mysql_))
.arg(mysql_errno(mysql_));
+ // Mark the connection as no longer usuable.
+ markUnusable();
+
// If there's no lost db callback or it returns false,
// then we're not attempting to recover so we're done.
if (!invokeDbLostCallback()) {
// We still need to throw so caller can error out of the current
// processing.
- isc_throw(db::DbOperationError,
+ isc_throw(db::DbConnectionUnusable,
"fatal database errror or connectivity lost");
default:
// Connection is ok, so it must be an SQL error
.arg(PQerrorMessage(conn_))
.arg(sqlstate ? sqlstate : "<sqlstate null>");
+ // Mark this connection as no longer usable.
+ markUnusable();
+
// If there's no lost db callback or it returns false,
// then we're not attempting to recover so we're done.
if (!invokeDbLostCallback()) {
// We still need to throw so caller can error out of the current
// processing.
- isc_throw(DbOperationError,
+ isc_throw(DbConnectionUnusable,
"fatal database error or connectivity lost");
}