// Copyright (C) 2022-2024 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include #include using namespace isc; using namespace isc::util; using namespace isc::asiolink; using namespace isc::db; using namespace isc::data; using namespace isc::dhcp; using namespace isc::dhcp::test; using namespace isc::process; using namespace isc::test; namespace ph = std::placeholders; GenericConfigBackendDbLostCallbackTest::GenericConfigBackendDbLostCallbackTest() : db_lost_callback_called_(0), db_recovered_callback_called_(0), db_failed_callback_called_(0), io_service_(boost::make_shared()) { } GenericConfigBackendDbLostCallbackTest::~GenericConfigBackendDbLostCallbackTest() { } void GenericConfigBackendDbLostCallbackTest::SetUp() { DatabaseConnection::db_lost_callback_ = 0; DatabaseConnection::db_recovered_callback_ = 0; DatabaseConnection::db_failed_callback_ = 0; isc::db::DatabaseConnection::setIOService(io_service_); setConfigBackendImplIOService(io_service_); isc::dhcp::TimerMgr::instance()->setIOService(io_service_); isc::dhcp::CfgMgr::instance().clear(); // Ensure we have the proper schema with no transient data. createSchema(); isc::dhcp::CfgMgr::instance().clear(); registerBackendType(); } void GenericConfigBackendDbLostCallbackTest::TearDown() { // If data wipe enabled, delete transient data otherwise destroy the schema destroySchema(); isc::dhcp::CfgMgr::instance().clear(); unregisterBackendType(); DatabaseConnection::db_lost_callback_ = 0; DatabaseConnection::db_recovered_callback_ = 0; DatabaseConnection::db_failed_callback_ = 0; isc::db::DatabaseConnection::setIOService(IOServicePtr()); setConfigBackendImplIOService(IOServicePtr()); isc::dhcp::TimerMgr::instance()->unregisterTimers(); isc::dhcp::CfgMgr::instance().clear(); } void GenericConfigBackendDbLostCallbackTest::testRetryOpenDbLostAndRecoveredCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = invalidConnectionString(); access += " retry-on-startup=true"; // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); std::shared_ptr dbr(new DbConnectionInitWithRetry()); // Connect to the CB backend. ASSERT_THROW(addBackend(access), DbOpenErrorWithRetry); dbr.reset(); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); access = validConnectionString(); CfgMgr::instance().clear(); config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); (const_cast(cfg))[0].setAccessString(access, true); io_service_->poll(); // Our lost and recovered connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); // Verify we can execute a query. We don't care about the answer. ASSERT_NO_THROW_LOG(servers = getAllServers()); } void GenericConfigBackendDbLostCallbackTest::testRetryOpenDbLostAndFailedCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = invalidConnectionString(); access += " retry-on-startup=true"; // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); std::shared_ptr dbr(new DbConnectionInitWithRetry()); // Connect to the CB backend. ASSERT_THROW(addBackend(access), DbOpenErrorWithRetry); dbr.reset(); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); io_service_->poll(); // Our lost and failed connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(1, db_failed_callback_called_); // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); } void GenericConfigBackendDbLostCallbackTest::testRetryOpenDbLostAndRecoveredAfterTimeoutCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = invalidConnectionString(); std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1 retry-on-startup=true"; access += extra; // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); std::shared_ptr dbr(new DbConnectionInitWithRetry()); // Connect to the CB backend. ASSERT_THROW(addBackend(access), DbOpenErrorWithRetry); dbr.reset(); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); access = validConnectionString(); access += extra; CfgMgr::instance().clear(); config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); sleep(1); io_service_->poll(); // Our lost and recovered connectivity callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); // Verify we can execute a query. We don't care about the answer. ASSERT_NO_THROW_LOG(servers = getAllServers()); sleep(1); io_service_->poll(); // No callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); // Verify we can execute a query. We don't care about the answer. ASSERT_NO_THROW_LOG(servers = getAllServers()); } void GenericConfigBackendDbLostCallbackTest::testRetryOpenDbLostAndFailedAfterTimeoutCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = invalidConnectionString(); std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1 retry-on-startup=true"; access += extra; // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); std::shared_ptr dbr(new DbConnectionInitWithRetry()); // Connect to the CB backend. ASSERT_THROW(addBackend(access), DbOpenErrorWithRetry); dbr.reset(); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); sleep(1); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); sleep(1); io_service_->poll(); // Our lost and failed connectivity callback should have been invoked. EXPECT_EQ(3, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(1, db_failed_callback_called_); // A query should fail with NoSuchDatabase. ASSERT_THROW(servers = getAllServers(), NoSuchDatabase); } void GenericConfigBackendDbLostCallbackTest::testNoCallbackOnOpenFailure() { DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = invalidConnectionString(); // Connect to the CB backend. ASSERT_THROW(addBackend(access), DbOpenError); io_service_->poll(); EXPECT_EQ(0, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); } void GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = validConnectionString(); ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); // Find the most recently opened socket. Our SQL client's socket should // be the next one. int last_open_socket = findLastSocketFd(); // Fill holes. FillFdHoles holes(last_open_socket); // Connect to the CB backend. ASSERT_NO_THROW(addBackend(access)); // Find the SQL client socket. int sql_socket = findLastSocketFd(); ASSERT_TRUE(sql_socket > last_open_socket); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; ASSERT_NO_THROW_LOG(servers = getAllServers()); // Now close the sql socket out from under backend client ASSERT_EQ(0, close(sql_socket)); // A query should fail with DbConnectionUnusable. ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable); io_service_->poll(); // Our lost and recovered connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); } void GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = validConnectionString(); ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); // Find the most recently opened socket. Our SQL client's socket should // be the next one. int last_open_socket = findLastSocketFd(); // Fill holes. FillFdHoles holes(last_open_socket); // Connect to the CB backend. ASSERT_NO_THROW(addBackend(access)); // Find the SQL client socket. int sql_socket = findLastSocketFd(); ASSERT_TRUE(sql_socket > last_open_socket); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; ASSERT_NO_THROW(servers = getAllServers()); access = invalidConnectionString(); CfgMgr::instance().clear(); // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); (const_cast(cfg))[0].setAccessString(access, true); // Now close the sql socket out from under backend client ASSERT_EQ(0, close(sql_socket)); // A query should fail with DbConnectionUnusable. ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable); io_service_->poll(); // Our lost and failed connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(1, db_failed_callback_called_); } void GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = validConnectionString(); std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1"; access += extra; ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); // Find the most recently opened socket. Our SQL client's socket should // be the next one. int last_open_socket = findLastSocketFd(); // Fill holes. FillFdHoles holes(last_open_socket); // Connect to the CB backend. ASSERT_NO_THROW(addBackend(access)); // Find the SQL client socket. int sql_socket = findLastSocketFd(); ASSERT_TRUE(sql_socket > last_open_socket); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; ASSERT_NO_THROW(servers = getAllServers()); access = invalidConnectionString(); access += extra; CfgMgr::instance().clear(); // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); (const_cast(cfg))[0].setAccessString(access, true); // Now close the sql socket out from under backend client ASSERT_EQ(0, close(sql_socket)); // A query should fail with DbConnectionUnusable. ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); access = validConnectionString(); access += extra; CfgMgr::instance().clear(); config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); sleep(1); io_service_->poll(); // Our lost and recovered connectivity callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); sleep(1); io_service_->poll(); // No callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(1, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); } void GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() { // Set the connectivity lost callback. DatabaseConnection::db_lost_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1); // Set the connectivity recovered callback. DatabaseConnection::db_recovered_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1); // Set the connectivity failed callback. DatabaseConnection::db_failed_callback_ = std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1); std::string access = validConnectionString(); std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1"; access += extra; ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); // Find the most recently opened socket. Our SQL client's socket should // be the next one. int last_open_socket = findLastSocketFd(); // Fill holes. FillFdHoles holes(last_open_socket); // Connect to the CB backend. ASSERT_NO_THROW(addBackend(access)); // Find the SQL client socket. int sql_socket = findLastSocketFd(); ASSERT_TRUE(sql_socket > last_open_socket); // Verify we can execute a query. We don't care about the answer. ServerCollection servers; ASSERT_NO_THROW(servers = getAllServers()); access = invalidConnectionString(); access += extra; CfgMgr::instance().clear(); // by adding an invalid access will cause the manager factory to throw // resulting in failure to recreate the manager config_ctl_info.reset(new ConfigControlInfo()); config_ctl_info->addConfigDatabase(access); CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info); const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases(); (const_cast(cfg))[0].setAccessString(access, true); // Now close the sql socket out from under backend client ASSERT_EQ(0, close(sql_socket)); // A query should fail with DbConnectionUnusable. ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(1, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); sleep(1); io_service_->poll(); // Our lost connectivity callback should have been invoked. EXPECT_EQ(2, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(0, db_failed_callback_called_); sleep(1); io_service_->poll(); // Our lost and failed connectivity callback should have been invoked. EXPECT_EQ(3, db_lost_callback_called_); EXPECT_EQ(0, db_recovered_callback_called_); EXPECT_EQ(1, db_failed_callback_called_); }