diff options
8 files changed, 1364 insertions, 1056 deletions
diff --git a/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc index 01c3027b8b..966016eb64 100644 --- a/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc +++ b/src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc @@ -5,25 +5,15 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <config.h> -#include <pgsql_cb_dhcp4.h> -#include <pgsql_cb_impl.h> -#include <database/database_connection.h> #include <database/db_exceptions.h> #include <database/server.h> -#include <database/testutils/schema.h> -#include <dhcp/dhcp6.h> -#include <dhcp/libdhcp++.h> -#include <dhcp/option4_addrlst.h> -#include <dhcp/option_int.h> -#include <dhcp/option_space.h> -#include <dhcp/option_string.h> #include <dhcpsrv/cfgmgr.h> -#include <dhcpsrv/client_class_def.h> #include <dhcpsrv/config_backend_dhcp4_mgr.h> -#include <dhcpsrv/pool.h> -#include <dhcpsrv/subnet.h> #include <dhcpsrv/testutils/pgsql_generic_backend_unittest.h> +#include <dhcpsrv/testutils/generic_cb_dhcp4_unittest.h> +#include <dhcpsrv/testutils/generic_cb_recovery_unittest.h> #include <dhcpsrv/testutils/test_utils.h> +#include <pgsql_cb_dhcp4.h> #include <pgsql/testutils/pgsql_schema.h> #include <testutils/multi_threading_utils.h> #include <testutils/gtest_utils.h> @@ -66,49 +56,32 @@ public: }; /// @brief Test fixture class for @c PgSqlConfigBackendDHCPv4. -class PgSqlConfigBackendDHCPv4Test : public PgSqlGenericBackendTest { +class PgSqlConfigBackendDHCPv4Test : public GenericConfigBackendDHCPv4Test { public: - /// @brief Constructor. - PgSqlConfigBackendDHCPv4Test() - : test_subnets_(), test_networks_(), test_option_defs_(), - test_options_(), test_client_classes_(), test_servers_(), timestamps_(), - cbptr_(), audit_entries_() { - // Ensure we have the proper schema with no transient data. - createPgSQLSchema(); + PgSqlConfigBackendDHCPv4Test(){}; - try { - // Create PgSQL connection and use it to start the backend. - DatabaseConnection::ParameterMap params = - DatabaseConnection::parse(validPgSQLConnectionString()); - cbptr_.reset(new TestPgSqlConfigBackendDHCPv4(params)); - - } catch (...) { - std::cerr << "*** ERROR: unable to open database. The test\n" - "*** environment is broken and must be fixed before\n" - "*** the PgSQL tests will run correctly.\n" - "*** The reason for the problem is described in the\n" - "*** accompanying exception output.\n"; - throw; - } + /// @brief Destructor. + virtual ~PgSqlConfigBackendDHCPv4Test(){}; - // Create test data. - initTestServers(); - initTestOptions(); - initTestSubnets(); - initTestSharedNetworks(); - initTestOptionDefs(); - initTestClientClasses(); - initTimestamps(); + virtual void createSchema() { + createPgSQLSchema(); } - /// @brief Destructor. - virtual ~PgSqlConfigBackendDHCPv4Test() { - cbptr_.reset(); - // If data wipe enabled, delete transient data otherwise destroy the schema. + virtual void destroySchema() { destroyPgSQLSchema(); } + std::string validConnectionString() { + return (validPgSQLConnectionString()); + } + + ConfigBackendDHCPv4Ptr backendFactory(db::DatabaseConnection::ParameterMap& + params) { + + return (ConfigBackendDHCPv4Ptr(new TestPgSqlConfigBackendDHCPv4(params))); + } + /// @brief Counts rows in a selected table in PgSQL database. /// /// This method can be used to verify that some configuration elements were @@ -132,1065 +105,98 @@ public: return (PgSqlGenericBackendTest::countRows(conn, table)); } - - /// @brief Creates several servers used in tests. - void initTestServers() { - test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1")); - test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis")); - test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2")); - test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3")); - } - - /// @brief Creates several subnets used in tests. - void initTestSubnets() { - // First subnet includes all parameters. - std::string interface_id_text = "whale"; - OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end()); - ElementPtr user_context = Element::createMap(); - user_context->set("foo", Element::create("bar")); - - Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40, 60, 1024)); - subnet->get4o6().setIface4o6("eth0"); - subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, - interface_id))); - subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64); - subnet->setFilename("/tmp/filename"); - subnet->allowClientClass("home"); - subnet->setIface("eth1"); - subnet->setMatchClientId(false); - subnet->setSiaddr(IOAddress("10.1.2.3")); - subnet->setT2(323212); - subnet->addRelayAddress(IOAddress("10.2.3.4")); - subnet->addRelayAddress(IOAddress("10.5.6.7")); - subnet->setT1(1234); - subnet->requireClientClass("required-class1"); - subnet->requireClientClass("required-class2"); - subnet->setReservationsGlobal(false); - subnet->setReservationsInSubnet(false); - subnet->setSname("server-hostname"); - subnet->setContext(user_context); - subnet->setValid(555555); - subnet->setAuthoritative(true); - subnet->setCalculateTeeTimes(true); - subnet->setT1Percent(0.345); - subnet->setT2Percent(0.444); - subnet->setDdnsSendUpdates(false); - - Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"))); - subnet->addPool(pool1); - - Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"), IOAddress("192.0.2.60"))); - subnet->addPool(pool2); - - // Add several options to the subnet. - subnet->getCfgOption()->add(test_options_[0]->option_, - test_options_[0]->persistent_, - test_options_[0]->space_name_); - - subnet->getCfgOption()->add(test_options_[1]->option_, - test_options_[1]->persistent_, - test_options_[1]->space_name_); - - subnet->getCfgOption()->add(test_options_[2]->option_, - test_options_[2]->persistent_, - test_options_[2]->space_name_); - - test_subnets_.push_back(subnet); - - // Adding another subnet with the same subnet id to test - // cases that this second instance can override existing - // subnet instance. - subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 20, 30, 40, 1024)); - - pool1.reset(new Pool4(IOAddress("10.0.0.10"), IOAddress("10.0.0.20"))); - subnet->addPool(pool1); - - pool1->getCfgOption()->add(test_options_[3]->option_, - test_options_[3]->persistent_, - test_options_[3]->space_name_); - - pool1->getCfgOption()->add(test_options_[4]->option_, - test_options_[4]->persistent_, - test_options_[4]->space_name_); - - pool2.reset(new Pool4(IOAddress("10.0.0.50"), IOAddress("10.0.0.60"))); - - pool2->allowClientClass("work"); - pool2->requireClientClass("required-class3"); - pool2->requireClientClass("required-class4"); - user_context = Element::createMap(); - user_context->set("bar", Element::create("foo")); - pool2->setContext(user_context); - - subnet->addPool(pool2); - - test_subnets_.push_back(subnet); - - subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048)); - Triplet<uint32_t> null_timer; - subnet->setT1(null_timer); - subnet->setT2(null_timer); - subnet->setValid(null_timer); - subnet->setDdnsSendUpdates(true); - subnet->setDdnsOverrideNoUpdate(true); - subnet->setDdnsOverrideClientUpdate(false); - subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); - subnet->setDdnsGeneratedPrefix("myhost"); - subnet->setDdnsQualifyingSuffix("example.org"); - - subnet->getCfgOption()->add(test_options_[0]->option_, - test_options_[0]->persistent_, - test_options_[0]->space_name_); - - test_subnets_.push_back(subnet); - - // Add a subnet with all defaults. - subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24, Triplet<uint32_t>(), - Triplet<uint32_t>(), Triplet<uint32_t>(), 4096)); - test_subnets_.push_back(subnet); - } - - /// @brief Creates several subnets used in tests. - void initTestSharedNetworks() { - ElementPtr user_context = Element::createMap(); - user_context->set("foo", Element::create("bar")); - - SharedNetwork4Ptr shared_network(new SharedNetwork4("level1")); - shared_network->allowClientClass("foo"); - shared_network->setIface("eth1"); - shared_network->setMatchClientId(false); - shared_network->setT2(323212); - shared_network->addRelayAddress(IOAddress("10.2.3.4")); - shared_network->addRelayAddress(IOAddress("10.5.6.7")); - shared_network->setT1(1234); - shared_network->requireClientClass("required-class1"); - shared_network->requireClientClass("required-class2"); - shared_network->setReservationsGlobal(false); - shared_network->setReservationsInSubnet(false); - shared_network->setContext(user_context); - shared_network->setValid(5555); - shared_network->setCalculateTeeTimes(true); - shared_network->setT1Percent(0.345); - shared_network->setT2Percent(0.444); - shared_network->setSiaddr(IOAddress("192.0.1.2")); - shared_network->setSname("frog"); - shared_network->setFilename("/dev/null"); - shared_network->setAuthoritative(true); - shared_network->setDdnsSendUpdates(false); - - // Add several options to the shared network. - shared_network->getCfgOption()->add(test_options_[2]->option_, - test_options_[2]->persistent_, - test_options_[2]->space_name_); - - shared_network->getCfgOption()->add(test_options_[3]->option_, - test_options_[3]->persistent_, - test_options_[3]->space_name_); - - shared_network->getCfgOption()->add(test_options_[4]->option_, - test_options_[4]->persistent_, - test_options_[4]->space_name_); - - test_networks_.push_back(shared_network); - - // Adding another shared network called "level1" to test - // cases that this second instance can override existing - // "level1" instance. - shared_network.reset(new SharedNetwork4("level1")); - test_networks_.push_back(shared_network); - - // Add more shared networks. - shared_network.reset(new SharedNetwork4("level2")); - Triplet<uint32_t> null_timer; - shared_network->setT1(null_timer); - shared_network->setT2(null_timer); - shared_network->setValid(null_timer); - shared_network->setDdnsSendUpdates(true); - shared_network->setDdnsOverrideNoUpdate(true); - shared_network->setDdnsOverrideClientUpdate(false); - shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); - shared_network->setDdnsGeneratedPrefix("myhost"); - shared_network->setDdnsQualifyingSuffix("example.org"); - - shared_network->getCfgOption()->add(test_options_[0]->option_, - test_options_[0]->persistent_, - test_options_[0]->space_name_); - test_networks_.push_back(shared_network); - - shared_network.reset(new SharedNetwork4("level3")); - test_networks_.push_back(shared_network); - } - - /// @brief Creates several option definitions used in tests. - void initTestOptionDefs() { - ElementPtr user_context = Element::createMap(); - user_context->set("foo", Element::create("bar")); - - OptionDefinitionPtr option_def(new OptionDefinition("foo", 234, - DHCP4_OPTION_SPACE, - "string", - "espace")); - test_option_defs_.push_back(option_def); - - option_def.reset(new OptionDefinition("bar", 234, DHCP4_OPTION_SPACE, - "uint32", true)); - test_option_defs_.push_back(option_def); - - option_def.reset(new OptionDefinition("fish", 235, DHCP4_OPTION_SPACE, - "record", true)); - option_def->addRecordField("uint32"); - option_def->addRecordField("string"); - test_option_defs_.push_back(option_def); - - option_def.reset(new OptionDefinition("whale", 236, "xyz", "string")); - test_option_defs_.push_back(option_def); - - option_def.reset(new OptionDefinition("foobar", 234, DHCP4_OPTION_SPACE, - "uint64", true)); - test_option_defs_.push_back(option_def); - } - - /// @brief Creates several DHCP options used in tests. - void initTestOptions() { - ElementPtr user_context = Element::createMap(); - user_context->set("foo", Element::create("bar")); - - OptionDefSpaceContainer defs; - - OptionDescriptor desc = - createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, - true, false, "my-boot-file"); - desc.space_name_ = DHCP4_OPTION_SPACE; - desc.setContext(user_context); - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL, - false, true, 64); - desc.space_name_ = DHCP4_OPTION_SPACE; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131), - desc.space_name_ = "vendor-encapsulated-options"; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createAddressOption<Option4AddrLst>(254, true, true, - "192.0.2.3"); - desc.space_name_ = DHCP4_OPTION_SPACE; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createEmptyOption(Option::V4, 1, true); - desc.space_name_ = "isc"; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createAddressOption<Option4AddrLst>(2, false, true, "10.0.0.5", - "10.0.0.3", "10.0.3.4"); - desc.space_name_ = "isc"; - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, - true, false, "my-boot-file-2"); - desc.space_name_ = DHCP4_OPTION_SPACE; - desc.setContext(user_context); - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, - true, false, "my-boot-file-3"); - desc.space_name_ = DHCP4_OPTION_SPACE; - desc.setContext(user_context); - test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); - - // Add definitions for DHCPv4 non-standard options in case we need to - // compare subnets, networks and pools in JSON format. In that case, - // the @c toElement functions require option definitions to generate the - // proper output. - defs.addItem(OptionDefinitionPtr(new OptionDefinition( - "vendor-encapsulated-1", 1, - "vendor-encapsulated-options", "uint32"))); - defs.addItem(OptionDefinitionPtr(new OptionDefinition( - "option-254", 254, DHCP4_OPTION_SPACE, - "ipv4-address", true))); - defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty"))); - defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", "ipv4-address", true))); - - // Register option definitions. - LibDHCP::setRuntimeOptionDefs(defs); - } - - /// @brief Creates several client classes used in tests. - void initTestClientClasses() { - ExpressionPtr match_expr = boost::make_shared<Expression>(); - CfgOptionPtr cfg_option = boost::make_shared<CfgOption>(); - auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option); - class1->setRequired(true); - class1->setNextServer(IOAddress("1.2.3.4")); - class1->setSname("cool"); - class1->setFilename("epc.cfg"); - class1->setValid(Triplet<uint32_t>(30, 60, 90)); - test_client_classes_.push_back(class1); - - auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option); - class2->setTest("member('foo')"); - test_client_classes_.push_back(class2); - - auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option); - class3->setTest("member('foo') and member('bar')"); - test_client_classes_.push_back(class3); - } - - /// @brief Initialize posix time values used in tests. - void initTimestamps() { - // Current time minus 1 hour to make sure it is in the past. - timestamps_["today"] = boost::posix_time::second_clock::local_time() - - boost::posix_time::hours(1); - // One second after today. - timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1); - // Yesterday. - timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24); - // One second after yesterday. - timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1); - // Two days ago. - timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48); - // Tomorrow. - timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24); - // One second after tomorrow. - timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1); - } - - /// @brief Logs audit entries in the @c audit_entries_ member. - /// - /// This function is called in case of an error. - /// - /// @param server_tag Server tag for which the audit entries should be logged. - std::string logExistingAuditEntries(const std::string& server_tag) { - std::ostringstream s; - - auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>(); - - for (auto audit_entry_it = mod_time_idx.begin(); - audit_entry_it != mod_time_idx.end(); - ++audit_entry_it) { - auto audit_entry = *audit_entry_it; - s << audit_entry->getObjectType() << ", " - << audit_entry->getObjectId() << ", " - << static_cast<int>(audit_entry->getModificationType()) << ", " - << audit_entry->getModificationTime() << ", " - << audit_entry->getRevisionId() << ", " - << audit_entry->getLogMessage() - << std::endl; - } - - return (s.str()); - } - - /// @brief Tests that the new audit entry is added. - /// - /// This method retrieves a collection of the existing audit entries and - /// checks that the new one has been added at the end of this collection. - /// It then verifies the values of the audit entry against the values - /// specified by the caller. - /// - /// @param exp_object_type Expected object type. - /// @param exp_modification_type Expected modification type. - /// @param exp_log_message Expected log message. - /// @param server_selector Server selector to be used for next query. - /// @param new_entries_num Number of the new entries expected to be inserted. - /// @param max_tested_entries Maximum number of entries tested. - void testNewAuditEntry(const std::string& exp_object_type, - const AuditEntry::ModificationType& exp_modification_type, - const std::string& exp_log_message, - const ServerSelector& server_selector = ServerSelector::ALL(), - const size_t new_entries_num = 1, - const size_t max_tested_entries = 65535) { - - // Get the server tag for which the entries are fetched. - std::string tag; - if (server_selector.getType() == ServerSelector::Type::ALL) { - // Server tag is 'all'. - tag = "all"; - - } else { - auto tags = server_selector.getTags(); - // This test is not meant to handle multiple server tags all at once. - if (tags.size() > 1) { - ADD_FAILURE() << "Test error: do not use multiple server tags"; - - } else if (tags.size() == 1) { - // Get the server tag for which we run the current test. - tag = tags.begin()->get(); - } - } - - auto audit_entries_size_save = audit_entries_[tag].size(); - - // Audit entries for different server tags are stored in separate - // containers. - ASSERT_NO_THROW_LOG(audit_entries_[tag] = cbptr_->getRecentAuditEntries(server_selector, - timestamps_["two days ago"], 0)); - ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size()) - << logExistingAuditEntries(tag); - - auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>(); - - // Iterate over specified number of entries starting from the most recent - // one and check they have correct values. - for (auto audit_entry_it = mod_time_idx.rbegin(); - ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) && - (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries)); - ++audit_entry_it) { - auto audit_entry = *audit_entry_it; - EXPECT_EQ(exp_object_type, audit_entry->getObjectType()) - << logExistingAuditEntries(tag); - EXPECT_EQ(exp_modification_type, audit_entry->getModificationType()) - << logExistingAuditEntries(tag); - EXPECT_EQ(exp_log_message, audit_entry->getLogMessage()) - << logExistingAuditEntries(tag); - } - } - - /// @brief Holds pointers to subnets used in tests. - std::vector<Subnet4Ptr> test_subnets_; - - /// @brief Holds pointers to shared networks used in tests. - std::vector<SharedNetwork4Ptr> test_networks_; - - /// @brief Holds pointers to option definitions used in tests. - std::vector<OptionDefinitionPtr> test_option_defs_; - - /// @brief Holds pointers to options used in tests. - std::vector<OptionDescriptorPtr> test_options_; - - /// @brief Holds pointers to classes used in tests. - std::vector<ClientClassDefPtr> test_client_classes_; - - /// @brief Holds pointers to the servers used in tests. - std::vector<ServerPtr> test_servers_; - - /// @brief Holds timestamp values used in tests. - std::map<std::string, boost::posix_time::ptime> timestamps_; - - /// @brief Holds pointer to the backend. - boost::shared_ptr<ConfigBackendDHCPv4> cbptr_; - - /// @brief Holds the most recent audit entries. - std::map<std::string, AuditEntryCollection> audit_entries_; }; // This test verifies that the expected backend type is returned. TEST_F(PgSqlConfigBackendDHCPv4Test, getType) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params))); - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - EXPECT_EQ("postgresql", cbptr_->getType()); + getTypeTest("postgresql"); } // This test verifies that by default localhost is returned as PgSQL connection // host. TEST_F(PgSqlConfigBackendDHCPv4Test, getHost) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params))); - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - EXPECT_EQ("localhost", cbptr_->getHost()); + getHostTest(); } // This test verifies that by default port of 0 is returned as PgSQL connection // port. TEST_F(PgSqlConfigBackendDHCPv4Test, getPort) { - DatabaseConnection::ParameterMap params; - params["name"] = "keatest"; - params["password"] = "keatest"; - params["user"] = "keatest"; - ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); - ASSERT_NO_THROW(cbptr_.reset(new PgSqlConfigBackendDHCPv4(params))); - EXPECT_EQ(0, cbptr_->getPort()); + getPortTest(); } // This test verifies that the server can be added, updated and deleted. TEST_F(PgSqlConfigBackendDHCPv4Test, createUpdateDeleteServer) { - // Explicitly set modification time to make sure that the time - // returned from the database is correct. - test_servers_[0]->setModificationTime(timestamps_["yesterday"]); - test_servers_[1]->setModificationTime(timestamps_["today"]); - - // Insert the server1 into the database. - ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0])); - - { - SCOPED_TRACE("CREATE audit entry for server"); - testNewAuditEntry("dhcp4_server", - AuditEntry::ModificationType::CREATE, - "server set"); - } - - // It should not be possible to create a duplicate of the logical - // server 'all'. - auto all_server = Server::create(ServerTag("all"), "this is logical server all"); - EXPECT_THROW(cbptr_->createUpdateServer4(all_server), isc::InvalidOperation); - - ServerPtr returned_server; - - // An attempt to fetch the server that hasn't been inserted should return - // a null pointer. - ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server2"))); - EXPECT_FALSE(returned_server); - - // Try to fetch the server which we expect to exist. - ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); - ASSERT_TRUE(returned_server); - EXPECT_EQ("server1", returned_server->getServerTag().get()); - EXPECT_EQ("this is server 1", returned_server->getDescription()); - EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime()); - - // This call is expected to update the existing server. - ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1])); - - { - SCOPED_TRACE("UPDATE audit entry for server"); - testNewAuditEntry("dhcp4_server", - AuditEntry::ModificationType::UPDATE, - "server set"); - } - - // Verify that the server has been updated. - ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); - ASSERT_TRUE(returned_server); - EXPECT_EQ("server1", returned_server->getServerTag().get()); - EXPECT_EQ("this is server 1 bis", returned_server->getDescription()); - EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime()); - - uint64_t servers_deleted = 0; - - // Try to delete non-existing server. - ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server2"))); - EXPECT_EQ(0, servers_deleted); - - // Make sure that the server1 wasn't deleted. - ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); - EXPECT_TRUE(returned_server); - - // Deleting logical server 'all' is not allowed. - EXPECT_THROW(cbptr_->deleteServer4(ServerTag()), isc::InvalidOperation); - - // Delete the existing server. - ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server1"))); - EXPECT_EQ(1, servers_deleted); - - { - SCOPED_TRACE("DELETE audit entry for server"); - testNewAuditEntry("dhcp4_server", - AuditEntry::ModificationType::DELETE, - "deleting a server"); - } - - // Make sure that the server is gone. - ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); - EXPECT_FALSE(returned_server); + createUpdateDeleteServerTest(); } -// This test verifies that it is possible to retrieve all servers from the -// database and then delete all of them. -TEST_F(PgSqlConfigBackendDHCPv4Test, getAndDeleteAllServers) { - for (auto i = 1; i < test_servers_.size(); ++i) { - ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[i])); - } - - ServerCollection servers; - ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4()); - ASSERT_EQ(test_servers_.size() - 1, servers.size()); - - // All servers should have been returned. - EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1"))); - EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2"))); - EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3"))); - - // The logical server all should not be returned. We merely return the - // user configured servers. - EXPECT_FALSE(ServerFetcher::get(servers, ServerTag())); - - // Delete all servers and make sure they are gone. - uint64_t deleted_servers = 0; - ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers4()); - - ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4()); - EXPECT_TRUE(servers.empty()); - - // All servers should be gone. - EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1"))); - EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2"))); - EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3"))); - - // The number of deleted server should be equal to the number of - // inserted servers. The logical 'all' server should be excluded. - EXPECT_EQ(test_servers_.size() - 1, deleted_servers); - - EXPECT_EQ(1, countRows("dhcp4_server")); -} /// @brief Test fixture for verifying database connection loss-recovery /// behavior. -class PgSqlConfigBackendDHCPv4DbLostCallbackTest : public ::testing::Test { +class PgSqlConfigBackendDHCPv4DbLostCallbackTest : public GenericConfigBackendDbLostCallbackTest { public: /// @brief Constructor - PgSqlConfigBackendDHCPv4DbLostCallbackTest() - : db_lost_callback_called_(0), db_recovered_callback_called_(0), - db_failed_callback_called_(0), - io_service_(boost::make_shared<isc::asiolink::IOService>()) { - isc::db::DatabaseConnection::db_lost_callback_ = 0; - isc::db::DatabaseConnection::db_recovered_callback_ = 0; - isc::db::DatabaseConnection::db_failed_callback_ = 0; - isc::dhcp::PgSqlConfigBackendImpl::setIOService(io_service_); - isc::dhcp::TimerMgr::instance()->setIOService(io_service_); - isc::dhcp::CfgMgr::instance().clear(); - } + PgSqlConfigBackendDHCPv4DbLostCallbackTest() {}; /// @brief Destructor - virtual ~PgSqlConfigBackendDHCPv4DbLostCallbackTest() { - isc::db::DatabaseConnection::db_lost_callback_ = 0; - isc::db::DatabaseConnection::db_recovered_callback_ = 0; - isc::db::DatabaseConnection::db_failed_callback_ = 0; - isc::dhcp::PgSqlConfigBackendImpl::setIOService(isc::asiolink::IOServicePtr()); - isc::dhcp::TimerMgr::instance()->unregisterTimers(); - isc::dhcp::CfgMgr::instance().clear(); - } + virtual ~PgSqlConfigBackendDHCPv4DbLostCallbackTest() {}; - /// @brief Prepares the class for a test. - /// - /// Invoked by gtest prior test entry, we create the - /// appropriate schema and create a basic DB manager to - /// wipe out any prior instance - virtual void SetUp() { - // Ensure we have the proper schema with no transient data. + /// @brief Creates the PostgreSQL CB schema. + virtual void createSchema() { createPgSQLSchema(); - isc::dhcp::CfgMgr::instance().clear(); - isc::dhcp::PgSqlConfigBackendDHCPv4::registerBackendType(); } - /// @brief Pre-text exit clean up - /// - /// Invoked by gtest upon test exit, we destroy the schema - /// we created. - virtual void TearDown() { - // If data wipe enabled, delete transient data otherwise destroy the schema + /// @brief Destroys the PostgreSQL CB schema. + virtual void destroySchema() { destroyPgSQLSchema(); - isc::dhcp::CfgMgr::instance().clear(); - isc::dhcp::PgSqlConfigBackendDHCPv4::unregisterBackendType(); } - /// @brief Method which returns the back end specific connection + /// @brief Method which returns a valid, back end specific connection /// string - virtual std::string validConnectString() { + virtual std::string validConnectionString() { return (validPgSQLConnectionString()); } - /// @brief Method which returns invalid back end specific connection - /// string - virtual std::string invalidConnectString() { + /// @brief Method which returns an invalid,back end specific connection + /// string. + virtual std::string invalidConnectionString() { return (connectionString(PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)); } - /// @brief Verifies open failures do NOT invoke db lost callback - /// - /// The db lost callback should only be invoked after successfully - /// opening the DB and then subsequently losing it. Failing to - /// open should be handled directly by the application layer. - void testNoCallbackOnOpenFailure(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbRecoveredCallback was invoked - void testDbLostAndRecoveredCallback(); - - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbFailedCallback was invoked - void testDbLostAndFailedCallback(); + /// @brief Registers PostgreSQL as a CB backend type. + virtual void registerBackendType() { + isc::dhcp::PgSqlConfigBackendDHCPv4::registerBackendType(); + } - /// @brief Verifies the CB manager's behavior if DB connection is lost - /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbRecoveredCallback was invoked after two reconnect - /// attempts (once failing and second triggered by timer) - void testDbLostAndRecoveredAfterTimeoutCallback(); + /// @brief Unregisters PostgreSQL as a CB backend type. + virtual void unregisterBackendType() { + isc::dhcp::PgSqlConfigBackendDHCPv4::unregisterBackendType(); + } - /// @brief Verifies the CB manager's behavior if DB connection is lost + /// @brief Sets the IOService instance in the CB implementation object. /// - /// This function creates a CB manager with a back end that supports - /// connectivity lost callback. It verifies connectivity by issuing a known - /// valid query. Next it simulates connectivity lost by identifying and - /// closing the socket connection to the CB backend. It then reissues the - /// query and verifies that: - /// -# The Query throws DbOperationError (rather than exiting) - /// -# The registered DbLostCallback was invoked - /// -# The registered DbFailedCallback was invoked after two reconnect - /// attempts (once failing and second triggered by timer) - void testDbLostAndFailedAfterTimeoutCallback(); - - /// @brief Callback function registered with the CB manager - bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_lost_callback_called_); + /// @param io_service pointer to the IOService instance to use. It may be + /// an empty pointer. + virtual void setConfigBackendImplIOService(isc::asiolink::IOServicePtr io_service) { + isc::dhcp::PgSqlConfigBackendImpl::setIOService(io_service); } - /// @brief Flag used to detect calls to db_lost_callback function - uint32_t db_lost_callback_called_; - - /// @brief Callback function registered with the CB manager - bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_recovered_callback_called_); + /// @brief Attempts to add a backend instance to the CB manager. + /// + /// @param access Connection access string containing the database + /// connetion parameters. + virtual void addBackend(const std::string& access) { + ConfigBackendDHCPv4Mgr::instance().addBackend(access); } - /// @brief Flag used to detect calls to db_recovered_callback function - uint32_t db_recovered_callback_called_; - - /// @brief Callback function registered with the CB manager - bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) { - return (++db_failed_callback_called_); + /// @brief Fetches a collection of all the servers currently in + /// the CB database. This function is used to check the operability + /// of the CB backend. + ServerCollection getAllServers() { + return (ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector())); } - - /// @brief Flag used to detect calls to db_failed_callback function - uint32_t db_failed_callback_called_; - - /// The IOService object, used for all ASIO operations. - isc::asiolink::IOServicePtr io_service_; }; -void -PgSqlConfigBackendDHCPv4DbLostCallbackTest::testNoCallbackOnOpenFailure() { - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = invalidConnectString(); - - // Connect to the CB backend. - ASSERT_THROW(ConfigBackendDHCPv4Mgr::instance().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 -PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndRecoveredCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - - 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(ConfigBackendDHCPv4Mgr::instance().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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector())); - - // 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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()), - 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 -PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndFailedCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - 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(ConfigBackendDHCPv4Mgr::instance().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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector())); - - access = invalidConnectString(); - 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<ConfigDbInfoList&>(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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()), - 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 -PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - 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(ConfigBackendDHCPv4Mgr::instance().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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector())); - - access = invalidConnectString(); - 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<ConfigDbInfoList&>(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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()), - 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 = validConnectString(); - 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 -PgSqlConfigBackendDHCPv4DbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() { - // Set the connectivity lost callback. - isc::db::DatabaseConnection::db_lost_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_lost_callback, this, ph::_1); - - // Set the connectivity recovered callback. - isc::db::DatabaseConnection::db_recovered_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_recovered_callback, this, ph::_1); - - // Set the connectivity failed callback. - isc::db::DatabaseConnection::db_failed_callback_ = - std::bind(&PgSqlConfigBackendDHCPv4DbLostCallbackTest::db_failed_callback, this, ph::_1); - - std::string access = validConnectString(); - 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(ConfigBackendDHCPv4Mgr::instance().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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector())); - - access = invalidConnectString(); - 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<ConfigDbInfoList&>(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 = ConfigBackendDHCPv4Mgr::instance().getPool()->getAllServers4(BackendSelector()), - 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_); -} /// @brief Verifies that db lost callback is not invoked on an open failure TEST_F(PgSqlConfigBackendDHCPv4DbLostCallbackTest, testNoCallbackOnOpenFailure) { diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index 063b249209..9c7e17fb3d 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -19,6 +19,8 @@ libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source. libdhcpsrvtest_la_SOURCES += test_utils.cc test_utils.h libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h libdhcpsrvtest_la_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h +libdhcpsrvtest_la_SOURCES += generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc +libdhcpsrvtest_la_SOURCES += generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc libdhcpsrvtest_la_SOURCES += lease_file_io.cc lease_file_io.h libdhcpsrvtest_la_SOURCES += test_config_backend.h libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp4.cc test_config_backend_dhcp4.h diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc new file mode 100644 index 0000000000..bb15977638 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc @@ -0,0 +1,589 @@ +// Copyright (C) 2022 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 <config.h> +#include <database/database_connection.h> +#include <database/db_exceptions.h> +#include <database/server.h> +#include <database/testutils/schema.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option_int.h> +#include <dhcp/option_space.h> +#include <dhcp/option_string.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/client_class_def.h> +#include <dhcpsrv/config_backend_dhcp4_mgr.h> +#include <dhcpsrv/pool.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/testutils/generic_cb_dhcp4_unittest.h> +#include <dhcpsrv/testutils/test_utils.h> +#include <testutils/gtest_utils.h> + +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +using namespace isc; +using namespace isc::util; +using namespace isc::asiolink; +using namespace isc::db; +using namespace isc::db::test; +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; + +void +GenericConfigBackendDHCPv4Test::SetUp() { + // Ensure we have the proper schema with no transient data. + createSchema(); + + try { + // Create a connection parameter map and use it to start the backend. + DatabaseConnection::ParameterMap params = + DatabaseConnection::parse(validConnectionString()); + cbptr_ = backendFactory(params); + } catch (...) { + std::cerr << "*** ERROR: unable to open database. The test\n" + "*** environment is broken and must be fixed before\n" + "*** the tests will run correctly.\n" + "*** The reason for the problem is described in the\n" + "*** accompanying exception output.\n"; + throw; + } + + // Create test data. + initTestServers(); + initTestOptions(); + initTestSubnets(); + initTestSharedNetworks(); + initTestOptionDefs(); + initTestClientClasses(); + initTimestamps(); +} + +void +GenericConfigBackendDHCPv4Test::TearDown() { + cbptr_.reset(); + // If data wipe enabled, delete transient data otherwise destroy the schema. + destroySchema(); +} + +void +GenericConfigBackendDHCPv4Test::initTestServers() { + test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1")); + test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis")); + test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2")); + test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3")); +} + +void +GenericConfigBackendDHCPv4Test::initTestSubnets() { + // First subnet includes all parameters. + std::string interface_id_text = "whale"; + OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end()); + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40, 60, 1024)); + subnet->get4o6().setIface4o6("eth0"); + subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, + interface_id))); + subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64); + subnet->setFilename("/tmp/filename"); + subnet->allowClientClass("home"); + subnet->setIface("eth1"); + subnet->setMatchClientId(false); + subnet->setSiaddr(IOAddress("10.1.2.3")); + subnet->setT2(323212); + subnet->addRelayAddress(IOAddress("10.2.3.4")); + subnet->addRelayAddress(IOAddress("10.5.6.7")); + subnet->setT1(1234); + subnet->requireClientClass("required-class1"); + subnet->requireClientClass("required-class2"); + subnet->setReservationsGlobal(false); + subnet->setReservationsInSubnet(false); + subnet->setSname("server-hostname"); + subnet->setContext(user_context); + subnet->setValid(555555); + subnet->setAuthoritative(true); + subnet->setCalculateTeeTimes(true); + subnet->setT1Percent(0.345); + subnet->setT2Percent(0.444); + subnet->setDdnsSendUpdates(false); + + Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"))); + subnet->addPool(pool1); + + Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"), IOAddress("192.0.2.60"))); + subnet->addPool(pool2); + + // Add several options to the subnet. + subnet->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + + subnet->getCfgOption()->add(test_options_[1]->option_, + test_options_[1]->persistent_, + test_options_[1]->space_name_); + + subnet->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + test_subnets_.push_back(subnet); + + // Adding another subnet with the same subnet id to test + // cases that this second instance can override existing + // subnet instance. + subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 20, 30, 40, 1024)); + + pool1.reset(new Pool4(IOAddress("10.0.0.10"), IOAddress("10.0.0.20"))); + subnet->addPool(pool1); + + pool1->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + pool1->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + pool2.reset(new Pool4(IOAddress("10.0.0.50"), IOAddress("10.0.0.60"))); + + pool2->allowClientClass("work"); + pool2->requireClientClass("required-class3"); + pool2->requireClientClass("required-class4"); + user_context = Element::createMap(); + user_context->set("bar", Element::create("foo")); + pool2->setContext(user_context); + + subnet->addPool(pool2); + + test_subnets_.push_back(subnet); + + subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048)); + Triplet<uint32_t> null_timer; + subnet->setT1(null_timer); + subnet->setT2(null_timer); + subnet->setValid(null_timer); + subnet->setDdnsSendUpdates(true); + subnet->setDdnsOverrideNoUpdate(true); + subnet->setDdnsOverrideClientUpdate(false); + subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); + subnet->setDdnsGeneratedPrefix("myhost"); + subnet->setDdnsQualifyingSuffix("example.org"); + + subnet->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + + test_subnets_.push_back(subnet); + + // Add a subnet with all defaults. + subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24, Triplet<uint32_t>(), + Triplet<uint32_t>(), Triplet<uint32_t>(), 4096)); + test_subnets_.push_back(subnet); +} + +void +GenericConfigBackendDHCPv4Test::initTestSharedNetworks() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + SharedNetwork4Ptr shared_network(new SharedNetwork4("level1")); + shared_network->allowClientClass("foo"); + shared_network->setIface("eth1"); + shared_network->setMatchClientId(false); + shared_network->setT2(323212); + shared_network->addRelayAddress(IOAddress("10.2.3.4")); + shared_network->addRelayAddress(IOAddress("10.5.6.7")); + shared_network->setT1(1234); + shared_network->requireClientClass("required-class1"); + shared_network->requireClientClass("required-class2"); + shared_network->setReservationsGlobal(false); + shared_network->setReservationsInSubnet(false); + shared_network->setContext(user_context); + shared_network->setValid(5555); + shared_network->setCalculateTeeTimes(true); + shared_network->setT1Percent(0.345); + shared_network->setT2Percent(0.444); + shared_network->setSiaddr(IOAddress("192.0.1.2")); + shared_network->setSname("frog"); + shared_network->setFilename("/dev/null"); + shared_network->setAuthoritative(true); + shared_network->setDdnsSendUpdates(false); + + // Add several options to the shared network. + shared_network->getCfgOption()->add(test_options_[2]->option_, + test_options_[2]->persistent_, + test_options_[2]->space_name_); + + shared_network->getCfgOption()->add(test_options_[3]->option_, + test_options_[3]->persistent_, + test_options_[3]->space_name_); + + shared_network->getCfgOption()->add(test_options_[4]->option_, + test_options_[4]->persistent_, + test_options_[4]->space_name_); + + test_networks_.push_back(shared_network); + + // Adding another shared network called "level1" to test + // cases that this second instance can override existing + // "level1" instance. + shared_network.reset(new SharedNetwork4("level1")); + test_networks_.push_back(shared_network); + + // Add more shared networks. + shared_network.reset(new SharedNetwork4("level2")); + Triplet<uint32_t> null_timer; + shared_network->setT1(null_timer); + shared_network->setT2(null_timer); + shared_network->setValid(null_timer); + shared_network->setDdnsSendUpdates(true); + shared_network->setDdnsOverrideNoUpdate(true); + shared_network->setDdnsOverrideClientUpdate(false); + shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT); + shared_network->setDdnsGeneratedPrefix("myhost"); + shared_network->setDdnsQualifyingSuffix("example.org"); + + shared_network->getCfgOption()->add(test_options_[0]->option_, + test_options_[0]->persistent_, + test_options_[0]->space_name_); + test_networks_.push_back(shared_network); + + shared_network.reset(new SharedNetwork4("level3")); + test_networks_.push_back(shared_network); +} + +void +GenericConfigBackendDHCPv4Test::initTestOptionDefs() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefinitionPtr option_def(new OptionDefinition("foo", 234, + DHCP4_OPTION_SPACE, + "string", + "espace")); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("bar", 234, DHCP4_OPTION_SPACE, + "uint32", true)); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("fish", 235, DHCP4_OPTION_SPACE, + "record", true)); + option_def->addRecordField("uint32"); + option_def->addRecordField("string"); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("whale", 236, "xyz", "string")); + test_option_defs_.push_back(option_def); + + option_def.reset(new OptionDefinition("foobar", 234, DHCP4_OPTION_SPACE, + "uint64", true)); + test_option_defs_.push_back(option_def); +} + +void +GenericConfigBackendDHCPv4Test::initTestOptions() { + ElementPtr user_context = Element::createMap(); + user_context->set("foo", Element::create("bar")); + + OptionDefSpaceContainer defs; + + OptionDescriptor desc = + createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, + true, false, "my-boot-file"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL, + false, true, 64); + desc.space_name_ = DHCP4_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131), + desc.space_name_ = "vendor-encapsulated-options"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption<Option4AddrLst>(254, true, true, + "192.0.2.3"); + desc.space_name_ = DHCP4_OPTION_SPACE; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createEmptyOption(Option::V4, 1, true); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createAddressOption<Option4AddrLst>(2, false, true, "10.0.0.5", + "10.0.0.3", "10.0.3.4"); + desc.space_name_ = "isc"; + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, + true, false, "my-boot-file-2"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME, + true, false, "my-boot-file-3"); + desc.space_name_ = DHCP4_OPTION_SPACE; + desc.setContext(user_context); + test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc))); + + // Add definitions for DHCPv4 non-standard options in case we need to + // compare subnets, networks and pools in JSON format. In that case, + // the @c toElement functions require option definitions to generate the + // proper output. + defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1, + "vendor-encapsulated-options", + "uint32"))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-254", 254, + DHCP4_OPTION_SPACE, + "ipv4-address", true))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty"))); + defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", "ipv4-address", true))); + + // Register option definitions. + LibDHCP::setRuntimeOptionDefs(defs); +} + +void +GenericConfigBackendDHCPv4Test::initTestClientClasses() { + ExpressionPtr match_expr = boost::make_shared<Expression>(); + CfgOptionPtr cfg_option = boost::make_shared<CfgOption>(); + auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option); + class1->setRequired(true); + class1->setNextServer(IOAddress("1.2.3.4")); + class1->setSname("cool"); + class1->setFilename("epc.cfg"); + class1->setValid(Triplet<uint32_t>(30, 60, 90)); + test_client_classes_.push_back(class1); + + auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option); + class2->setTest("member('foo')"); + test_client_classes_.push_back(class2); + + auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option); + class3->setTest("member('foo') and member('bar')"); + test_client_classes_.push_back(class3); +} + +void +GenericConfigBackendDHCPv4Test::initTimestamps() { + // Current time minus 1 hour to make sure it is in the past. + timestamps_["today"] = boost::posix_time::second_clock::local_time() + - boost::posix_time::hours(1); + // One second after today. + timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1); + // Yesterday. + timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24); + // One second after yesterday. + timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1); + // Two days ago. + timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48); + // Tomorrow. + timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24); + // One second after tomorrow. + timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1); +} + +std::string +GenericConfigBackendDHCPv4Test::logExistingAuditEntries(const std::string& server_tag) { + std::ostringstream s; + + auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>(); + + for (auto audit_entry_it = mod_time_idx.begin(); + audit_entry_it != mod_time_idx.end(); + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + s << audit_entry->getObjectType() << ", " + << audit_entry->getObjectId() << ", " + << static_cast<int>(audit_entry->getModificationType()) << ", " + << audit_entry->getModificationTime() << ", " + << audit_entry->getRevisionId() << ", " + << audit_entry->getLogMessage() + << std::endl; + } + + return (s.str()); +} + +void +GenericConfigBackendDHCPv4Test::getTypeTest(const std::string& expected_type) { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ(expected_type, cbptr_->getType()); +} + +void +GenericConfigBackendDHCPv4Test::getHostTest() { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ("localhost", cbptr_->getHost()); +} + +void +GenericConfigBackendDHCPv4Test::getPortTest() { + DatabaseConnection::ParameterMap params; + params["name"] = "keatest"; + params["password"] = "keatest"; + params["user"] = "keatest"; + ASSERT_NO_THROW(cbptr_ = backendFactory(params)); + ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap()); + EXPECT_EQ(0, cbptr_->getPort()); +} + +void +GenericConfigBackendDHCPv4Test::newAuditEntryTest(const std::string& exp_object_type, + const AuditEntry::ModificationType& + exp_modification_type, + const std::string& exp_log_message, + const ServerSelector& server_selector, + const size_t new_entries_num, + const size_t max_tested_entries) { + + // Get the server tag for which the entries are fetched. + std::string tag; + if (server_selector.getType() == ServerSelector::Type::ALL) { + // Server tag is 'all'. + tag = "all"; + } else { + auto tags = server_selector.getTags(); + // This test is not meant to handle multiple server tags all at once. + if (tags.size() > 1) { + ADD_FAILURE() << "Test error: do not use multiple server tags"; + } else if (tags.size() == 1) { + // Get the server tag for which we run the current test. + tag = tags.begin()->get(); + } + } + + auto audit_entries_size_save = audit_entries_[tag].size(); + + // Audit entries for different server tags are stored in separate + // containers. + ASSERT_NO_THROW_LOG(audit_entries_[tag] + = cbptr_->getRecentAuditEntries(server_selector, + timestamps_["two days ago"], 0)); + ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size()) + << logExistingAuditEntries(tag); + + auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>(); + + // Iterate over specified number of entries starting from the most recent + // one and check they have correct values. + for (auto audit_entry_it = mod_time_idx.rbegin(); + ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) && + (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries)); + ++audit_entry_it) { + auto audit_entry = *audit_entry_it; + EXPECT_EQ(exp_object_type, audit_entry->getObjectType()) + << logExistingAuditEntries(tag); + EXPECT_EQ(exp_modification_type, audit_entry->getModificationType()) + << logExistingAuditEntries(tag); + EXPECT_EQ(exp_log_message, audit_entry->getLogMessage()) + << logExistingAuditEntries(tag); + } +} + +// This test verifies that the server can be added, updated and deleted. +void GenericConfigBackendDHCPv4Test::createUpdateDeleteServerTest() { + // Explicitly set modification time to make sure that the time + // returned from the database is correct. + test_servers_[0]->setModificationTime(timestamps_["yesterday"]); + test_servers_[1]->setModificationTime(timestamps_["today"]); + + // Insert the server1 into the database. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0])); + + { + SCOPED_TRACE("CREATE audit entry for server"); + newAuditEntryTest("dhcp4_server", + AuditEntry::ModificationType::CREATE, + "server set"); + } + + // It should not be possible to create a duplicate of the logical + // server 'all'. + auto all_server = Server::create(ServerTag("all"), "this is logical server all"); + EXPECT_THROW(cbptr_->createUpdateServer4(all_server), isc::InvalidOperation); + + ServerPtr returned_server; + + // An attempt to fetch the server that hasn't been inserted should return + // a null pointer. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server2"))); + EXPECT_FALSE(returned_server); + + // Try to fetch the server which we expect to exist. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); + ASSERT_TRUE(returned_server); + EXPECT_EQ("server1", returned_server->getServerTag().get()); + EXPECT_EQ("this is server 1", returned_server->getDescription()); + EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime()); + + // This call is expected to update the existing server. + ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1])); + + { + SCOPED_TRACE("UPDATE audit entry for server"); + newAuditEntryTest("dhcp4_server", + AuditEntry::ModificationType::UPDATE, + "server set"); + } + + // Verify that the server has been updated. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); + ASSERT_TRUE(returned_server); + EXPECT_EQ("server1", returned_server->getServerTag().get()); + EXPECT_EQ("this is server 1 bis", returned_server->getDescription()); + EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime()); + + uint64_t servers_deleted = 0; + + // Try to delete non-existing server. + ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server2"))); + EXPECT_EQ(0, servers_deleted); + + // Make sure that the server1 wasn't deleted. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); + EXPECT_TRUE(returned_server); + + // Deleting logical server 'all' is not allowed. + EXPECT_THROW(cbptr_->deleteServer4(ServerTag()), isc::InvalidOperation); + + // Delete the existing server. + ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server1"))); + EXPECT_EQ(1, servers_deleted); + + { + SCOPED_TRACE("DELETE audit entry for server"); + newAuditEntryTest("dhcp4_server", + AuditEntry::ModificationType::DELETE, + "deleting a server"); + } + + // Make sure that the server is gone. + ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1"))); + EXPECT_FALSE(returned_server); +} diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h new file mode 100644 index 0000000000..9bcabc231a --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h @@ -0,0 +1,171 @@ +// Copyright (C) 2022 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/. + +#ifndef GENERIC_CONFIG_BACKEND_DHCP4_H +#define GENERIC_CONFIG_BACKEND_DHCP4_H + +#include <database/database_connection.h> +#include <dhcpsrv/config_backend_dhcp4_mgr.h> +#include <dhcpsrv/testutils/generic_backend_unittest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Generic test fixture class for testing DHCPv4 +/// config backend operations. +class GenericConfigBackendDHCPv4Test : public GenericBackendTest { +public: + /// @brief Constructor. + GenericConfigBackendDHCPv4Test() + : test_subnets_(), test_networks_(), test_option_defs_(), + test_options_(), test_client_classes_(), test_servers_(), timestamps_(), + cbptr_(), audit_entries_() { + } + + /// @brief Destructor. + virtual ~GenericConfigBackendDHCPv4Test(){}; + + /// @brief Prepares the class for a test. + /// + /// Invoked by gtest prior test entry, we create the + /// appropriate schema and create a basic host manager to + /// wipe out any prior instance + virtual void SetUp(); + + /// @brief Pre-text exit clean up + /// + /// Invoked by gtest upon test exit, we destroy the schema + /// we created. + virtual void TearDown(); + + /// @brief Abstract method for destroying the back end specific schema + virtual void destroySchema() = 0; + + /// @brief Abstract method for creating the back end specific schema + virtual void createSchema() = 0; + + /// @brief Abstract method which returns the back end specific connection + /// string + virtual std::string validConnectionString() = 0; + + /// @brief Abstract method which instantiates an instance of a + /// DHCPv4 configuration back end. + /// + /// @params Connection parameters describing the back end to create. + /// + /// @return Pointer to the newly created back end instance. + virtual ConfigBackendDHCPv4Ptr backendFactory(db::DatabaseConnection::ParameterMap& + params) = 0; + + /// @brief Counts rows in a selected table in the back end database. + /// + /// This method can be used to verify that some configuration elements were + /// deleted from a selected table as a result of cascade delete or a trigger. + /// For example, deleting a subnet should trigger deletion of its address + /// pools and options. By counting the rows on each table we can determine + /// whether the deletion took place on all tables for which it was expected. + /// + /// @param table Table name. + /// @return Number of rows in the specified table. + virtual size_t countRows(const std::string& table) const = 0; + + /// @brief Creates several servers used in tests. + void initTestServers(); + + /// @brief Creates several subnets used in tests. + void initTestSubnets(); + + /// @brief Creates several subnets used in tests. + void initTestSharedNetworks(); + + /// @brief Creates several option definitions used in tests. + void initTestOptionDefs(); + + /// @brief Creates several DHCP options used in tests. + void initTestOptions(); + + /// @brief Creates several client classes used in tests. + void initTestClientClasses(); + + /// @brief Initialize posix time values used in tests. + void initTimestamps(); + + /// @brief Logs audit entries in the @c audit_entries_ member. + /// + /// This function is called in case of an error. + /// + /// @param server_tag Server tag for which the audit entries should be logged. + std::string logExistingAuditEntries(const std::string& server_tag); + + /// @brief Tests that a backend of the given type can be instantiated. + /// + /// @param expected_type type of the back end created (i.e. "mysql", + /// "postgresql"). + void getTypeTest(const std::string& expected_type); + + /// @brief Verifies that a backend on the localhost can be instantiated. + void getHostTest(); + + /// @brief Verifies that a backend on the localhost port 0 can be instantiated. + void getPortTest(); + + /// @brief Tests that the new audit entry is added. + /// + /// This method retrieves a collection of the existing audit entries and + /// checks that the new one has been added at the end of this collection. + /// It then verifies the values of the audit entry against the values + /// specified by the caller. + /// + /// @param exp_object_type Expected object type. + /// @param exp_modification_type Expected modification type. + /// @param exp_log_message Expected log message. + /// @param server_selector Server selector to be used for next query. + /// @param new_entries_num Number of the new entries expected to be inserted. + /// @param max_tested_entries Maximum number of entries tested. + void newAuditEntryTest(const std::string& exp_object_type, + const db::AuditEntry::ModificationType& exp_modification_type, + const std::string& exp_log_message, + const db::ServerSelector& server_selector = db::ServerSelector::ALL(), + const size_t new_entries_num = 1, + const size_t max_tested_entries = 65535); + + /// @brief Verifies that the server can be added, updated and deleted. + void createUpdateDeleteServerTest(); + + /// @brief Holds pointers to subnets used in tests. + std::vector<Subnet4Ptr> test_subnets_; + + /// @brief Holds pointers to shared networks used in tests. + std::vector<SharedNetwork4Ptr> test_networks_; + + /// @brief Holds pointers to option definitions used in tests. + std::vector<OptionDefinitionPtr> test_option_defs_; + + /// @brief Holds pointers to options used in tests. + std::vector<OptionDescriptorPtr> test_options_; + + /// @brief Holds pointers to classes used in tests. + std::vector<ClientClassDefPtr> test_client_classes_; + + /// @brief Holds pointers to the servers used in tests. + std::vector<db::ServerPtr> test_servers_; + + /// @brief Holds timestamp values used in tests. + std::map<std::string, boost::posix_time::ptime> timestamps_; + + /// @brief Holds pointer to the backend. + boost::shared_ptr<ConfigBackendDHCPv4> cbptr_; + + /// @brief Holds the most recent audit entries. + std::map<std::string, db::AuditEntryCollection> audit_entries_; +}; + +} // namespace test +} // namespace dhcp +} // namespace isc + +#endif // GENERIC_CONFIG_BACKEND_DHCP4_H diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc new file mode 100644 index 0000000000..b7ddfc28e8 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc @@ -0,0 +1,376 @@ +// Copyright (C) 2022 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 <config.h> +#include <database/db_exceptions.h> +#include <database/server.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/testutils/generic_cb_recovery_unittest.h> +#include <dhcpsrv/testutils/test_utils.h> +#include <testutils/gtest_utils.h> + +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> +#include <gtest/gtest.h> + +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<IOService>()) { +} + +GenericConfigBackendDbLostCallbackTest::~GenericConfigBackendDbLostCallbackTest() { +} + +void +GenericConfigBackendDbLostCallbackTest::SetUp() { + DatabaseConnection::db_lost_callback_ = 0; + DatabaseConnection::db_recovered_callback_ = 0; + DatabaseConnection::db_failed_callback_ = 0; + 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; + setConfigBackendImplIOService(IOServicePtr()); + isc::dhcp::TimerMgr::instance()->unregisterTimers(); + isc::dhcp::CfgMgr::instance().clear(); +} + +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<ConfigDbInfoList&>(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<ConfigDbInfoList&>(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<ConfigDbInfoList&>(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_); +} diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h new file mode 100644 index 0000000000..065a931b25 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h @@ -0,0 +1,164 @@ +// Copyright (C) 2022 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/. + +#ifndef GENERIC_CONFIG_BACKEND_RECOVERY_H +#define GENERIC_CONFIG_BACKEND_RECOVERY_H + +#include <database/database_connection.h> +#include <database/server_collection.h> +#include <dhcpsrv/config_backend_dhcp4_mgr.h> +#include <dhcpsrv/testutils/generic_backend_unittest.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Test fixture for verifying config backend database connection +/// loss-recovery behavior. +class GenericConfigBackendDbLostCallbackTest : public ::testing::Test { +public: + /// @brief Constructor + GenericConfigBackendDbLostCallbackTest(); + + /// @brief Destructor + virtual ~GenericConfigBackendDbLostCallbackTest(); + + /// @brief Abstract method for destroying the back end specific schema + virtual void destroySchema() = 0; + + /// @brief Abstract method for creating the back end specific schema + virtual void createSchema() = 0; + + /// @brief Abstract method which returns a valid, back end specific connection + /// string + virtual std::string validConnectionString() = 0; + + /// @brief Abstract method which returns an invalid,back end specific connection + /// string + virtual std::string invalidConnectionString() = 0; + + /// @brief Abstract method which registers a CB backend type. + virtual void registerBackendType() = 0; + + /// @brief Abstract method which unregisters a CB backend type. + virtual void unregisterBackendType() = 0; + + /// @brief Abstract method which sets the IOService instance in the CB + /// implementation object. + /// + /// @param io_service pointer to the IOService instance to use. It may be + /// an empty pointer. + virtual void setConfigBackendImplIOService(isc::asiolink::IOServicePtr io_service) = 0; + + /// @brief Abstract method which sets the IOService instance in the CB + virtual void addBackend(const std::string& access) = 0; + + /// @brief Abstract method which sets the IOService instance in the CB + virtual db::ServerCollection getAllServers() = 0; + + /// @brief Prepares the class for a test. + /// + /// Invoked by gtest prior test entry, we create the + /// appropriate schema and create a basic DB manager to + /// wipe out any prior instance + virtual void SetUp(); + + /// @brief Pre-text exit clean up + /// + /// Invoked by gtest upon test exit, we destroy the schema + /// we created. + virtual void TearDown(); + + /// @brief Verifies open failures do NOT invoke db lost callback + /// + /// The db lost callback should only be invoked after successfully + /// opening the DB and then subsequently losing it. Failing to + /// open should be handled directly by the application layer. + void testNoCallbackOnOpenFailure(); + + /// @brief Verifies the CB manager's behavior if DB connection is lost + /// + /// This function creates a CB manager with a back end that supports + /// connectivity lost callback. It verifies connectivity by issuing a known + /// valid query. Next it simulates connectivity lost by identifying and + /// closing the socket connection to the CB backend. It then reissues the + /// query and verifies that: + /// -# The Query throws DbOperationError (rather than exiting) + /// -# The registered DbLostCallback was invoked + /// -# The registered DbRecoveredCallback was invoked + void testDbLostAndRecoveredCallback(); + + /// @brief Verifies the CB manager's behavior if DB connection is lost + /// + /// This function creates a CB manager with a back end that supports + /// connectivity lost callback. It verifies connectivity by issuing a known + /// valid query. Next it simulates connectivity lost by identifying and + /// closing the socket connection to the CB backend. It then reissues the + /// query and verifies that: + /// -# The Query throws DbOperationError (rather than exiting) + /// -# The registered DbLostCallback was invoked + /// -# The registered DbFailedCallback was invoked + void testDbLostAndFailedCallback(); + + /// @brief Verifies the CB manager's behavior if DB connection is lost + /// + /// This function creates a CB manager with a back end that supports + /// connectivity lost callback. It verifies connectivity by issuing a known + /// valid query. Next it simulates connectivity lost by identifying and + /// closing the socket connection to the CB backend. It then reissues the + /// query and verifies that: + /// -# The Query throws DbOperationError (rather than exiting) + /// -# The registered DbLostCallback was invoked + /// -# The registered DbRecoveredCallback was invoked after two reconnect + /// attempts (once failing and second triggered by timer) + void testDbLostAndRecoveredAfterTimeoutCallback(); + + /// @brief Verifies the CB manager's behavior if DB connection is lost + /// + /// This function creates a CB manager with a back end that supports + /// connectivity lost callback. It verifies connectivity by issuing a known + /// valid query. Next it simulates connectivity lost by identifying and + /// closing the socket connection to the CB backend. It then reissues the + /// query and verifies that: + /// -# The Query throws DbOperationError (rather than exiting) + /// -# The registered DbLostCallback was invoked + /// -# The registered DbFailedCallback was invoked after two reconnect + /// attempts (once failing and second triggered by timer) + void testDbLostAndFailedAfterTimeoutCallback(); + + /// @brief Callback function registered with the CB manager + bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) { + return (++db_lost_callback_called_); + } + + /// @brief Flag used to detect calls to db_lost_callback function + uint32_t db_lost_callback_called_; + + /// @brief Callback function registered with the CB manager + bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) { + return (++db_recovered_callback_called_); + } + + /// @brief Flag used to detect calls to db_recovered_callback function + uint32_t db_recovered_callback_called_; + + /// @brief Callback function registered with the CB manager + bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) { + return (++db_failed_callback_called_); + } + + /// @brief Flag used to detect calls to db_failed_callback function + uint32_t db_failed_callback_called_; + + /// The IOService object, used for all ASIO operations. + isc::asiolink::IOServicePtr io_service_; +}; + +} // namespace test +} // namespace dhcp +} // namespace isc + +#endif // GENERIC_CONFIG_BACKEND_RECOVERY_H diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc index a6ba7eaf08..29b83f0f81 100644 --- a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc +++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc @@ -18,7 +18,7 @@ PgSqlGenericBackendTest::PgSqlGenericBackendTest() } size_t -PgSqlGenericBackendTest::countRows(PgSqlConnection& conn, const std::string& table) const { +PgSqlGenericBackendTest::countRows(PgSqlConnection& conn, const std::string& table) { // Execute a simple select query on all rows. std::string query = "SELECT * FROM " + table; PGresult * result = PQexec(conn.conn_, query.c_str()); diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h index 11c311482f..d292ff5dd7 100644 --- a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h +++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h @@ -35,7 +35,7 @@ public: /// @param conn PgSql connection to be used for the query. /// @param table Table name. /// @return Number of rows in the specified table. - size_t countRows(db::PgSqlConnection& conn, const std::string& table) const; + static size_t countRows(db::PgSqlConnection& conn, const std::string& table); }; } |