]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1375] added IOService to mysql and postgresql connections
authorRazvan Becheriu <razvan@isc.org>
Wed, 4 Nov 2020 23:23:11 +0000 (01:23 +0200)
committerRazvan Becheriu <razvan@isc.org>
Wed, 9 Dec 2020 17:12:46 +0000 (19:12 +0200)
37 files changed:
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/ctrl_dhcp4_srv.h
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/ctrl_dhcp6_srv.h
src/lib/asiolink/io_service.cc
src/lib/database/database_connection.cc
src/lib/database/database_connection.h
src/lib/database/tests/database_connection_unittest.cc
src/lib/dhcpsrv/cfg_db_access.cc
src/lib/dhcpsrv/cfg_db_access.h
src/lib/dhcpsrv/cql_host_data_source.cc
src/lib/dhcpsrv/cql_host_data_source.h
src/lib/dhcpsrv/host_data_source_factory.cc
src/lib/dhcpsrv/host_data_source_factory.h
src/lib/dhcpsrv/host_mgr.cc
src/lib/dhcpsrv/host_mgr.h
src/lib/dhcpsrv/lease_mgr_factory.cc
src/lib/dhcpsrv/lease_mgr_factory.h
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/mysql_host_data_source.h
src/lib/dhcpsrv/mysql_lease_mgr.cc
src/lib/dhcpsrv/mysql_lease_mgr.h
src/lib/dhcpsrv/pgsql_host_data_source.cc
src/lib/dhcpsrv/pgsql_host_data_source.h
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/pgsql_lease_mgr.h
src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
src/lib/dhcpsrv/tests/host_cache_unittest.cc
src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc
src/lib/dhcpsrv/tests/host_mgr_unittest.cc
src/lib/dhcpsrv/testutils/memory_host_data_source.cc
src/lib/dhcpsrv/testutils/memory_host_data_source.h
src/lib/mysql/mysql_connection.h
src/lib/pgsql/pgsql_connection.cc
src/lib/pgsql/pgsql_connection.h

index 94c15496c96f9975ee7ef12a85d3161a45ac7396..094a65db4657584b19da976da1787a6ca79bc23c 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <sstream>
 
+using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::db;
@@ -802,11 +803,18 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
 
     // Re-open lease and host database with new parameters.
     try {
-        DatabaseConnection::db_lost_callback =
+        DatabaseConnection::db_lost_callback_ =
             std::bind(&ControlledDhcpv4Srv::dbLostCallback, srv, ph::_1);
+
+        DatabaseConnection::db_recovered_callback_ =
+            std::bind(&ControlledDhcpv4Srv::dbRecoveredCallback, srv, ph::_1);
+
+        DatabaseConnection::db_failed_callback_ =
+            std::bind(&ControlledDhcpv4Srv::dbFailedCallback, srv, ph::_1);
+
         CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
         cfg_db->setAppendedParameters("universe=4");
-        cfg_db->createManagers();
+        cfg_db->createManagers(server_->io_service_);
     } catch (const std::exception& ex) {
         err << "Unable to open database: " << ex.what();
         return (isc::config::createAnswer(1, err.str()));
@@ -831,10 +839,10 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
-    // Configure DHCP packet queueing
+    // Configure DHCP packet queuing.
     try {
         data::ConstElementPtr qc;
-        qc  = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
+        qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
         if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
             LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE)
                      .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr());
@@ -959,7 +967,7 @@ ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
 
 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/,
                                          uint16_t client_port /*= 0*/)
-    : Dhcpv4Srv(server_port, client_port), io_service_(),
+    : Dhcpv4Srv(server_port, client_port), io_service_(boost::make_shared<IOService>()),
       timer_mgr_(TimerMgr::instance()) {
     if (getInstance()) {
         isc_throw(InvalidOperation,
@@ -1054,7 +1062,7 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
 
 void ControlledDhcpv4Srv::shutdownServer(int exit_value) {
     setExitValue(exit_value);
-    io_service_.stop();       // Stop ASIO transmissions
+    io_service_->stop();       // Stop ASIO transmissions
     shutdown();               // Initiate DHCPv4 shutdown procedure.
 }
 
@@ -1064,7 +1072,8 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
 
         // The closure captures either a shared pointer (memory leak)
         // or a raw pointer (pointing to a deleted object).
-        DatabaseConnection::db_lost_callback = 0;
+        DatabaseConnection::db_lost_callback_ = 0;
+        DatabaseConnection::db_recovered_callback_ = 0;
 
         timer_mgr_->unregisterTimers();
 
@@ -1112,7 +1121,7 @@ void ControlledDhcpv4Srv::sessionReader(void) {
     // Process one asio event. If there are more events, iface_mgr will call
     // this callback more than once.
     if (getInstance()) {
-        getInstance()->io_service_.run_one();
+        getInstance()->io_service_->run_one();
     }
 }
 
@@ -1140,66 +1149,9 @@ ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
     TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
 }
 
-void
-ControlledDhcpv4Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
-    bool reopened = false;
-
-    // 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());
-    }
-
-    if (reopened) {
-        // Cancel the timer.
-        if (TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
-            TimerMgr::instance()->cancel("Dhcp4DbReconnectTimer");
-        }
-
-        // Set network state to service enabled
-        network_state_->enableService();
-
-        // Toss the reconnect control, we're done with it
-        db_reconnect_ctl.reset();
-    } else {
-        if (!db_reconnect_ctl->checkRetries()) {
-            // We're out of retries, log it and initiate shutdown.
-            LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_RETRIES_EXHAUSTED)
-            .arg(db_reconnect_ctl->maxRetries());
-            shutdownServer(EXIT_FAILURE);
-            return;
-        }
-
-        LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_SCHEDULE)
-                .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
-                .arg(db_reconnect_ctl->maxRetries())
-                .arg(db_reconnect_ctl->retryInterval());
-
-        if (!TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
-            TimerMgr::instance()->registerTimer("Dhcp4DbReconnectTimer",
-                            std::bind(&ControlledDhcpv4Srv::dbReconnect, this,
-                                      db_reconnect_ctl),
-                            db_reconnect_ctl->retryInterval(),
-                            asiolink::IntervalTimer::ONE_SHOT);
-        }
-
-        TimerMgr::instance()->setup("Dhcp4DbReconnectTimer");
-    }
-}
-
 bool
 ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
-    // Disable service until we recover
+    // Disable service until the connection is recovered.
     network_state_->disableService();
 
     if (!db_reconnect_ctl) {
@@ -1208,21 +1160,51 @@ ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
         return (false);
     }
 
-    // If reconnect isn't enabled log it,
-    // initiate a shutdown and return false.
+    // If reconnect isn't enabled log it, initiate a shutdown and return false.
     if (!db_reconnect_ctl->retriesLeft() ||
         !db_reconnect_ctl->retryInterval()) {
         LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
             .arg(db_reconnect_ctl->retriesLeft())
             .arg(db_reconnect_ctl->retryInterval());
         shutdownServer(EXIT_FAILURE);
-        return(false);
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+ControlledDhcpv4Srv::dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
+    // Enable service after the connection is recovered.
+    network_state_->enableService();
+
+    if (!db_reconnect_ctl) {
+        // This shouldn't never happen
+        LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
+        return (false);
     }
 
-    // Invoke reconnect method
-    dbReconnect(db_reconnect_ctl);
+    db_reconnect_ctl->resetRetries();
+
+    return (true);
+}
+
+bool
+ControlledDhcpv4Srv::dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
+    if (!db_reconnect_ctl) {
+        // This shouldn't never happen
+        LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
+        return (false);
+    }
+
+    //LOG_INFO(dhcp4_logger, DHCP4_DB_FAILED_RECONNECT)
+    //        .arg(db_reconnect_ctl->maxRetries());
+
+    db_reconnect_ctl->resetRetries();
+
+    shutdownServer(EXIT_FAILURE);
 
-    return(true);
+    return (true);
 }
 
 void
index 1f0a862030a97e4dc1b8aa0a2ee58e5ad2017c83..81c6ce636549bd76764d3d943aba9eae8241c17f 100644 (file)
@@ -387,27 +387,7 @@ private:
     /// deleted.
     void deleteExpiredReclaimedLeases(const uint32_t secs);
 
-    /// @brief Attempts to reconnect the server to the DB backend managers
-    ///
-    /// This is a self-rescheduling function that attempts to reconnect to the
-    /// server's DB backends after connectivity to one or more have been
-    /// lost.  Upon entry it will attempt to reconnect via @ref CfgDdbAccess::
-    /// createManagers.  If this is succesful, DHCP servicing is re-enabled and
-    /// server returns to normal operation.
-    ///
-    /// If reconnection fails and the maximum number of retries has not been
-    /// exhausted, it will schedule a call to itself to occur at the
-    /// configured retry interval. DHCP service remains disabled.
-    ///
-    /// If the maximum number of retries has been exhausted an error is logged
-    /// and the server shuts down.
-    ///
-    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
-    /// configured reconnect parameters
-    ///
-    void dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
-
-    /// @brief Callback DB backends should invoke upon loss of connectivity
+    /// @brief Callback DB backends should invoke upon loss of the connectivity
     ///
     /// This function is invoked by DB backends when they detect a loss of
     /// connectivity.  The parameter, db_reconnect_ctl, conveys the configured
@@ -416,10 +396,8 @@ private:
     ///
     /// If either value is zero, reconnect is presumed to be disabled and
     /// the function will schedule a shutdown and return false.  This instructs
-    /// the DB backend layer (the caller) to treat the connectivity loss as fatal.
-    ///
-    /// Otherwise, the function saves db_reconnect_ctl and invokes
-    /// dbReconnect to initiate the reconnect process.
+    /// the DB backend layer (the caller) to treat the connectivity loss as
+    /// fatal. It stops the DHCP service until the connection is recovered.
     ///
     /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
     /// configured reconnect parameters
@@ -427,6 +405,25 @@ private:
     /// @return false if reconnect is not configured, true otherwise
     bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
 
+    /// @brief Callback DB backends should invoke upon restoration of the
+    /// connectivity
+    ///
+    /// This function is invoked by DB backends when they recover the
+    /// connectivity. It starts the DHCP service after the connection is
+    /// recovered.
+    ///
+    /// @return false if reconnect is not configured, true otherwise
+    bool dbRecoveredCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+
+    /// @brief Callback DB backends should invoke upon failing to restore of the
+    /// connectivity
+    ///
+    /// This function is invoked by DB backends when they fail to recover the
+    /// connectivity. It stops the server.
+    ///
+    /// @return false if reconnect is not configured, true otherwise
+    bool dbFailedCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+
     /// @brief Callback invoked periodically to fetch configuration updates
     /// from the Config Backends.
     ///
@@ -448,7 +445,7 @@ private:
     static ControlledDhcpv4Srv* server_;
 
     /// @brief IOService object, used for all ASIO operations.
-    isc::asiolink::IOService io_service_;
+    isc::asiolink::IOServicePtr io_service_;
 
     /// @brief Instance of the @c TimerMgr.
     ///
index 5a0c749f31df5742d538b3145734c588247b8a8c..c687d1bfba58d50a82baeb4f9094aaa272130b83 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <sstream>
 
+using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::db;
@@ -805,11 +806,18 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
 
     // Re-open lease and host database with new parameters.
     try {
-        DatabaseConnection::db_lost_callback =
+        DatabaseConnection::db_lost_callback_ =
             std::bind(&ControlledDhcpv6Srv::dbLostCallback, srv, ph::_1);
+
+        DatabaseConnection::db_recovered_callback_ =
+            std::bind(&ControlledDhcpv6Srv::dbRecoveredCallback, srv, ph::_1);
+
+        DatabaseConnection::db_failed_callback_ =
+            std::bind(&ControlledDhcpv6Srv::dbFailedCallback, srv, ph::_1);
+
         CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
         cfg_db->setAppendedParameters("universe=6");
-        cfg_db->createManagers();
+        cfg_db->createManagers(server_->io_service_);
     } catch (const std::exception& ex) {
         err << "Unable to open database: " << ex.what();
         return (isc::config::createAnswer(1, err.str()));
@@ -853,10 +861,10 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
-    // Configure DHCP packet queueing
+    // Configure DHCP packet queuing.
     try {
         data::ConstElementPtr qc;
-        qc  = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
+        qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
         if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, qc)) {
             LOG_INFO(dhcp6_logger, DHCP6_CONFIG_PACKET_QUEUE)
                      .arg(IfaceMgr::instance().getPacketQueue6()->getInfoStr());
@@ -978,7 +986,7 @@ ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) {
 
 ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port,
                                          uint16_t client_port)
-    : Dhcpv6Srv(server_port, client_port), io_service_(),
+    : Dhcpv6Srv(server_port, client_port), io_service_(boost::make_shared<IOService>()),
       timer_mgr_(TimerMgr::instance()) {
     if (getInstance()) {
         isc_throw(InvalidOperation,
@@ -1073,7 +1081,7 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port,
 
 void ControlledDhcpv6Srv::shutdownServer(int exit_value) {
     setExitValue(exit_value);
-    io_service_.stop();    // Stop ASIO transmissions
+    io_service_->stop();    // Stop ASIO transmissions
     shutdown();            // Initiate DHCPv6 shutdown procedure.
 }
 
@@ -1083,7 +1091,8 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
 
         // The closure captures either a shared pointer (memory leak)
         // or a raw pointer (pointing to a deleted object).
-        DatabaseConnection::db_lost_callback = 0;
+        DatabaseConnection::db_lost_callback_ = 0;
+        DatabaseConnection::db_recovered_callback_ = 0;
 
         timer_mgr_->unregisterTimers();
 
@@ -1131,7 +1140,7 @@ void ControlledDhcpv6Srv::sessionReader(void) {
     // Process one asio event. If there are more events, iface_mgr will call
     // this callback more than once.
     if (getInstance()) {
-        getInstance()->io_service_.run_one();
+        getInstance()->io_service_->run_one();
     }
 }
 
@@ -1159,66 +1168,9 @@ ControlledDhcpv6Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
     TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
 }
 
-void
-ControlledDhcpv6Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
-    bool reopened = false;
-
-    // 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(dhcp6_logger, DHCP6_DB_RECONNECT_ATTEMPT_FAILED).arg(ex.what());
-    }
-
-    if (reopened) {
-        // Cancel the timer.
-        if (TimerMgr::instance()->isTimerRegistered("Dhcp6DbReconnectTimer")) {
-            TimerMgr::instance()->cancel("Dhcp6DbReconnectTimer");
-        }
-
-        // Set network state to service enabled
-        network_state_->enableService();
-
-        // Toss the reconnect control, we're done with it
-        db_reconnect_ctl.reset();
-    } else {
-        if (!db_reconnect_ctl->checkRetries()) {
-            // We're out of retries, log it and initiate shutdown.
-            LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_RETRIES_EXHAUSTED)
-            .arg(db_reconnect_ctl->maxRetries());
-            shutdownServer(EXIT_FAILURE);
-            return;
-        }
-
-        LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_ATTEMPT_SCHEDULE)
-                .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
-                .arg(db_reconnect_ctl->maxRetries())
-                .arg(db_reconnect_ctl->retryInterval());
-
-        if (!TimerMgr::instance()->isTimerRegistered("Dhcp6DbReconnectTimer")) {
-            TimerMgr::instance()->registerTimer("Dhcp6DbReconnectTimer",
-                            std::bind(&ControlledDhcpv6Srv::dbReconnect, this,
-                                      db_reconnect_ctl),
-                            db_reconnect_ctl->retryInterval(),
-                            asiolink::IntervalTimer::ONE_SHOT);
-        }
-
-        TimerMgr::instance()->setup("Dhcp6DbReconnectTimer");
-    }
-}
-
 bool
 ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
-    // Disable service until we recover
+    // Disable service until the connection is recovered.
     network_state_->disableService();
 
     if (!db_reconnect_ctl) {
@@ -1227,20 +1179,51 @@ ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
         return (false);
     }
 
-    // If reconnect isn't enabled log it and initiate a shutdown.
+    // If reconnect isn't enabled log it, initiate a shutdown and return false.
     if (!db_reconnect_ctl->retriesLeft() ||
         !db_reconnect_ctl->retryInterval()) {
         LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_DISABLED)
             .arg(db_reconnect_ctl->retriesLeft())
             .arg(db_reconnect_ctl->retryInterval());
         shutdownServer(EXIT_FAILURE);
-        return(false);
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+ControlledDhcpv6Srv::dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
+    // Enable service after the connection is recovered.
+    network_state_->enableService();
+
+    if (!db_reconnect_ctl) {
+        // This shouldn't never happen
+        LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_NO_DB_CTL);
+        return (false);
     }
 
-    // Invoke reconnect method
-    dbReconnect(db_reconnect_ctl);
+    db_reconnect_ctl->resetRetries();
+
+    return (true);
+}
+
+bool
+ControlledDhcpv6Srv::dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
+    if (!db_reconnect_ctl) {
+        // This shouldn't never happen
+        LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_NO_DB_CTL);
+        return (false);
+    }
+
+    //LOG_INFO(dhcp6_logger, DHCP6_DB_FAILED_RECONNECT)
+    //        .arg(db_reconnect_ctl->maxRetries());
+
+    db_reconnect_ctl->resetRetries();
+
+    shutdownServer(EXIT_FAILURE);
 
-    return(true);
+    return (true);
 }
 
 void
index fe39e98e15b918609dedeaec4044816e0160b433..fabfe6bf763f9acd707b4e1ef8918967e243766a 100644 (file)
@@ -387,27 +387,7 @@ private:
     /// deleted.
     void deleteExpiredReclaimedLeases(const uint32_t secs);
 
-    /// @brief Attempts to reconnect the server to the DB backend managers
-    ///
-    /// This is a self-rescheduling function that attempts to reconnect to the
-    /// server's DB backends after connectivity to one or more have been
-    /// lost.  Upon entry it will attempt to reconnect via @ref CfgDdbAccess::
-    /// createManagers.  If this is succesful, DHCP servicing is re-enabled and
-    /// server returns to normal operation.
-    ///
-    /// If reconnection fails and the maximum number of retries has not been
-    /// exhausted, it will schedule a call to itself to occur at the
-    /// configured retry interval. DHCP service remains disabled.
-    ///
-    /// If the maximum number of retries has been exhausted an error is logged
-    /// and the server shuts down.
-    ///
-    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
-    /// configured reconnect parameters
-    ///
-    void dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
-
-    /// @brief Callback DB backends should invoke upon loss of connectivity
+    /// @brief Callback DB backends should invoke upon loss of the connectivity
     ///
     /// This function is invoked by DB backends when they detect a loss of
     /// connectivity.  The parameter, db_reconnect_ctl, conveys the configured
@@ -416,10 +396,8 @@ private:
     ///
     /// If either value is zero, reconnect is presumed to be disabled and
     /// the function will schedule a shutdown and return false.  This instructs
-    /// the DB backend layer (the caller) to treat the connectivity loss as fatal.
-    ///
-    /// Otherwise, the function saves db_reconnect_ctl and invokes
-    /// dbReconnect to initiate the reconnect process.
+    /// the DB backend layer (the caller) to treat the connectivity loss as
+    /// fatal. It stops the DHCP service until the connection is recovered.
     ///
     /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
     /// configured reconnect parameters
@@ -427,6 +405,25 @@ private:
     /// @return false if reconnect is not configured, true otherwise
     bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
 
+    /// @brief Callback DB backends should invoke upon restoration of the
+    /// connectivity
+    ///
+    /// This function is invoked by DB backends when they recover the
+    /// connectivity. It starts the DHCP service after the connection is
+    /// recovered.
+    ///
+    /// @return false if reconnect is not configured, true otherwise
+    bool dbRecoveredCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+
+    /// @brief Callback DB backends should invoke upon failing to restore of the
+    /// connectivity
+    ///
+    /// This function is invoked by DB backends when they fail to recover the
+    /// connectivity. It stops the server.
+    ///
+    /// @return false if reconnect is not configured, true otherwise
+    bool dbFailedCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+
     /// @brief Callback invoked periodically to fetch configuration updates
     /// from the Config Backends.
     ///
@@ -448,7 +445,7 @@ private:
     static ControlledDhcpv6Srv* server_;
 
     /// @brief IOService object, used for all ASIO operations.
-    isc::asiolink::IOService io_service_;
+    isc::asiolink::IOServicePtr io_service_;
 
     /// @brief Instance of the @c TimerMgr.
     ///
index cbc3ccca3821a49f1b96c13e28264041fd7910b5..2140f2c2d1fcc73fb258018f66412e36a000c6b0 100644 (file)
@@ -22,13 +22,19 @@ namespace {
 // io_service::post().
 class CallbackWrapper {
 public:
+
+    /// \brief Constructor
     CallbackWrapper(const std::function<void()>& callback) :
-        callback_(callback)
-    {}
+        callback_(callback) {}
+
+    /// \brief Function operator
     void operator()() {
         callback_();
     }
+
 private:
+
+    /// \brief The callback function
     std::function<void()> callback_;
 };
 }
@@ -90,6 +96,10 @@ public:
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
     boost::asio::io_service& get_io_service() { return io_service_; };
+
+    /// \brief Post a callback on the IO service
+    ///
+    /// \param callback The callback to be run on the IO service.
     void post(const std::function<void ()>& callback) {
         const CallbackWrapper wrapper(callback);
         io_service_.post(wrapper);
index e61793160b711e7d173f84dca8e379100dee8af3..6c5d427dc2e8b0624be14ef4b373cff6386f43c8 100644 (file)
@@ -110,14 +110,13 @@ DatabaseConnection::configuredReadOnly() const {
     return (readonly_value == "true");
 }
 
-ReconnectCtlPtr
-DatabaseConnection::makeReconnectCtl() const {
-    ReconnectCtlPtr retry;
+void
+DatabaseConnection::makeReconnectCtl(const std::string& timer_name) {
     string type = "unknown";
     unsigned int retries = 0;
     unsigned int interval = 0;
 
-    // Assumes that parsing ensurse only valid values are present
+    // Assumes that parsing ensures only valid values are present
     try {
         type = getParameter("type");
     } catch (...) {
@@ -139,15 +138,32 @@ DatabaseConnection::makeReconnectCtl() const {
         // Wasn't specified so we'll use default of 0;
     }
 
-    retry.reset(new ReconnectCtl(type, retries, interval));
-    return (retry);
+    reconnect_ctl_ = boost::make_shared<ReconnectCtl>(timer_name, type, retries,
+                                                      interval);
 }
 
 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());
+DatabaseConnection::invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+    if (DatabaseConnection::db_lost_callback_) {
+        return (DatabaseConnection::db_lost_callback_(db_reconnect_ctl));
+    }
+
+    return (false);
+}
+
+bool
+DatabaseConnection::invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+    if (DatabaseConnection::db_recovered_callback_) {
+        return (DatabaseConnection::db_recovered_callback_(db_reconnect_ctl));
+    }
+
+    return (false);
+}
+
+bool
+DatabaseConnection::invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+    if (DatabaseConnection::db_failed_callback_) {
+        return (DatabaseConnection::db_failed_callback_(db_reconnect_ctl));
     }
 
     return (false);
@@ -214,8 +230,9 @@ DatabaseConnection::toElementDbAccessString(const std::string& dbaccess) {
     return (toElement(params));
 }
 
-DatabaseConnection::DbLostCallback
-DatabaseConnection::db_lost_callback = 0;
+DbCallback DatabaseConnection::db_lost_callback_ = 0;
+DbCallback DatabaseConnection::db_recovered_callback_ = 0;
+DbCallback DatabaseConnection::db_failed_callback_ = 0;
 
-};
-};
+}  // namespace db
+}  // namespace isc
index 990dc08c6dc061236ca10c96e1840d4e15ff004c..ba313a680a47815e765a189c1815f2e2e84d8e49 100644 (file)
@@ -39,17 +39,9 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-/// @brief Exception thrown when connectivity has been lost and
-/// cannot be recovered.
-class DbUnrecoverableError : public Exception {
-public:
-    DbUnrecoverableError(const char* file, size_t line, const char* what) :
-        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 {
+class DbConnectionUnusable : public Exception {
 public:
     DbConnectionUnusable(const char* file, size_t line, const char* what) :
         isc::Exception(file, line, what) {}
@@ -93,19 +85,29 @@ public:
 class ReconnectCtl {
 public:
     /// @brief Constructor
+    ///
     /// @param backend_type type of the caller backend.
+    /// @param timer_name timer associated to this object
     /// @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) {}
+    ReconnectCtl(const std::string& backend_type, const std::string& timer_name,
+                 unsigned int max_retries, unsigned int retry_interval)
+        : backend_type_(backend_type), timer_name_(timer_name),
+          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 Returns the associated timer name.
+    ///
+    /// @return the associated timer.
+    std::string timerName() const {
+        return (timer_name_);
+    }
+
     /// @brief Decrements the number of retries remaining
     ///
     /// Each call decrements the number of retries by one until zero is reached.
@@ -129,10 +131,19 @@ public:
         return (retry_interval_);
     }
 
+    /// @brief Resets the retries count
+    void resetRetries() {
+        retries_left_ = max_retries_;
+    }
+
 private:
+
     /// @brief Caller backend type.
     const std::string backend_type_;
 
+    /// @brief Timer associated to this object.
+    std::string timer_name_;
+
     /// @brief Maximum number of retry attempts to make
     unsigned int max_retries_;
 
@@ -146,6 +157,9 @@ private:
 /// @brief Pointer to an instance of ReconnectCtl
 typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr;
 
+/// @brief Defines a callback prototype for propagating events upward
+typedef std::function<bool (ReconnectCtlPtr db_reconnect_ctl)> DbCallback;
+
 /// @brief Common database connection class.
 ///
 /// This class provides functions that are common for establishing
@@ -173,7 +187,7 @@ public:
     /// @param parameters A data structure relating keywords and values
     ///        concerned with the database.
     DatabaseConnection(const ParameterMap& parameters)
-        :parameters_(parameters), unusable_(false) {
+        : parameters_(parameters), unusable_(false) {
     }
 
     /// @brief Destructor
@@ -181,8 +195,16 @@ public:
 
     /// @brief Instantiates a ReconnectCtl based on the connection's
     /// reconnect parameters
-    /// @return pointer to the new ReconnectCtl object
-    virtual ReconnectCtlPtr makeReconnectCtl() const;
+    ///
+    /// @param timer_name of the timer used for the ReconnectCtl object.
+    virtual void makeReconnectCtl(const std::string& timer_name);
+
+    /// @brief The reconnect settings.
+    ///
+    /// @brief return The reconnect settings.
+    ReconnectCtlPtr reconnectCtl() {
+        return (reconnect_ctl_);
+    }
 
     /// @brief Returns value of a connection parameter.
     ///
@@ -219,19 +241,23 @@ public:
     /// and set to false.
     bool configuredReadOnly() const;
 
-    /// @brief Defines a callback prototype for propogating events upward
-    typedef std::function<bool (ReconnectCtlPtr db_retry)> 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
+    /// @return Returns the result of the callback or false if there is no
     /// callback.
+    static bool invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl);
+
+    /// @brief Invokes the connection's restored connectivity callback
     ///
     /// @return Returns the result of the callback or false if there is no
     /// callback.
-    bool invokeDbLostCallback() const;
+    static bool invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl);
+
+    /// @brief Invokes the connection's restore failed connectivity callback
+    ///
+    /// @return Returns the result of the callback or false if there is no
+    /// callback.
+    static bool invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl);
 
     /// @brief Unparse a parameter map
     ///
@@ -245,9 +271,17 @@ public:
     /// @return a pointer to configuration
     static isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess);
 
-    /// @brief Optional call back function to invoke if a successfully
-    /// open connection subsequently fails
-    static DbLostCallback db_lost_callback;
+    /// @brief Optional callback function to invoke if an opened connection is
+    /// lost
+    static DbCallback db_lost_callback_;
+
+    /// @brief Optional callback function to invoke if an opened connection
+    /// recovery succeeded
+    static DbCallback db_recovered_callback_;
+
+    /// @brief Optional callback function to invoke if an opened connection
+    /// recovery failed
+    static DbCallback db_failed_callback_;
 
     /// @brief Throws an exception if the connection is not usable.
     /// @throw DbConnectionUnusable
@@ -277,6 +311,9 @@ private:
     /// parameters and error information. This flag can be used as a guard in
     /// code to prevent inadvertent use of a broken connection.
     bool unusable_;
+
+    /// @brief Reconnect settings.
+    ReconnectCtlPtr reconnect_ctl_;
 };
 
 }  // namespace db
index 180b6dd0b63e59a5af21739a0085f869594f56c7..7442454045418731a49d5da358b375cfe44dd375 100644 (file)
@@ -24,7 +24,16 @@ public:
     /// Constructor
     DatabaseConnectionCallbackTest()
         : db_reconnect_ctl_(0) {
-        DatabaseConnection::db_lost_callback = 0;
+        DatabaseConnection::db_lost_callback_ = 0;
+        DatabaseConnection::db_recovered_callback_ = 0;
+        DatabaseConnection::db_failed_callback_ = 0;
+    }
+
+    /// Destructor
+    ~DatabaseConnectionCallbackTest() {
+        DatabaseConnection::db_lost_callback_ = 0;
+        DatabaseConnection::db_recovered_callback_ = 0;
+        DatabaseConnection::db_failed_callback_ = 0;
     }
 
     /// @brief Callback to register with a DatabaseConnection
@@ -40,6 +49,34 @@ public:
         return (true);
     }
 
+    /// @brief Callback to register with a DatabaseConnection
+    ///
+    /// @param db_reconnect_ctl ReconnectCtl containing reconnect
+    /// parameters
+    bool dbRecoveredCallback(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;
+        db_reconnect_ctl_->resetRetries();
+        return (true);
+    }
+
+    /// @brief Callback to register with a DatabaseConnection
+    ///
+    /// @param db_reconnect_ctl ReconnectCtl containing reconnect
+    /// parameters
+    bool dbFailedCallback(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;
+        db_reconnect_ctl_->resetRetries();
+        return (false);
+    }
+
     /// @brief Retainer for the control passed into the callback
     ReconnectCtlPtr db_reconnect_ctl_;
 };
@@ -63,17 +100,53 @@ TEST(DatabaseConnectionTest, getParameter) {
 /// @brief NoDbLostCallback
 ///
 /// This test verifies that DatabaseConnection::invokeDbLostCallback
-/// returns a false if there is connection has no registered
-/// DbLostCallback.
+/// returns false if the connection has no registered DbCallback.
 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("60000");
     DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
+
+    bool ret = false;
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+    EXPECT_FALSE(ret);
+    EXPECT_FALSE(db_reconnect_ctl_);
+}
+
+/// @brief NoDbRecoveredCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbRecoveredCallback
+/// returns false if the connection has no registered DbCallback.
+TEST_F(DatabaseConnectionCallbackTest, NoDbRecoveredCallback) {
+    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("60000");
+    DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
+
+    bool ret = false;
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbRecoveredCallback(datasrc.reconnectCtl()));
+    EXPECT_FALSE(ret);
+    EXPECT_FALSE(db_reconnect_ctl_);
+}
+
+/// @brief NoDbFailedCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbFailedCallback
+/// returns false if the connection has no registered DbCallback.
+TEST_F(DatabaseConnectionCallbackTest, NoDbFailedCallback) {
+    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("60000");
+    DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
 
     bool ret = false;
-    ASSERT_NO_THROW(ret = datasrc.invokeDbLostCallback());
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbFailedCallback(datasrc.reconnectCtl()));
     EXPECT_FALSE(ret);
     EXPECT_FALSE(db_reconnect_ctl_);
 }
@@ -81,7 +154,7 @@ TEST_F(DatabaseConnectionCallbackTest, NoDbLostCallback) {
 /// @brief dbLostCallback
 ///
 /// This test verifies that DatabaseConnection::invokeDbLostCallback
-/// safely invokes the registered DbLostCallback.  It also tests
+/// safely invokes the registered DbCallback.  It also tests
 /// operation of DbReconnectCtl retry accounting methods.
 TEST_F(DatabaseConnectionCallbackTest, dbLostCallback) {
     /// Create a Database configuration that includes the reconnect
@@ -92,21 +165,72 @@ TEST_F(DatabaseConnectionCallbackTest, dbLostCallback) {
     pmap[std::string("reconnect-wait-time")] = std::string("60000");
 
     /// Install the callback.
-    DatabaseConnection::db_lost_callback =
+    DatabaseConnection::db_lost_callback_ =
         std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
     /// Create the connection..
     DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
+    bool ret = false;
 
     /// We should be able to invoke the callback and get
     /// the correct reconnect control parameters from it.
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+    EXPECT_TRUE(ret);
+    ASSERT_TRUE(db_reconnect_ctl_);
+    ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+    ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+    ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
+    ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
+    EXPECT_EQ(60000, 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());
+}
+
+/// @brief dbRecoveredCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbRecoveredCallback
+/// safely invokes the registered DbRecoveredCallback.  It also tests
+/// operation of DbReconnectCtl retry reset method.
+TEST_F(DatabaseConnectionCallbackTest, dbRecoveredCallback) {
+    /// 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("60000");
+
+    /// Install the callback.
+    DatabaseConnection::db_lost_callback_ =
+        std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
+    DatabaseConnection::db_recovered_callback_ =
+        std::bind(&DatabaseConnectionCallbackTest::dbRecoveredCallback, this, ph::_1);
+    /// Create the connection..
+    DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
     bool ret = false;
-    ASSERT_NO_THROW(ret = datasrc.invokeDbLostCallback());
+
+    /// We should be able to invoke the callback and get
+    /// the correct reconnect control parameters from it.
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
     EXPECT_TRUE(ret);
     ASSERT_TRUE(db_reconnect_ctl_);
     ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+    ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
     ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
     ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
     EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+    ASSERT_TRUE(db_reconnect_ctl_->checkRetries());
 
     /// Verify that checkRetries() correctly decrements
     /// down to zero, and that retriesLeft() returns
@@ -120,6 +244,86 @@ TEST_F(DatabaseConnectionCallbackTest, dbLostCallback) {
     EXPECT_FALSE(db_reconnect_ctl_->checkRetries());
     EXPECT_EQ(0, db_reconnect_ctl_->retriesLeft());
     EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+
+    /// Reset the reconnect ctl object to verify that it is set again.
+    db_reconnect_ctl_.reset();
+
+    /// We should be able to invoke the callback and get
+    /// the correct reconnect control parameters from it.
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbRecoveredCallback(datasrc.reconnectCtl()));
+    EXPECT_TRUE(ret);
+    ASSERT_TRUE(db_reconnect_ctl_);
+    ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+    ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+    EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+    /// Retries are reset, verify that's reflected.
+    EXPECT_EQ(3, db_reconnect_ctl_->retriesLeft());
+    EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+}
+
+/// @brief dbFailedCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbFailedCallback
+/// safely invokes the registered DbFailedCallback.
+TEST_F(DatabaseConnectionCallbackTest, dbFailedCallback) {
+    /// 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("60000");
+
+    /// Install the callback.
+    DatabaseConnection::db_lost_callback_ =
+        std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
+    DatabaseConnection::db_failed_callback_ =
+        std::bind(&DatabaseConnectionCallbackTest::dbFailedCallback, this, ph::_1);
+    /// Create the connection..
+    DatabaseConnection datasrc(pmap);
+    datasrc.makeReconnectCtl("timer");
+    bool ret = false;
+
+    /// We should be able to invoke the callback and get
+    /// the correct reconnect control parameters from it.
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+    EXPECT_TRUE(ret);
+    ASSERT_TRUE(db_reconnect_ctl_);
+    ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+    ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+    ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
+    ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
+    EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+    ASSERT_TRUE(db_reconnect_ctl_->checkRetries());
+
+    /// 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());
+
+    /// Reset the reconnect ctl object to verify that it is set again.
+    db_reconnect_ctl_.reset();
+
+    /// We should be able to invoke the callback and get
+    /// the correct reconnect control parameters from it.
+    ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbFailedCallback(datasrc.reconnectCtl()));
+    EXPECT_FALSE(ret);
+    ASSERT_TRUE(db_reconnect_ctl_);
+    ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+    ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+    EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+    /// Retries are reset, verify that's reflected.
+    EXPECT_EQ(3, db_reconnect_ctl_->retriesLeft());
+    EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
 }
 
 // This test checks that a database access string can be parsed correctly.
index dd843a97c455df572a2f653765cdff15e1f5b010..c51be236a04c70e186b9f113567e4a4c8770317b 100644 (file)
@@ -53,23 +53,23 @@ CfgDbAccess::getHostDbAccessStringList() const {
 }
 
 void
-CfgDbAccess::createManagers() const {
+CfgDbAccess::createManagers(const isc::asiolink::IOServicePtr& io_service) const {
     // Recreate lease manager.
     LeaseMgrFactory::destroy();
-    LeaseMgrFactory::create(getLeaseDbAccessString());
+    LeaseMgrFactory::create(getLeaseDbAccessString(), io_service);
 
     // Recreate host data source.
     HostMgr::create();
 
     // Restore the host cache.
     if (HostDataSourceFactory::registeredFactory("cache")) {
-        HostMgr::addBackend("type=cache");
+        HostMgr::addBackend("type=cache", io_service);
     }
 
     // Add database backends.
     std::list<std::string> host_db_access_list = getHostDbAccessStringList();
     for (std::string& hds : host_db_access_list) {
-        HostMgr::addBackend(hds);
+        HostMgr::addBackend(hds, io_service);
     }
 
     // Check for a host cache.
index 6d7bf01629c839177f0a094ad81bc02bfcf915f4..d4b96aa586c73ffb22c50ce3b58b7e1754df37f1 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef CFG_DBACCESS_H
 #define CFG_DBACCESS_H
 
+#include <asiolink/io_service.h>
 #include <cc/cfg_to_element.h>
 #include <database/database_connection.h>
 
@@ -102,7 +103,9 @@ public:
 
     /// @brief Creates instance of lease manager and host data sources
     /// according to the configuration specified.
-    void createManagers() const;
+    ///
+    /// @param IOService object, used for all ASIO operations.
+    void createManagers(const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr()) const;
 
 protected:
 
index 38cd0e2107d80be7463da64ba719d73d7ec245f1..6b595f7c8252a9bd7e1768adbadc4e261368126b 100644 (file)
@@ -3447,7 +3447,8 @@ CqlHostDataSourceImpl::mergeHosts(const ConstHostPtr& source_host,
     source_host->getCfgOption6()->mergeTo(*target_host->getCfgOption6());
 }
 
-CqlHostDataSource::CqlHostDataSource(const CqlConnection::ParameterMap& parameters)
+CqlHostDataSource::CqlHostDataSource(const CqlConnection::ParameterMap& parameters,
+                                     const isc::asiolink::IOServicePtr& /*io_service*/)
     : impl_(new CqlHostDataSourceImpl(parameters)) {
 }
 
index f63b9e253853d848d083eebe6edaff4ba9f0eca2..d15581e5cf07fc7b5d3cae1b43916d59aff6a72f 100644 (file)
@@ -18,6 +18,7 @@
 #ifndef CQL_HOST_DATA_SOURCE_H
 #define CQL_HOST_DATA_SOURCE_H
 
+#include <asiolink/io_service.h>
 #include <cql/cql_connection.h>
 #include <dhcpsrv/base_host_data_source.h>
 
@@ -86,7 +87,8 @@ public:
     /// schema version is invalid.
     /// @throw isc::db::DbOperationError An operation on the open database has
     ///        failed.
-    explicit CqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
+    explicit CqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters,
+                               const isc::asiolink::IOServicePtr& io_service);
 
     /// @brief Virtual destructor.
     ///
index 00a71b30935c0103ca5f2666f56ecb8840b80a6d..7de0f787a70075d3a2daab664c642f7ad28f87fe 100644 (file)
@@ -34,6 +34,7 @@
 #include <sstream>
 #include <utility>
 
+using namespace isc::asiolink;
 using namespace isc::db;
 using namespace std;
 
@@ -44,7 +45,8 @@ map<string, HostDataSourceFactory::Factory> HostDataSourceFactory::map_;
 
 void
 HostDataSourceFactory::add(HostDataSourceList& sources,
-                           const string& dbaccess) {
+                           const string& dbaccess,
+                           const IOServicePtr& io_service) {
     // Parse the access string and create a redacted string for logging.
     DatabaseConnection::ParameterMap parameters =
             DatabaseConnection::parse(dbaccess);
@@ -74,7 +76,7 @@ HostDataSourceFactory::add(HostDataSourceList& sources,
     }
 
     // Call the factory and push the pointer on sources.
-    sources.push_back(boost::shared_ptr<BaseHostDataSource>(index->second(parameters)));
+    sources.push_back(index->second(parameters, io_service));
 
     // Check the factory did not return NULL.
     if (!sources.back()) {
@@ -177,10 +179,11 @@ struct MySqlHostDataSourceInit {
 
     // Factory class method
     static HostDataSourcePtr
-    factory(const DatabaseConnection::ParameterMap& parameters) {
+    factory(const DatabaseConnection::ParameterMap& parameters,
+            const isc::asiolink::IOServicePtr& io_service) {
         LOG_INFO(hosts_logger, DHCPSRV_MYSQL_HOST_DB)
             .arg(DatabaseConnection::redactedAccessString(parameters));
-        return (HostDataSourcePtr(new MySqlHostDataSource(parameters)));
+        return (HostDataSourcePtr(new MySqlHostDataSource(parameters, io_service)));
     }
 };
 
@@ -202,10 +205,11 @@ struct PgSqlHostDataSourceInit {
 
     // Factory class method
     static HostDataSourcePtr
-    factory(const DatabaseConnection::ParameterMap& parameters) {
+    factory(const DatabaseConnection::ParameterMap& parameters,
+            const isc::asiolink::IOServicePtr& io_service) {
         LOG_INFO(hosts_logger, DHCPSRV_PGSQL_HOST_DB)
             .arg(DatabaseConnection::redactedAccessString(parameters));
-        return (HostDataSourcePtr(new PgSqlHostDataSource(parameters)));
+        return (HostDataSourcePtr(new PgSqlHostDataSource(parameters, io_service)));
     }
 };
 
@@ -227,10 +231,11 @@ struct CqlHostDataSourceInit {
 
     // Factory class method
     static HostDataSourcePtr
-    factory(const DatabaseConnection::ParameterMap& parameters) {
+    factory(const DatabaseConnection::ParameterMap& parameters,
+            const isc::asiolink::IOServicePtr& io_service) {
         LOG_INFO(hosts_logger, DHCPSRV_CQL_HOST_DB)
             .arg(DatabaseConnection::redactedAccessString(parameters));
-        return (HostDataSourcePtr(new CqlHostDataSource(parameters)));
+        return (HostDataSourcePtr(new CqlHostDataSource(parameters, io_service)));
     }
 };
 
index b39ad954e49d97759ef4d488904dfb90b6678d2e..924f06b46b4840aa5ad52bfe482374bb3e4ed8fb 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef HOST_DATA_SOURCE_FACTORY_H
 #define HOST_DATA_SOURCE_FACTORY_H
 
+#include <asiolink/io_service.h>
 #include <database/database_connection.h>
 #include <dhcpsrv/base_host_data_source.h>
 #include <exceptions/exceptions.h>
@@ -58,12 +59,14 @@ public:
     ///        "keyword=value" pairs, separated by spaces. They are backend-
     ///        -end specific, although must include the "type" keyword which
     ///        gives the backend in use.
+    /// @param io_service The IOService object, used for all ASIO operations.
     ///
     /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
     ///        keyword.
     /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
     ///        identify a supported backend.
-    static void add(HostDataSourceList& sources, const std::string& dbaccess);
+    static void add(HostDataSourceList& sources, const std::string& dbaccess,
+                    const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr());
 
     /// @brief Delete a host data source.
     ///
@@ -79,7 +82,8 @@ public:
     ///
     /// A factory takes a parameter map and returns a pointer to a host
     /// data source. In case of failure it must throw and not return NULL.
-    typedef std::function<HostDataSourcePtr (const db::DatabaseConnection::ParameterMap&)> Factory;
+    typedef std::function<HostDataSourcePtr (const db::DatabaseConnection::ParameterMap&,
+                                             const isc::asiolink::IOServicePtr&)> Factory;
 
     /// @brief Register a host data source factory
     ///
index 78cc1121a4b6d4d94842768a865a07144468801a..b401b304769ea73c8d25836344b43f658b4e53c6 100644 (file)
@@ -42,8 +42,8 @@ HostMgr::create() {
 }
 
 void
-HostMgr::addBackend(const std::string& access) {
-    HostDataSourceFactory::add(getHostMgrPtr()->alternate_sources_, access);
+HostMgr::addBackend(const std::string& access, const IOServicePtr& io_service) {
+    HostDataSourceFactory::add(getHostMgrPtr()->alternate_sources_, access, io_service);
 }
 
 bool
index 4123e3da7ee75e2e8d10e4b45caa19951b309228..fced6d9f532d332c00396eb4045a940e8042ea8b 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef HOST_MGR_H
 #define HOST_MGR_H
 
+#include <asiolink/io_service.h>
 #include <database/database_connection.h>
 #include <dhcpsrv/base_host_data_source.h>
 #include <dhcpsrv/cache_host_data_source.h>
@@ -66,11 +67,14 @@ public:
     ///
     /// @param access Host backend access parameters for the alternate
     /// host backend. It holds "keyword=value" pairs, separated by spaces.
+    /// @param io_service The IOService object, used for all ASIO operations.
+    ///
     /// The supported values are specific to the alternate backend in use.
     /// However, the "type" parameter will be common and it will specify which
     /// backend is to be used. Currently, no parameters are supported
     /// and the parameter is ignored.
-    static void addBackend(const std::string& access);
+    static void addBackend(const std::string& access,
+                           const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr());
 
     /// @brief Delete an alternate host backend (aka host data source).
     ///
index 30269c02c8f8ded90332940df02bc5da57745cc1..e41ae276e6f7e4be3d201b9ac86fafecdd616d80 100644 (file)
@@ -42,7 +42,8 @@ LeaseMgrFactory::getLeaseMgrPtr() {
 }
 
 void
-LeaseMgrFactory::create(const std::string& dbaccess) {
+LeaseMgrFactory::create(const std::string& dbaccess,
+                        const isc::asiolink::IOServicePtr& io_service) {
     const std::string type = "type";
 
     // Parse the access string and create a redacted string for logging.
@@ -61,7 +62,7 @@ LeaseMgrFactory::create(const std::string& dbaccess) {
     if (parameters[type] == string("mysql")) {
 #ifdef HAVE_MYSQL
         LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
-        getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
+        getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters, io_service));
         return;
 #else
         LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg("mysql");
@@ -73,7 +74,7 @@ LeaseMgrFactory::create(const std::string& dbaccess) {
     if (parameters[type] == string("postgresql")) {
 #ifdef HAVE_PGSQL
         LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
-        getLeaseMgrPtr().reset(new PgSqlLeaseMgr(parameters));
+        getLeaseMgrPtr().reset(new PgSqlLeaseMgr(parameters, io_service));
         return;
 #else
         LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg("postgresql");
index 9938643868858d78720072d6f838d40d9ec6f65f..792f3e5413ae789c75e301d4df3ee7e2a7768476 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef LEASE_MGR_FACTORY_H
 #define LEASE_MGR_FACTORY_H
 
+#include <asiolink/io_service.h>
 #include <database/database_connection.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <exceptions/exceptions.h>
@@ -62,12 +63,14 @@ public:
     ///        "keyword=value" pairs, separated by spaces. They are backend-
     ///        -end specific, although must include the "type" keyword which
     ///        gives the backend in use.
+    /// @param io_service The IOService object, used for all ASIO operations.
     ///
     /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
     ///        keyword.
     /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
     ///        identify a supported backend.
-    static void create(const std::string& dbaccess);
+    static void create(const std::string& dbaccess,
+                       const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr());
 
     /// @brief Destroy lease manager
     ///
@@ -97,10 +100,9 @@ private:
     /// is encapsulated in this method to avoid a "static initialization
     /// fiasco" if defined in an external static variable.
     static boost::scoped_ptr<LeaseMgr>& getLeaseMgrPtr();
-
 };
 
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
 
 #endif // LEASE_MGR_FACTORY_H
index a1c712e78a0fce3ade5be7e983f1a6fa91685179..04e8a1302fd83f2718c4b801f20e98d742f16472 100644 (file)
@@ -2054,7 +2054,8 @@ public:
     ///
     /// This constructor opens database connection and initializes prepared
     /// statements used in the queries.
-    MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters);
+    MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters,
+                            const isc::asiolink::IOServicePtr& io_service);
 
     /// @brief Destructor.
     ~MySqlHostDataSourceImpl();
@@ -2730,7 +2731,8 @@ MySqlHostDataSource::MySqlHostContextAlloc::~MySqlHostContextAlloc() {
     // If running in single-threaded mode, there's nothing to do here.
 }
 
-MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
+MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters,
+                                                 const isc::asiolink::IOServicePtr& io_service)
     : parameters_(parameters), ip_reservations_unique_(true) {
 
     // Validate the schema version first.
@@ -3023,8 +3025,9 @@ MySqlHostDataSourceImpl::checkReadOnly(MySqlHostContextPtr& ctx) const {
     }
 }
 
-MySqlHostDataSource::MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
-    : impl_(new MySqlHostDataSourceImpl(parameters)) {
+MySqlHostDataSource::MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters,
+                                         const isc::asiolink::IOServicePtr& io_service)
+    : impl_(new MySqlHostDataSourceImpl(parameters, io_service)) {
 }
 
 MySqlHostDataSource::~MySqlHostDataSource() {
index 0a0495d3d0755a1789313fc163b92e0077c4c869..25d1a618e11a4812787dc5339937d5903151a651 100644 (file)
@@ -62,7 +62,8 @@ public:
     /// schema version is invalid.
     /// @throw isc::db::DbOperationError An operation on the open database has
     ///        failed.
-    MySqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
+    MySqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters,
+                        const isc::asiolink::IOServicePtr& io_service);
 
     /// @brief Virtual destructor.
     ///
index 2e9a6a714b25515bb04dd62346ba9246e383d384..86040d79fac0970387c71d75a4e4e297225069b2 100644 (file)
@@ -9,8 +9,12 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
 #include <mysql/mysql_connection.h>
 #include <util/multi_threading_mgr.h>
 
@@ -1732,8 +1736,10 @@ bool MySqlLeaseStatsQuery::negative_count_ = false;
 
 // MySqlLeaseContext Constructor
 
-MySqlLeaseContext::MySqlLeaseContext(const DatabaseConnection::ParameterMap& parameters)
-    : conn_(parameters) {
+MySqlLeaseContext::MySqlLeaseContext(const MySqlConnection::ParameterMap& parameters,
+                                     const isc::asiolink::IOServicePtr& io_service,
+                                     DbCallback callback)
+    : conn_(parameters, io_service, callback) {
 }
 
 // MySqlLeaseContextAlloc Constructor and Destructor
@@ -1774,8 +1780,9 @@ MySqlLeaseMgr::MySqlLeaseContextAlloc::~MySqlLeaseContextAlloc() {
 
 // MySqlLeaseMgr Constructor and Destructor
 
-MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters)
-    : parameters_(parameters) {
+MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters,
+                             const IOServicePtr& io_service)
+    : parameters_(parameters), io_service_(io_service) {
 
     // Validate schema version first.
     std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
@@ -1792,16 +1799,81 @@ MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters)
     // Create an initial context.
     pool_.reset(new MySqlLeaseContextPool());
     pool_->pool_.push_back(createContext());
+
+    auto db_reconnect_ctl = pool_->pool_[0]->conn_.reconnectCtl();
+
+    std::string manager = "MySqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    TimerMgr::instance()->registerTimer(timer_name,
+        std::bind(&MySqlLeaseMgr::dbReconnect, db_reconnect_ctl),
+                  db_reconnect_ctl->retryInterval(),
+                  asiolink::IntervalTimer::ONE_SHOT);
 }
 
 MySqlLeaseMgr::~MySqlLeaseMgr() {
+    std::string manager = "MySqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    TimerMgr::instance()->unregisterTimer(timer_name);
+}
+
+bool
+MySqlLeaseMgr::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+    MultiThreadingCriticalSection cs;
+
+    DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl);
+
+    bool reopened = false;
+
+    // At least one connection was lost.
+    try {
+        CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+        LeaseMgrFactory::destroy();
+        LeaseMgrFactory::create(cfg_db->getLeaseDbAccessString()/*, io_service_ */);
+
+        reopened = true;
+    } catch (const std::exception& ex) {
+        //LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_DB_RECONNECT_ATTEMPT_FAILED)
+        //        .arg(ex.what());
+    }
+
+    if (reopened) {
+        // Cancel the timer.
+        const std::string& timer_name = db_reconnect_ctl->timerName();
+        TimerMgr::instance()->cancel(timer_name);
+
+        DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl);
+    } else {
+        if (!db_reconnect_ctl->checkRetries()) {
+            // We're out of retries, log it and initiate shutdown.
+            //LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_DB_RECONNECT_RETRIES_EXHAUSTED)
+            //        .arg(db_reconnect_ctl->maxRetries());
+
+            DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+
+            return (false);
+        }
+
+        //LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB_RECONNECT_ATTEMPT_SCHEDULE)
+        //        .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+        //        .arg(db_reconnect_ctl->maxRetries())
+        //        .arg(db_reconnect_ctl->retryInterval());
+
+        TimerMgr::instance()->setup(db_reconnect_ctl->timerName());
+    }
+
+    return (true);
 }
 
 // Create context.
 
 MySqlLeaseContextPtr
 MySqlLeaseMgr::createContext() const {
-    MySqlLeaseContextPtr ctx(new MySqlLeaseContext(parameters_));
+    MySqlLeaseContextPtr ctx(new MySqlLeaseContext(parameters_, io_service_,
+                                                   &MySqlLeaseMgr::dbReconnect));
 
     // Open the database.
     ctx->conn_.openDatabase();
@@ -1815,6 +1887,12 @@ MySqlLeaseMgr::createContext() const {
     ctx->exchange4_.reset(new MySqlLease4Exchange());
     ctx->exchange6_.reset(new MySqlLease6Exchange());
 
+    std::string manager = "MySqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    ctx->conn_.makeReconnectCtl(timer_name);
+
     return (ctx);
 }
 
index 64af58ad6c04152fa001249c38afa7b2148558ba..073e674174fe1e78926bf536e773e07e92e94409 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef MYSQL_LEASE_MGR_H
 #define MYSQL_LEASE_MGR_H
 
+#include <asiolink/io_service.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/dhcpsrv_exceptions.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -43,7 +44,10 @@ public:
     /// @brief Constructor
     ///
     /// @param parameters See MySqlLeaseMgr constructor.
-    MySqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters);
+    /// @param io_service The IOService object, used for all ASIO operations.
+    MySqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters,
+                      const isc::asiolink::IOServicePtr& io_service,
+                      db::DbCallback callback);
 
     /// The exchange objects are used for transfer of data to/from the database.
     /// They are pointed-to objects as the contents may change in "const" calls,
@@ -100,13 +104,15 @@ public:
     ///
     /// @param parameters A data structure relating keywords and values
     ///        concerned with the database.
+    /// @param io_service The IOService object, used for all ASIO operations.
     ///
     /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
     /// @throw isc::db::DbOpenError Error opening the database or the schema
     /// version is incorrect.
     /// @throw isc::db::DbOperationError An operation on the open database has
     ///        failed.
-    MySqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters);
+    MySqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters,
+                  const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr());
 
     /// @brief Destructor (closes database)
     virtual ~MySqlLeaseMgr();
@@ -121,6 +127,27 @@ public:
     /// failed.
     MySqlLeaseContextPtr createContext() const;
 
+    /// @brief Attempts to reconnect the server to the lease DB backend manager.
+    ///
+    /// This is a self-rescheduling function that attempts to reconnect to the
+    /// server's lease DB backends after connectivity to one or more have been
+    /// lost. Upon entry it will attempt to reconnect via
+    /// @ref LeaseMgrFactory::create.
+    /// If this is successful, DHCP servicing is re-enabled and server returns
+    /// to normal operation.
+    ///
+    /// If reconnection fails and the maximum number of retries has not been
+    /// exhausted, it will schedule a call to itself to occur at the
+    /// configured retry interval. DHCP service remains disabled.
+    ///
+    /// If the maximum number of retries has been exhausted an error is logged
+    /// and the server shuts down.
+    ///
+    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+    /// configured reconnect parameters.
+    /// @return true if connection has been recovered, false otherwise.
+    static bool dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
+
     /// @brief Local version of getDBVersion() class method
     static std::string getDBVersion();
 
@@ -959,6 +986,9 @@ private:
 
     /// @brief The pool of contexts
     MySqlLeaseContextPoolPtr pool_;
+
+    /// @brief IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_service_;
 };
 
 }  // namespace dhcp
index d0f3c02c8e8d3d4ea68ea54889f6fd5364a42b2b..95dbe4e4690f115a3eb11ac6c23ce9a7b6889a82 100644 (file)
@@ -1416,7 +1416,8 @@ public:
     ///
     /// This constructor opens database connection and initializes prepared
     /// statements used in the queries.
-    PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters);
+    PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters,
+                            const isc::asiolink::IOServicePtr& io_service);
 
     /// @brief Destructor.
     ~PgSqlHostDataSourceImpl();
@@ -2171,7 +2172,8 @@ PgSqlHostDataSource::PgSqlHostContextAlloc::~PgSqlHostContextAlloc() {
     // If running in single-threaded mode, there's nothing to do here.
 }
 
-PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
+PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters,
+                                                 const isc::asiolink::IOServicePtr& io_service)
     : parameters_(parameters), ip_reservations_unique_(true) {
 
     // Validate the schema version first.
@@ -2432,8 +2434,9 @@ PgSqlHostDataSourceImpl::checkReadOnly(PgSqlHostContextPtr& ctx) const {
 
 /*********** PgSqlHostDataSource *********************/
 
-PgSqlHostDataSource::PgSqlHostDataSource(const PgSqlConnection::ParameterMap& parameters)
-    : impl_(new PgSqlHostDataSourceImpl(parameters)) {
+PgSqlHostDataSource::PgSqlHostDataSource(const PgSqlConnection::ParameterMap& parameters,
+                                         const isc::asiolink::IOServicePtr& io_service)
+    : impl_(new PgSqlHostDataSourceImpl(parameters, io_service)) {
 }
 
 PgSqlHostDataSource::~PgSqlHostDataSource() {
index 55b76afd89926a5739e54a372b01d8101ac0ccfb..f00d487a28a74fc6421091158cd41b5cd53b19dc 100644 (file)
@@ -66,7 +66,8 @@ public:
     /// @throw isc::db::DbOpenError Error opening the database
     /// @throw isc::db::DbOperationError An operation on the open database has
     ///        failed.
-   PgSqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
+   PgSqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters,
+                       const isc::asiolink::IOServicePtr& io_service);
 
     /// @brief Virtual destructor.
     /// Frees database resources and closes the database connection through
index 8d9ebcb6346cdf43ae73125886ac0d803813265a..6cd6a82a4bf0859628e07d0c2485f37a53d53c32 100644 (file)
@@ -9,9 +9,13 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/pgsql_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
 #include <util/multi_threading_mgr.h>
 
 #include <boost/make_shared.hpp>
@@ -1166,8 +1170,10 @@ bool PgSqlLeaseStatsQuery::negative_count_ = false;
 
 // PgSqlLeaseContext Constructor
 
-PgSqlLeaseContext::PgSqlLeaseContext(const DatabaseConnection::ParameterMap& parameters)
-    : conn_(parameters) {
+PgSqlLeaseContext::PgSqlLeaseContext(const PgSqlConnection::ParameterMap& parameters,
+                                     const isc::asiolink::IOServicePtr& io_service,
+                                     DbCallback callback)
+    : conn_(parameters, io_service, callback) {
 }
 
 // PgSqlLeaseContextAlloc Constructor and Destructor
@@ -1208,8 +1214,9 @@ PgSqlLeaseMgr::PgSqlLeaseContextAlloc::~PgSqlLeaseContextAlloc() {
 
 // PgSqlLeaseMgr Constructor and Destructor
 
-PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
-    : parameters_(parameters) {
+PgSqlLeaseMgr::PgSqlLeaseMgr(const PgSqlConnection::ParameterMap& parameters,
+                             const IOServicePtr& io_service)
+    : parameters_(parameters), io_service_(io_service) {
 
     // Validate schema version first.
     std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
@@ -1226,16 +1233,81 @@ PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
     // Create an initial context.
     pool_.reset(new PgSqlLeaseContextPool());
     pool_->pool_.push_back(createContext());
+
+    auto db_reconnect_ctl = pool_->pool_[0]->conn_.reconnectCtl();
+
+    std::string manager = "PgSqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    TimerMgr::instance()->registerTimer(timer_name,
+        std::bind(&PgSqlLeaseMgr::dbReconnect, db_reconnect_ctl),
+                  db_reconnect_ctl->retryInterval(),
+                  asiolink::IntervalTimer::ONE_SHOT);
 }
 
 PgSqlLeaseMgr::~PgSqlLeaseMgr() {
+    std::string manager = "PgSqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    TimerMgr::instance()->unregisterTimer(timer_name);
+}
+
+bool
+PgSqlLeaseMgr::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+    MultiThreadingCriticalSection cs;
+
+    DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl);
+
+    bool reopened = false;
+
+    // At least one connection was lost.
+    try {
+        CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+        LeaseMgrFactory::destroy();
+        LeaseMgrFactory::create(cfg_db->getLeaseDbAccessString()/*, io_service_ */);
+
+        reopened = true;
+    } catch (const std::exception& ex) {
+        //LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_DB_RECONNECT_ATTEMPT_FAILED)
+        //        .arg(ex.what());
+    }
+
+    if (reopened) {
+        // Cancel the timer.
+        const std::string& timer_name = db_reconnect_ctl->timerName();
+        TimerMgr::instance()->cancel(timer_name);
+
+        DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl);
+    } else {
+        if (!db_reconnect_ctl->checkRetries()) {
+            // We're out of retries, log it and initiate shutdown.
+            //LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_DB_RECONNECT_RETRIES_EXHAUSTED)
+            //        .arg(db_reconnect_ctl->maxRetries());
+
+            DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+
+            return (false);
+        }
+
+        //LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB_RECONNECT_ATTEMPT_SCHEDULE)
+        //        .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+        //        .arg(db_reconnect_ctl->maxRetries())
+        //        .arg(db_reconnect_ctl->retryInterval());
+
+        TimerMgr::instance()->setup(db_reconnect_ctl->timerName());
+    }
+
+    return (true);
 }
 
 // Create context.
 
 PgSqlLeaseContextPtr
 PgSqlLeaseMgr::createContext() const {
-    PgSqlLeaseContextPtr ctx(new PgSqlLeaseContext(parameters_));
+    PgSqlLeaseContextPtr ctx(new PgSqlLeaseContext(parameters_, io_service_,
+                                                   &PgSqlLeaseMgr::dbReconnect));
 
     // Open the database.
     ctx->conn_.openDatabase();
@@ -1257,6 +1329,12 @@ PgSqlLeaseMgr::createContext() const {
     ctx->exchange4_.reset(new PgSqlLease4Exchange());
     ctx->exchange6_.reset(new PgSqlLease6Exchange());
 
+    std::string manager = "PgSqlLeaseMgr[";
+    manager += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+    std::string timer_name = manager + "]DbReconnectTimer";
+
+    ctx->conn_.makeReconnectCtl(timer_name);
+
     return (ctx);
 }
 
index e221854e9faa3b3ef24021871f8b091cad24593f..2c8d1118496766e73a8cc27b69cfdeaf8db6b043 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef PGSQL_LEASE_MGR_H
 #define PGSQL_LEASE_MGR_H
 
+#include <asiolink/io_service.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/dhcpsrv_exceptions.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -42,7 +43,10 @@ public:
     /// @brief Constructor
     ///
     /// @param parameters See PgSqlLeaseMgr constructor.
-    PgSqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters);
+    /// @param io_service The IOService object, used for all ASIO operations.
+    PgSqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters,
+                      const isc::asiolink::IOServicePtr& io_service,
+                      db::DbCallback callback);
 
     /// The exchange objects are used for transfer of data to/from the database.
     /// They are pointed-to objects as the contents may change in "const" calls,
@@ -99,13 +103,15 @@ public:
     ///
     /// @param parameters A data structure relating keywords and values
     ///        concerned with the database.
+    /// @param io_service The IOService object, used for all ASIO operations.
     ///
     /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
     /// @throw isc::db::DbOpenError Error opening the database or the schema
     /// version is incorrect.
     /// @throw isc::db::DbOperationError An operation on the open database has
     ///        failed.
-    PgSqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters);
+    PgSqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters,
+                  const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr());
 
     /// @brief Destructor (closes database)
     virtual ~PgSqlLeaseMgr();
@@ -120,6 +126,27 @@ public:
     /// failed.
     PgSqlLeaseContextPtr createContext() const;
 
+    /// @brief Attempts to reconnect the server to the lease DB backend manager.
+    ///
+    /// This is a self-rescheduling function that attempts to reconnect to the
+    /// server's lease DB backends after connectivity to one or more have been
+    /// lost. Upon entry it will attempt to reconnect via
+    /// @ref LeaseMgrFactory::create.
+    /// If this is successful, DHCP servicing is re-enabled and server returns
+    /// to normal operation.
+    ///
+    /// If reconnection fails and the maximum number of retries has not been
+    /// exhausted, it will schedule a call to itself to occur at the
+    /// configured retry interval. DHCP service remains disabled.
+    ///
+    /// If the maximum number of retries has been exhausted an error is logged
+    /// and the server shuts down.
+    ///
+    /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+    /// configured reconnect parameters.
+    /// @return true if connection has been recovered, false otherwise.
+    static bool dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
+
     /// @brief Local version of getDBVersion() class method
     static std::string getDBVersion();
 
@@ -918,6 +945,9 @@ private:
 
     /// @brief The pool of contexts
     PgSqlLeaseContextPoolPtr pool_;
+
+    /// @brief IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_service_;
 };
 
 }  // namespace dhcp
index 6f926e32966575fdad095960ba3731d2bd43125b..93d3a16adfb5c7789e9e7595dfaaed7e5d7030ad 100644 (file)
@@ -900,9 +900,10 @@ TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueAccepted) {
     // Create host data source which accepts setting non-unique IP
     // reservations.
     MemHostDataSourcePtr hds(new MemHostDataSource());
-    auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+    auto testFactory = [hds](const DatabaseConnection::ParameterMap&,
+                             const IOServicePtr&) {
         return (hds);
-     };
+    };
     HostDataSourceFactory::registerFactory("test", testFactory);
     HostMgr::addBackend("type=test");
 
@@ -934,9 +935,10 @@ TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueRefused) {
     // Create host data source which does not accept setting IP reservations
     // non-unique setting.
     NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
-    auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+    auto testFactory = [hds](const DatabaseConnection::ParameterMap&,
+                             const IOServicePtr&) {
         return (hds);
-     };
+    };
     HostDataSourceFactory::registerFactory("test", testFactory);
     HostMgr::addBackend("type=test");
 
@@ -1617,9 +1619,10 @@ TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueAccepted) {
     // Create host data source which accepts setting non-unique IP
     // reservations.
     MemHostDataSourcePtr hds(new MemHostDataSource());
-    auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+    auto testFactory = [hds](const DatabaseConnection::ParameterMap&,
+                             const IOServicePtr&) {
         return (hds);
-     };
+    };
     HostDataSourceFactory::registerFactory("test", testFactory);
     HostMgr::addBackend("type=test");
 
@@ -1651,9 +1654,10 @@ TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueRefused) {
     // Create host data source which does not accept setting IP reservations
     // non-unique setting.
     NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
-    auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+    auto testFactory = [hds](const DatabaseConnection::ParameterMap&,
+                             const IOServicePtr&) {
         return (hds);
-     };
+    };
     HostDataSourceFactory::registerFactory("test", testFactory);
     HostMgr::addBackend("type=test");
 
index 51c85b0fce842cc2e8282af18f9dcb18b4aa2fca..6fca3a4f1f449985881f09ebac860fdc2639ca90 100644 (file)
@@ -3271,7 +3271,7 @@ LeaseMgrDbLostCallbackTest::TearDown() {
 
 void
 LeaseMgrDbLostCallbackTest::testNoCallbackOnOpenFailure() {
-    DatabaseConnection::db_lost_callback =
+    DatabaseConnection::db_lost_callback_ =
         std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
 
     callback_called_ = false;
@@ -3284,7 +3284,7 @@ LeaseMgrDbLostCallbackTest::testNoCallbackOnOpenFailure() {
 void
 LeaseMgrDbLostCallbackTest::testDbLostCallback() {
     // Set the connectivity lost callback.
-    DatabaseConnection::db_lost_callback =
+    DatabaseConnection::db_lost_callback_ =
         std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
 
     // Find the most recently opened socket. Our SQL client's socket should
index 1bf00d41faf4d6e22a2eb4aae8c982e635927446..75fe229a4528e32c3061e1c3f3906b0689463f86 100644 (file)
@@ -522,11 +522,11 @@ public:
 class LeaseMgrDbLostCallbackTest : public ::testing::Test {
 public:
     LeaseMgrDbLostCallbackTest() {
-        db::DatabaseConnection::db_lost_callback = 0;
+        db::DatabaseConnection::db_lost_callback_ = 0;
     }
 
     virtual ~LeaseMgrDbLostCallbackTest() {
-        db::DatabaseConnection::db_lost_callback = 0;
+        db::DatabaseConnection::db_lost_callback_ = 0;
     }
 
     /// @brief Prepares the class for a test.
index c6ad92009845c7a3a9e7c12097a24e9ce09fdf7f..937e705b4794af8d6610c36ba926deee92f96b2d 100644 (file)
@@ -122,7 +122,8 @@ public:
 
         // Host cache.
         hcptr_.reset(new TestHostCache());
-        auto cacheFactory = [this](const DatabaseConnection::ParameterMap&) {
+        auto cacheFactory = [this](const DatabaseConnection::ParameterMap&,
+                                   const isc::asiolink::IOServicePtr&) {
             return (hcptr_);
         };
         HostDataSourceFactory::registerFactory("cache", cacheFactory);
@@ -130,7 +131,8 @@ public:
 
         // Host data source.
         memptr_.reset(new TestHostDataSource());
-        auto testFactory = [this](const DatabaseConnection::ParameterMap&) {
+        auto testFactory = [this](const DatabaseConnection::ParameterMap&,
+                                  const isc::asiolink::IOServicePtr&) {
             return (memptr_);
         };
         HostDataSourceFactory::registerFactory("test", testFactory);
@@ -795,7 +797,8 @@ public:
 
         // No cache.
         ncptr_.reset(new TestNoCache());
-        auto nocacheFactory = [this](const DatabaseConnection::ParameterMap&) {
+        auto nocacheFactory = [this](const DatabaseConnection::ParameterMap&,
+                                     const isc::asiolink::IOServicePtr&) {
             return (ncptr_);
         };
         HostDataSourceFactory::registerFactory("nocache", nocacheFactory);
@@ -803,7 +806,8 @@ public:
 
         // One backend.
         obptr_.reset(new TestOneBackend());
-        auto oneFactory = [this](const DatabaseConnection::ParameterMap&) {
+        auto oneFactory = [this](const DatabaseConnection::ParameterMap&,
+                                 const isc::asiolink::IOServicePtr&) {
             return (obptr_);
         };
         HostDataSourceFactory::registerFactory("one", oneFactory);
index dba626d8668d4d19be4dcb348f581898606a81a5..38cbd5ecd8983bf17a9b636882e8cb0b9a56d86c 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <config.h>
 
+#include <asiolink/io_service.h>
 #include <dhcpsrv/host_data_source_factory.h>
 #include <dhcpsrv/testutils/memory_host_data_source.h>
 #include <exceptions/exceptions.h>
@@ -17,6 +18,7 @@
 
 using namespace std;
 using namespace isc;
+using namespace isc::asiolink;
 using namespace isc::db;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
@@ -38,7 +40,7 @@ public:
 
 // @brief Factory of mem1
 HostDataSourcePtr
-mem1Factory(const DatabaseConnection::ParameterMap&) {
+mem1Factory(const DatabaseConnection::ParameterMap&, const IOServicePtr&) {
     return (HostDataSourcePtr(new Mem1HostDataSource()));
 }
 
@@ -57,7 +59,7 @@ public:
 
 // @brief Factory of mem2
 HostDataSourcePtr
-mem2Factory(const DatabaseConnection::ParameterMap&) {
+mem2Factory(const DatabaseConnection::ParameterMap&, const IOServicePtr&) {
     return (HostDataSourcePtr(new Mem2HostDataSource()));
 }
 
@@ -67,7 +69,8 @@ bool registerFactory2() {
 }
 
 // @brief Factory function returning 0
-HostDataSourcePtr factory0(const DatabaseConnection::ParameterMap&) {
+HostDataSourcePtr
+factory0(const DatabaseConnection::ParameterMap&, const IOServicePtr&) {
     return (HostDataSourcePtr());
 }
 
index 7d1051feaff06a74cc53327346136395603d286f..973c08358e1fc1545fdc62611c92ff35f3a8435a 100644 (file)
@@ -1383,7 +1383,7 @@ public:
     /// appropriate schema and create a basic host manager to
     /// wipe out any prior instance
     virtual void SetUp() {
-        DatabaseConnection::db_lost_callback = 0;
+        DatabaseConnection::db_lost_callback_ = 0;
         // Ensure we have the proper schema with no transient data.
         createSchema();
         // Wipe out any pre-existing mgr
@@ -1395,7 +1395,7 @@ public:
     /// Invoked by gtest upon test exit, we destroy the schema
     /// we created.
     virtual void TearDown() {
-        DatabaseConnection::db_lost_callback = 0;
+        DatabaseConnection::db_lost_callback_ = 0;
         // If data wipe enabled, delete transient data otherwise destroy the schema
         destroySchema();
     }
@@ -1440,7 +1440,7 @@ HostMgrDbLostCallbackTest::testDbLostCallback() {
     HostMgr::create();
 
     // Set the connectivity lost callback.
-    DatabaseConnection::db_lost_callback =
+    DatabaseConnection::db_lost_callback_ =
         std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
 
     // Find the most recently opened socket. Our SQL client's socket should
index fdfc6f73ffd21f75dfd895677aa9a4b905e935cf..84b911dd45f511d913907fbd4b85500974e157df 100644 (file)
@@ -6,8 +6,10 @@
 
 #include <config.h>
 
+#include <asiolink/io_service.h>
 #include <dhcpsrv/testutils/memory_host_data_source.h>
 
+using namespace isc::asiolink;
 using namespace isc::db;
 using namespace std;
 
@@ -381,7 +383,8 @@ MemHostDataSource::size() const {
 }
 
 HostDataSourcePtr
-memFactory(const DatabaseConnection::ParameterMap& /*parameters*/) {
+memFactory(const DatabaseConnection::ParameterMap& /*parameters*/,
+           const IOServicePtr& /*io_service*/) {
     return (HostDataSourcePtr(new MemHostDataSource()));
 }
 
index b2d06f48f2ff7db1ef0f97121641637d6862738c..9cff839ad6a6605a902680f5eac05eec0bceb2db 100644 (file)
@@ -329,7 +329,8 @@ typedef boost::shared_ptr<MemHostDataSource> MemHostDataSourcePtr;
 /// @param parameters
 /// @return A pointer to a base host data source instance.
 HostDataSourcePtr
-memFactory(const db::DatabaseConnection::ParameterMap& /*parameters*/);
+memFactory(const db::DatabaseConnection::ParameterMap& /*parameters*/,
+           const isc::asiolink::IOServicePtr& /*io_service*/);
 
 } // namespace isc::dhcp::test
 } // namespace isc::dhcp
index c9808b803d034ae56b0571f15aa1ef1a6a9b72a4..ada6eb495f4eecd257afadeb82771e983e8367ba 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef MYSQL_CONNECTION_H
 #define MYSQL_CONNECTION_H
 
+#include <asiolink/io_service.h>
 #include <database/database_connection.h>
 #include <database/db_exceptions.h>
 #include <database/db_log.h>
@@ -239,8 +240,11 @@ public:
     /// @brief Constructor
     ///
     /// Initialize MySqlConnection object with parameters needed for connection.
-    MySqlConnection(const ParameterMap& parameters)
-        : DatabaseConnection(parameters) {
+    MySqlConnection(const ParameterMap& parameters,
+                    const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr(),
+                    DbCallback callback = DbCallback())
+        : DatabaseConnection(parameters), io_service_(io_service),
+          callback_(callback) {
     }
 
     /// @brief Destructor
@@ -514,7 +518,7 @@ public:
     /// @tparam StatementIndex Type of the statement index enum.
     ///
     /// @param index Index of the query to be executed.
-    /// @param in_bindings Input bindings holding values to substitue placeholders
+    /// @param in_bindings Input bindings holding values to substitute placeholders
     /// in the query.
     ///
     /// @return Number of affected rows.
@@ -586,10 +590,8 @@ public:
     ///
     /// If the error is recoverable, the function will throw a DbOperationError.
     /// If the error is deemed unrecoverable, such as a loss of connectivity
-    /// with the server, the function will call invokeDbLostCallback(). If the
-    /// invocation returns false then either there is no callback registered
-    /// or the callback has elected not to attempt to reconnect, and a
-    /// DbUnrecoverableError is thrown.
+    /// with the server, the function will call startRecoverDbConnection() which
+    /// will start the connection recovery.
     ///
     /// If the invocation returns true, this indicates the calling layer will
     /// attempt recovery, and the function throws a DbOperationError to allow
@@ -625,20 +627,16 @@ public:
                     .arg(mysql_error(mysql_))
                     .arg(mysql_errno(mysql_));
 
-                // Mark the connection as no longer usuable.
+                // 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()) {
-                    isc_throw(db::DbUnrecoverableError,
-                              "database connectivity cannot be recovered");
-                }
+                // Start the connection recovery.
+                startRecoverDbConnection();
 
                 // We still need to throw so caller can error out of the current
                 // processing.
                 isc_throw(db::DbConnectionUnusable,
-                          "fatal database errror or connectivity lost");
+                          "fatal database error or connectivity lost");
             default:
                 // Connection is ok, so it must be an SQL error
                 isc_throw(db::DbOperationError, what << " for <"
@@ -650,6 +648,17 @@ public:
         }
     }
 
+    /// @brief The recover connection
+    ///
+    /// This function starts the recover process of the connection.
+    ///
+    /// @note The recover function must be run on the IO Service thread.
+    void startRecoverDbConnection() {
+        if (callback_) {
+            io_service_->post(std::bind(callback_, reconnectCtl()));
+        }
+    }
+
     /// @brief Prepared statements
     ///
     /// This field is public, because it is used heavily from MySqlConnection
@@ -667,9 +676,15 @@ public:
     /// This field is public, because it is used heavily from MySqlConnection
     /// and will be from MySqlHostDataSource.
     MySqlHolder mysql_;
+
+    /// @brief IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief The callback used to recover the connection.
+    DbCallback callback_;
 };
 
-}; // end of isc::db namespace
-}; // end of isc namespace
+} // end of isc::db namespace
+} // end of isc namespace
 
 #endif // MYSQL_CONNECTION_H
index 3b32f3d39c74b4b208ec7a722d6a734d5b2760d4..b3b48bdb2a6196ecae7e5dbb1ab5d2fae1241b9f 100644 (file)
@@ -121,7 +121,7 @@ PgSqlConnection::~PgSqlConnection() {
         // Deallocate the prepared queries.
         if (PQstatus(conn_) == CONNECTION_OK) {
             PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
-            if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+            if (PQresultStatus(r) != PGRES_COMMAND_OK) {
                 // Highly unlikely but we'll log it and go on.
                 DB_LOG_ERROR(PGSQL_DEALLOC_ERROR)
                     .arg(PQerrorMessage(conn_));
@@ -159,7 +159,7 @@ PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
     // Prepare all statements queries with all known fields datatype
     PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
                             statement.nbparams, statement.types));
-    if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
         isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
                   << statement.text << ", reason: " << PQerrorMessage(conn_));
     }
@@ -324,7 +324,7 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
         // misleadingly returned as fatal. However, a loss of connectivity
         // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR.
         const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
-        if  ((sqlstate == NULL) ||
+        if ((sqlstate == NULL) ||
             ((memcmp(sqlstate, "08", 2) == 0) ||  // Connection Exception
              (memcmp(sqlstate, "53", 2) == 0) ||  // Insufficient resources
              (memcmp(sqlstate, "54", 2) == 0) ||  // Program Limit exceeded
@@ -338,12 +338,8 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
             // 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()) {
-                isc_throw(db::DbUnrecoverableError,
-                          "database connectivity cannot be recovered");
-            }
+            // Start the connection recovery.
+            startRecoverDbConnection();
 
             // We still need to throw so caller can error out of the current
             // processing.
@@ -354,9 +350,9 @@ PgSqlConnection::checkStatementError(const PgSqlResult& r,
         // Apparently it wasn't fatal, so we throw with a helpful message.
         const char* error_message = PQerrorMessage(conn_);
         isc_throw(DbOperationError, "Statement exec failed:" << " for: "
-                << statement.name << ", status: " << s
-                << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
-                <<  "], reason: " << error_message);
+                  << statement.name << ", status: " << s
+                  << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
+                  << " ], reason: " << error_message);
     }
 }
 
@@ -394,5 +390,5 @@ PgSqlConnection::rollback() {
     }
 }
 
-}; // end of isc::db namespace
-}; // end of isc namespace
+} // end of isc::db namespace
+} // end of isc namespace
index 3920bd81c39d650c54582801c4fb6be49e307d83..9337727f5875082fdffcfe13944f65cf76756285 100644 (file)
@@ -6,6 +6,7 @@
 #ifndef PGSQL_CONNECTION_H
 #define PGSQL_CONNECTION_H
 
+#include <asiolink/io_service.h>
 #include <database/database_connection.h>
 
 #include <libpq-fe.h>
@@ -304,8 +305,11 @@ public:
     /// @brief Constructor
     ///
     /// Initialize PgSqlConnection object with parameters needed for connection.
-    PgSqlConnection(const ParameterMap& parameters)
-        : DatabaseConnection(parameters) {
+    PgSqlConnection(const ParameterMap& parameters,
+                    const isc::asiolink::IOServicePtr& io_service = isc::asiolink::IOServicePtr(),
+                    DbCallback callback = DbCallback())
+        : DatabaseConnection(parameters), io_service_(io_service),
+          callback_(callback) {
     }
 
     /// @brief Destructor
@@ -397,10 +401,8 @@ public:
     ///
     /// If the error is recoverable, the function will throw a DbOperationError.
     /// If the error is deemed unrecoverable, such as a loss of connectivity
-    /// with the server, the function will call invokeDbLostCallback(). If the
-    /// invocation returns false then either there is no callback registered
-    /// or the callback has elected not to attempt to reconnect, and a
-    /// DbUnrecoverableError is thrown.
+    /// with the server, the function will call startRecoverDbConnection() which
+    /// will start the connection recovery.
     ///
     /// If the invocation returns true, this indicates the calling layer will
     /// attempt recovery, and the function throws a DbOperationError to allow
@@ -413,6 +415,17 @@ public:
     void checkStatementError(const PgSqlResult& r,
                              PgSqlTaggedStatement& statement);
 
+    /// @brief The recover connection
+    ///
+    /// This function starts the recover process of the connection.
+    ///
+    /// @note The recover function must be run on the IO Service thread.
+    void startRecoverDbConnection() {
+        if (callback_) {
+            io_service_->post(std::bind(callback_, reconnectCtl()));
+        }
+    }
+
     /// @brief PgSql connection handle
     ///
     /// This field is public, because it is used heavily from PgSqlLeaseMgr
@@ -434,9 +447,14 @@ public:
         return (conn_);
     }
 
+    /// @brief IOService object, used for all ASIO operations.
+    isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief The callback used to recover the connection.
+    DbCallback callback_;
 };
 
-}; // end of isc::db namespace
-}; // end of isc namespace
+} // end of isc::db namespace
+} // end of isc namespace
 
 #endif // PGSQL_CONNECTION_H