From: Razvan Becheriu Date: Wed, 4 Nov 2020 23:23:11 +0000 (+0200) Subject: [#1375] added IOService to mysql and postgresql connections X-Git-Tag: Kea-1.9.3~104 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=126579d1d4e534af57211ad08bf100a92351e72e;p=thirdparty%2Fkea.git [#1375] added IOService to mysql and postgresql connections --- diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 94c15496c9..094a65db46 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -29,6 +29,7 @@ #include +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()), 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 diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 1f0a862030..81c6ce6365 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -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. /// diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 5a0c749f31..c687d1bfba 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -29,6 +29,7 @@ #include +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()), 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 diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index fe39e98e15..fabfe6bf76 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -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. /// diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc index cbc3ccca38..2140f2c2d1 100644 --- a/src/lib/asiolink/io_service.cc +++ b/src/lib/asiolink/io_service.cc @@ -22,13 +22,19 @@ namespace { // io_service::post(). class CallbackWrapper { public: + + /// \brief Constructor CallbackWrapper(const std::function& callback) : - callback_(callback) - {} + callback_(callback) {} + + /// \brief Function operator void operator()() { callback_(); } + private: + + /// \brief The callback function std::function 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& callback) { const CallbackWrapper wrapper(callback); io_service_.post(wrapper); diff --git a/src/lib/database/database_connection.cc b/src/lib/database/database_connection.cc index e61793160b..6c5d427dc2 100644 --- a/src/lib/database/database_connection.cc +++ b/src/lib/database/database_connection.cc @@ -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(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 diff --git a/src/lib/database/database_connection.h b/src/lib/database/database_connection.h index 990dc08c6d..ba313a680a 100644 --- a/src/lib/database/database_connection.h +++ b/src/lib/database/database_connection.h @@ -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 ReconnectCtlPtr; +/// @brief Defines a callback prototype for propagating events upward +typedef std::function 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 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 diff --git a/src/lib/database/tests/database_connection_unittest.cc b/src/lib/database/tests/database_connection_unittest.cc index 180b6dd0b6..7442454045 100644 --- a/src/lib/database/tests/database_connection_unittest.cc +++ b/src/lib/database/tests/database_connection_unittest.cc @@ -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. diff --git a/src/lib/dhcpsrv/cfg_db_access.cc b/src/lib/dhcpsrv/cfg_db_access.cc index dd843a97c4..c51be236a0 100644 --- a/src/lib/dhcpsrv/cfg_db_access.cc +++ b/src/lib/dhcpsrv/cfg_db_access.cc @@ -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 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. diff --git a/src/lib/dhcpsrv/cfg_db_access.h b/src/lib/dhcpsrv/cfg_db_access.h index 6d7bf01629..d4b96aa586 100644 --- a/src/lib/dhcpsrv/cfg_db_access.h +++ b/src/lib/dhcpsrv/cfg_db_access.h @@ -7,6 +7,7 @@ #ifndef CFG_DBACCESS_H #define CFG_DBACCESS_H +#include #include #include @@ -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: diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc index 38cd0e2107..6b595f7c82 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.cc +++ b/src/lib/dhcpsrv/cql_host_data_source.cc @@ -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)) { } diff --git a/src/lib/dhcpsrv/cql_host_data_source.h b/src/lib/dhcpsrv/cql_host_data_source.h index f63b9e2538..d15581e5cf 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.h +++ b/src/lib/dhcpsrv/cql_host_data_source.h @@ -18,6 +18,7 @@ #ifndef CQL_HOST_DATA_SOURCE_H #define CQL_HOST_DATA_SOURCE_H +#include #include #include @@ -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. /// diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc index 00a71b3093..7de0f787a7 100644 --- a/src/lib/dhcpsrv/host_data_source_factory.cc +++ b/src/lib/dhcpsrv/host_data_source_factory.cc @@ -34,6 +34,7 @@ #include #include +using namespace isc::asiolink; using namespace isc::db; using namespace std; @@ -44,7 +45,8 @@ map 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(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))); } }; diff --git a/src/lib/dhcpsrv/host_data_source_factory.h b/src/lib/dhcpsrv/host_data_source_factory.h index b39ad954e4..924f06b46b 100644 --- a/src/lib/dhcpsrv/host_data_source_factory.h +++ b/src/lib/dhcpsrv/host_data_source_factory.h @@ -7,6 +7,7 @@ #ifndef HOST_DATA_SOURCE_FACTORY_H #define HOST_DATA_SOURCE_FACTORY_H +#include #include #include #include @@ -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 Factory; + typedef std::function Factory; /// @brief Register a host data source factory /// diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index 78cc1121a4..b401b30476 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -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 diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 4123e3da7e..fced6d9f53 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -7,6 +7,7 @@ #ifndef HOST_MGR_H #define HOST_MGR_H +#include #include #include #include @@ -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). /// diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc index 30269c02c8..e41ae276e6 100644 --- a/src/lib/dhcpsrv/lease_mgr_factory.cc +++ b/src/lib/dhcpsrv/lease_mgr_factory.cc @@ -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"); diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h index 9938643868..792f3e5413 100644 --- a/src/lib/dhcpsrv/lease_mgr_factory.h +++ b/src/lib/dhcpsrv/lease_mgr_factory.h @@ -7,6 +7,7 @@ #ifndef LEASE_MGR_FACTORY_H #define LEASE_MGR_FACTORY_H +#include #include #include #include @@ -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& getLeaseMgrPtr(); - }; -}; // end of isc::dhcp namespace -}; // end of isc namespace +} // end of isc::dhcp namespace +} // end of isc namespace #endif // LEASE_MGR_FACTORY_H diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index a1c712e78a..04e8a1302f 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -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() { diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 0a0495d3d0..25d1a618e1 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -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. /// diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index 2e9a6a714b..86040d79fa 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -9,8 +9,12 @@ #include #include #include +#include +#include #include +#include #include +#include #include #include @@ -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 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(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(this)); + std::string timer_name = manager + "]DbReconnectTimer"; + + ctx->conn_.makeReconnectCtl(timer_name); + return (ctx); } diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h index 64af58ad6c..073e674174 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.h +++ b/src/lib/dhcpsrv/mysql_lease_mgr.h @@ -7,6 +7,7 @@ #ifndef MYSQL_LEASE_MGR_H #define MYSQL_LEASE_MGR_H +#include #include #include #include @@ -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 diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index d0f3c02c8e..95dbe4e469 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -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() { diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index 55b76afd89..f00d487a28 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -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 diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index 8d9ebcb634..6cd6a82a4b 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -9,9 +9,13 @@ #include #include #include +#include +#include #include #include +#include #include +#include #include #include @@ -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 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(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(this)); + std::string timer_name = manager + "]DbReconnectTimer"; + + ctx->conn_.makeReconnectCtl(timer_name); + return (ctx); } diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index e221854e9f..2c8d111849 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -7,6 +7,7 @@ #ifndef PGSQL_LEASE_MGR_H #define PGSQL_LEASE_MGR_H +#include #include #include #include @@ -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 diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc index 6f926e3296..93d3a16adf 100644 --- a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc +++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc @@ -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"); diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index 51c85b0fce..6fca3a4f1f 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -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 diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h index 1bf00d41fa..75fe229a45 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h @@ -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. diff --git a/src/lib/dhcpsrv/tests/host_cache_unittest.cc b/src/lib/dhcpsrv/tests/host_cache_unittest.cc index c6ad920098..937e705b47 100644 --- a/src/lib/dhcpsrv/tests/host_cache_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_cache_unittest.cc @@ -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); diff --git a/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc b/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc index dba626d866..38cbd5ecd8 100644 --- a/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -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()); } diff --git a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc index 7d1051feaf..973c08358e 100644 --- a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc @@ -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 diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc index fdfc6f73ff..84b911dd45 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc @@ -6,8 +6,10 @@ #include +#include #include +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())); } diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.h b/src/lib/dhcpsrv/testutils/memory_host_data_source.h index b2d06f48f2..9cff839ad6 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.h +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.h @@ -329,7 +329,8 @@ typedef boost::shared_ptr 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 diff --git a/src/lib/mysql/mysql_connection.h b/src/lib/mysql/mysql_connection.h index c9808b803d..ada6eb495f 100644 --- a/src/lib/mysql/mysql_connection.h +++ b/src/lib/mysql/mysql_connection.h @@ -7,6 +7,7 @@ #ifndef MYSQL_CONNECTION_H #define MYSQL_CONNECTION_H +#include #include #include #include @@ -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 diff --git a/src/lib/pgsql/pgsql_connection.cc b/src/lib/pgsql/pgsql_connection.cc index 3b32f3d39c..b3b48bdb2a 100644 --- a/src/lib/pgsql/pgsql_connection.cc +++ b/src/lib/pgsql/pgsql_connection.cc @@ -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 : "") - << "], reason: " << error_message); + << statement.name << ", status: " << s + << "sqlstate:[ " << (sqlstate ? sqlstate : "") + << " ], 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 diff --git a/src/lib/pgsql/pgsql_connection.h b/src/lib/pgsql/pgsql_connection.h index 3920bd81c3..9337727f58 100644 --- a/src/lib/pgsql/pgsql_connection.h +++ b/src/lib/pgsql/pgsql_connection.h @@ -6,6 +6,7 @@ #ifndef PGSQL_CONNECTION_H #define PGSQL_CONNECTION_H +#include #include #include @@ -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