mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 13:07:50 +00:00
[#2275] Refactored pgsql_cb unit tests
Created common classes for testing config backend. Refactored pgsql_cb using said classes. new files: src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h src/lib/dhcpsrv/testutils/Makefile.am - added new files src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h - made countRows static src/hooks/dhcp/pgsql_cb/tests/pgsql_cb_dhcp4_unittest.cc - refactored using new classes
This commit is contained in:
parent
ab8fe5ddbf
commit
21ae136de5
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
589
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
Normal file
589
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
Normal file
@ -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);
|
||||
}
|
171
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
Normal file
171
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
Normal file
@ -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
|
376
src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
Normal file
376
src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
Normal file
@ -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_);
|
||||
}
|
164
src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
Normal file
164
src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
Normal file
@ -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
|
@ -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());
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user