summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h')
-rw-r--r--src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h554
1 files changed, 554 insertions, 0 deletions
diff --git a/src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h b/src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h
new file mode 100644
index 0000000..638a902
--- /dev/null
+++ b/src/hooks/dhcp/lease_cmds/tests/lease_cmds_unittest.h
@@ -0,0 +1,554 @@
+// Copyright (C) 2017-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 <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+#include <config/command_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/resource_handler.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <stats/stats_mgr.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <errno.h>
+#include <set>
+
+namespace {
+
+/// @brief High valid lifetime used for leases in the tests below.
+constexpr uint32_t HIGH_VALID_LIFETIME = 0xFFFFFFFE;
+
+/// @brief December 11th 2030 date used in the unit tests for cltt.
+constexpr time_t DEC_2030_TIME = 1923222072;
+
+/// @brief Test fixture for testing loading and unloading the flex-id library
+class LibLoadTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ LibLoadTest(std::string lib_filename)
+ : lib_name_(lib_filename) {
+ isc::config::CommandMgr::instance();
+ unloadLibs();
+ }
+
+ /// @brief Destructor
+ /// Removes files that may be left over from previous tests
+ virtual ~LibLoadTest() {
+ unloadLibs();
+ }
+
+ /// @brief Adds library/parameters to list of libraries to be loaded
+ void addLib(const std::string& lib, isc::data::ConstElementPtr params) {
+ libraries_.push_back(make_pair(lib, params));
+ }
+
+ /// @brief Load all specified libraries.
+ ///
+ /// The libraries are stored in libraries
+ void loadLibs() {
+ ASSERT_TRUE(isc::hooks::HooksManager::loadLibraries(libraries_))
+ << "library loading failed";
+ }
+
+ /// @brief Unloads all libraries.
+ void unloadLibs() {
+ ASSERT_NO_THROW(isc::hooks::HooksManager::unloadLibraries());
+ }
+
+ /// @brief Checks whether specified command is registered
+ ///
+ /// @param name name of the command to be checked
+ /// @param expect_true true - must be registered, false - must not be
+ void checkCommandRegistered(const std::string& name, bool expect_true) {
+ // First get the list of registered commands
+ isc::data::ConstElementPtr lst = isc::data::Element::fromJSON("{ \"command\": \"list-commands\" }");
+ isc::data::ConstElementPtr rsp = isc::config::CommandMgr::instance().processCommand(lst);
+
+ ASSERT_TRUE(rsp);
+
+ isc::data::ConstElementPtr args = rsp->get("arguments");
+ ASSERT_TRUE(args);
+
+ std::string args_txt = args->str();
+
+ if (expect_true) {
+ EXPECT_TRUE(args_txt.find(name) != std::string::npos);
+ } else {
+ EXPECT_TRUE(args_txt.find(name) == std::string::npos);
+ }
+ }
+
+ /// @brief tests specified command and verifies response
+ ///
+ /// This method loads the library, sends specific command,
+ /// then checks if the result is as expected, checks if text response
+ /// is ok (optional, check skipped if exp_txt is empty) and then returns
+ /// the response (for possible additional checks).
+ ///
+ /// @param cmd JSON command to be sent (must be valid JSON)
+ /// @param exp_result 0 - success, 1 - error, 2 - ...
+ /// @param exp_txt expected text response (optional)
+ /// @return full response returned by the command execution.
+ isc::data::ConstElementPtr testCommand(std::string cmd_txt, int exp_result,
+ std::string exp_txt) {
+ // Let's load the library first.
+ loadLib();
+
+ isc::data::ConstElementPtr cmd;
+ EXPECT_NO_THROW(cmd = isc::data::Element::fromJSON(cmd_txt));
+ if (!cmd) {
+ ADD_FAILURE() << cmd_txt << " is not a valid JSON, test broken";
+ return (isc::data::ConstElementPtr());
+ }
+
+ // Process the command and verify response.
+ isc::data::ConstElementPtr rsp = isc::config::CommandMgr::instance().processCommand(cmd);
+ checkAnswer(rsp, exp_result, exp_txt);
+
+ return (rsp);
+ }
+
+ /// @brief Compares the status in the given parse result to a given value.
+ ///
+ /// @param answer Element set containing an integer response and string
+ /// comment.
+ /// @param exp_status is an integer against which to compare the status.
+ /// @param exp_txt is expected text (not checked if "")
+ void checkAnswer(isc::data::ConstElementPtr answer,
+ int exp_status,
+ std::string exp_txt = "") {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode != exp_status) {
+ ADD_FAILURE() << "Expected status code " << exp_status
+ << " but received " << rcode << ", comment: "
+ << (comment ? comment->str() : "(none)");
+ }
+
+ // Ok, parseAnswer interface is weird. If there are no arguments,
+ // it returns content of text. But if there is an argument,
+ // it returns the argument and it's not possible to retrieve
+ // "text" (i.e. comment).
+ if (comment->getType() != isc::data::Element::string) {
+ comment = answer->get("text");
+ }
+
+ if (!exp_txt.empty()) {
+ EXPECT_EQ(exp_txt, comment->stringValue());
+ }
+ }
+
+ /// @brief Loads the library specified by lib_name_
+ void loadLib() {
+ if (libraries_.empty()) {
+ isc::data::ElementPtr params = isc::data::Element::createMap();
+ addLib(lib_name_, params);
+ }
+ EXPECT_NO_THROW(loadLibs());
+ }
+
+ /// @brief Test checks if specified commands are provided by the library.
+ ///
+ /// @param cms a vector of string with command names
+ void testCommands(const std::vector<std::string> cmds) {
+ // The commands should not be registered yet.
+ for (auto cmd = cmds.begin(); cmd != cmds.end(); ++cmd) {
+ checkCommandRegistered(*cmd, false);
+ }
+
+ loadLib();
+
+ // The commands should be available after library was loaded.
+ for (auto cmd = cmds.begin(); cmd != cmds.end(); ++cmd) {
+ checkCommandRegistered(*cmd, true);
+ }
+
+ unloadLibs();
+
+ // and the commands should be gone now.
+ for (auto cmd = cmds.begin(); cmd != cmds.end(); ++cmd) {
+ checkCommandRegistered(*cmd, false);
+ }
+
+ }
+
+ // Check that the library can be loaded and unloaded multiple times.
+ void testMultipleLoads() {
+ EXPECT_NO_THROW(loadLib());
+ EXPECT_NO_THROW(unloadLibs());
+
+ EXPECT_NO_THROW(loadLib());
+ EXPECT_NO_THROW(unloadLibs());
+
+ EXPECT_NO_THROW(loadLib());
+ EXPECT_NO_THROW(unloadLibs());
+
+ EXPECT_NO_THROW(loadLib());
+ EXPECT_NO_THROW(unloadLibs());
+ }
+
+ /// @brief Verify that NameChangeRequest holds valid values.
+ ///
+ /// This function picks first NameChangeRequest from the internal server's
+ /// queue and checks that it holds valid parameters. The NameChangeRequest
+ /// is removed from the queue.
+ ///
+ /// @param type An expected type of the NameChangeRequest (Add or Remove).
+ /// @param reverse An expected setting of the reverse update flag.
+ /// @param forward An expected setting of the forward update flag.
+ /// @param addr A string representation of the IPv6 address held in the
+ /// NameChangeRequest.
+ /// @param fqdn The expected string value of the FQDN, if blank the
+ /// check is skipped
+ void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+ const bool reverse, const bool forward,
+ const std::string& addr,
+ const std::string& fqdn = "") {
+ isc::dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = isc::dhcp::CfgMgr::instance().getD2ClientMgr().peekAt(0));
+ ASSERT_TRUE(ncr);
+
+ EXPECT_EQ(type, ncr->getChangeType());
+ EXPECT_EQ(forward, ncr->isForwardChange());
+ EXPECT_EQ(reverse, ncr->isReverseChange());
+ EXPECT_EQ(addr, ncr->getIpAddress());
+
+ if (!fqdn.empty()) {
+ EXPECT_EQ(fqdn, ncr->getFqdn());
+ }
+
+ // Process the message off the queue
+ ASSERT_NO_THROW(isc::dhcp::CfgMgr::instance().getD2ClientMgr().runReadyIO());
+ }
+
+ /// List of libraries to be/being loaded (usually just one)
+ isc::hooks::HookLibsCollection libraries_;
+
+ /// Path to the library filename
+ std::string lib_name_;
+};
+
+/// @brief Class dedicated to testing lease_cmds library.
+///
+/// Provides convenience methods for loading, testing all commands and
+/// unloading the lease_cmds library.
+class LeaseCmdsTest : public LibLoadTest {
+public:
+
+ /// @brief Pointer to the lease manager
+ isc::dhcp::LeaseMgr* lmptr_;
+
+ /// @brief Reference to the D2 client manager.
+ isc::dhcp::D2ClientMgr& d2_mgr_;
+
+ /// @brief Constructor
+ ///
+ /// Sets the library filename and clears the lease manager pointer.
+ /// Also ensured there is no lease manager leftovers from previous
+ /// test.
+ LeaseCmdsTest()
+ : LibLoadTest(LEASE_CMDS_LIB_SO),
+ d2_mgr_(isc::dhcp::CfgMgr::instance().getD2ClientMgr()) {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ enableD2();
+ lmptr_ = 0;
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Removes library (if any), destroys lease manager (if any).
+ virtual ~LeaseCmdsTest() {
+ // destroys lease manager first because the other order triggers
+ // a clang/boost bug
+ isc::dhcp::LeaseMgrFactory::destroy();
+ disableD2();
+ unloadLibs();
+ lmptr_ = 0;
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Creates an IPv4 lease
+ ///
+ /// Lease parameters: valid lifetime = 0xFFFFFFFE, cltt = 1923222072, fqdn-fwd = false,
+ /// fqdn-rev = true, hostname = myhost.example.com
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param hw_address_pattern value to be used for generating HW address by repeating
+ /// it 6 times.
+ /// @param client_id_pattern value to be used for generating client identifier by
+ /// repeating it 8 times.
+ /// @param declined controls whether the lease should be in declined state.
+ ///
+ /// @return Returns the lease created
+ isc::dhcp::Lease4Ptr createLease4(const std::string& ip_address,
+ const isc::dhcp::SubnetID& subnet_id,
+ const uint8_t hw_address_pattern,
+ const uint8_t client_id_pattern,
+ bool declined = false) {
+ isc::dhcp::Lease4Ptr lease(new isc::dhcp::Lease4());
+
+ lease->addr_ = isc::asiolink::IOAddress(ip_address);
+
+ // Set other parameters. For historical reasons, address 0 is not used.
+ lease->hwaddr_.reset(new isc::dhcp::HWAddr(std::vector<uint8_t>(6, hw_address_pattern), isc::dhcp::HTYPE_ETHER));
+ lease->client_id_ = isc::dhcp::ClientIdPtr(new isc::dhcp::ClientId(std::vector<uint8_t>(8, client_id_pattern)));
+ // Purposely using high cltt and valid lifetime to test that
+ // expiration time is cast properly.
+ lease->valid_lft_ = HIGH_VALID_LIFETIME; // Very high valid lifetime
+ lease->cltt_ = DEC_2030_TIME; // December 11th 2030
+ lease->updateCurrentExpirationTime();
+ if (declined) {
+ lease->state_ = isc::dhcp::Lease::STATE_DECLINED;
+ }
+ lease->subnet_id_ = subnet_id;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+
+ return (lease);
+ }
+
+ /// @brief Creates an IPv6 lease
+ ///
+ /// Lease parameters: cltt = 1923222072, fqdn-fwd = false, fqdn-rev = true,
+ /// hostname = myhost.example.com, preferred lifetime = 1800,
+ /// valid lifetime = 0xFFFFFFFE
+ ///
+ /// @param ip_address IP address for the lease.
+ /// @param subnet_id subnet identifier
+ /// @param duid_address_pattern value to be used for generating DUID by
+ /// repeating it 8 times
+ /// @param declined controls whether the lease should be in declined state.
+ ///
+ /// @return Returns the lease created
+ isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
+ const isc::dhcp::SubnetID& subnet_id,
+ const uint8_t duid_pattern,
+ bool declined = false) {
+ isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
+
+ lease->addr_ = isc::asiolink::IOAddress(ip_address);
+ lease->type_ = isc::dhcp::Lease::TYPE_NA;
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
+ lease->preferred_lft_ = 1800;
+ // Purposely using high cltt and valid lifetime to test that
+ // expiration time is cast properly.
+ lease->valid_lft_ = HIGH_VALID_LIFETIME; // Very high valid lifetime
+ lease->cltt_ = DEC_2030_TIME; // December 11th 2030
+ lease->updateCurrentExpirationTime();
+ if (declined) {
+ lease->state_ = isc::dhcp::Lease::STATE_DECLINED;
+ }
+ lease->subnet_id_ = subnet_id;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+
+ return (lease);
+ }
+
+ /// @brief Initializes lease manager (and optionally populates it with a lease)
+ ///
+ /// Creates a lease manager (memfile, trimmed down to keep everything in memory
+ /// only) and optionally can create a lease, which is useful for leaseX-get and
+ /// leaseX-del type of tests. For lease details, see @ref createLease4 and
+ /// @ref createLease6.
+ ///
+ /// @param v6 true = v6, false = v4
+ /// @param insert_lease governs whether a lease should be pre-inserted
+ /// @param declined governs whether a lease should be in declined state
+ void initLeaseMgr(bool v6, bool insert_lease, bool declined = false) {
+ isc::dhcp::LeaseMgrFactory::destroy();
+ std::ostringstream s;
+ s << "type=memfile persist=false " << (v6 ? "universe=6" : "universe=4");
+ isc::dhcp::LeaseMgrFactory::create(s.str());
+
+ lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
+ ASSERT_TRUE(lmptr_);
+
+ isc::dhcp::CfgMgr& cfg_mgr = isc::dhcp::CfgMgr::instance();
+ if (v6) {
+ isc::dhcp::Subnet6Ptr subnet66(new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 66));
+ isc::dhcp::Subnet6Ptr subnet99(new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 99));
+ isc::dhcp::CfgSubnets6Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets6();
+ subnets->add(subnet66);
+ subnets->add(subnet99);
+ cfg_mgr.commit();
+ } else {
+ isc::dhcp::Subnet4Ptr subnet44(new isc::dhcp::Subnet4(isc::asiolink::IOAddress("192.0.2.0"), 24, 1, 2, 3, 44));
+ isc::dhcp::Subnet4Ptr subnet88(new isc::dhcp::Subnet4(isc::asiolink::IOAddress("192.0.3.0"), 24, 1, 2, 3, 88));
+ isc::dhcp::CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+ subnets->add(subnet44);
+ subnets->add(subnet88);
+ cfg_mgr.commit();
+ }
+
+ if (insert_lease) {
+ if (v6) {
+ lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42, declined));
+ lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56, declined));
+ lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42, declined));
+ lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56, declined));
+ if (declined) {
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 66, "declined-addresses"),
+ int64_t(2));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 99, "declined-addresses"),
+ int64_t(2));
+ } else {
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 66, "declined-addresses"),
+ int64_t(0));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 99, "declined-addresses"),
+ int64_t(0));
+ }
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 66, "assigned-nas" ),
+ int64_t(2));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 99, "assigned-nas" ),
+ int64_t(2));
+ } else {
+ lmptr_->addLease(createLease4("192.0.2.1", 44, 0x08, 0x42, declined));
+ lmptr_->addLease(createLease4("192.0.2.2", 44, 0x09, 0x56, declined));
+ lmptr_->addLease(createLease4("192.0.3.1", 88, 0x08, 0x42, declined));
+ lmptr_->addLease(createLease4("192.0.3.2", 88, 0x09, 0x56, declined));
+ if (declined) {
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 44, "declined-addresses"),
+ int64_t(2));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 88, "declined-addresses"),
+ int64_t(2));
+ } else {
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 44, "declined-addresses"),
+ int64_t(0));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 88, "declined-addresses"),
+ int64_t(0));
+ }
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 44, "assigned-addresses"),
+ int64_t(2));
+ isc::stats::StatsMgr::instance().setValue(
+ isc::stats::StatsMgr::generateName("subnet", 88, "assigned-addresses"),
+ int64_t(2));
+ }
+ }
+ }
+
+ /// @brief This function checks that the JSON list contains an entry
+ /// indicating lease deletion, creation or update failure.
+ ///
+ /// @param failed_leases_list JSON list containing list of leases.
+ /// @param expected_type Expected lease type as text.
+ /// @param expected_ip_address Expected IP address.
+ /// @oaram expected_control_result Expected control result for the lease.
+ /// @param expected_error_msg Expected error message. Default is an empty
+ /// string which indicates that the error message should not be checked.
+ void checkFailedLease(const isc::data::ConstElementPtr& failed_leases_list,
+ const std::string& expected_type,
+ const std::string& expected_ip_address,
+ const int expected_control_result,
+ const std::string& expected_error_msg = "") {
+ ASSERT_TRUE(failed_leases_list);
+
+ for (auto i = 0; i < failed_leases_list->size(); ++i) {
+ auto failed_lease = failed_leases_list->get(i);
+ ASSERT_TRUE(failed_lease);
+ ASSERT_EQ(isc::data::Element::map, failed_lease->getType());
+
+ auto ip_address = failed_lease->get("ip-address");
+ ASSERT_TRUE(ip_address);
+ ASSERT_EQ(isc::data::Element::string, ip_address->getType());
+
+ if (ip_address->stringValue() == expected_ip_address) {
+ auto lease_type = failed_lease->get("type");
+ ASSERT_TRUE(lease_type);
+ ASSERT_EQ(isc::data::Element::string, lease_type->getType());
+ EXPECT_EQ(expected_type, lease_type->stringValue());
+
+ auto control_result = failed_lease->get("result");
+ ASSERT_TRUE(control_result);
+ ASSERT_EQ(isc::data::Element::integer, control_result->getType());
+ EXPECT_EQ(expected_control_result, control_result->intValue());
+
+ if (!expected_error_msg.empty()) {
+ auto error_msg = failed_lease->get("error-message");
+ ASSERT_TRUE(error_msg);
+ ASSERT_EQ(isc::data::Element::string, error_msg->getType());
+ EXPECT_EQ(expected_error_msg, error_msg->stringValue());
+ }
+
+ return;
+ }
+ }
+
+ ADD_FAILURE() << "expected lease not found";
+ }
+
+ /// @brief Enables DHCP-DDNS updates.
+ void enableD2() {
+ isc::dhcp::D2ClientConfigPtr cfg(new isc::dhcp::D2ClientConfig());
+ ASSERT_NO_THROW(cfg->enableUpdates(true));
+ ASSERT_NO_THROW(isc::dhcp::CfgMgr::instance().setD2ClientConfig(cfg));
+ d2_mgr_.startSender(std::bind(&LeaseCmdsTest::d2ErrorHandler, this,
+ std::placeholders::_1, std::placeholders::_2));
+ }
+
+ /// @brief Disables DHCP-DDNS updates.
+ void disableD2() {
+ d2_mgr_.stopSender();
+ // Default constructor creates a config with DHCP-DDNS updates
+ // disabled.
+ isc::dhcp::D2ClientConfigPtr cfg(new isc::dhcp::D2ClientConfig());
+ isc::dhcp::CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ /// @brief No-op error handler for D2.
+ void d2ErrorHandler(const isc::dhcp_ddns::NameChangeSender::Result,
+ isc::dhcp_ddns::NameChangeRequestPtr&) {
+ // no-op
+ }
+
+ /// @brief Fetches the number of entries in the NCR sender queue.
+ ///
+ /// @return The NCR queue size.
+ int ncrQueueSize() {
+ int size = -1;
+ try {
+ size = d2_mgr_.getQueueSize();
+ } catch (...) {
+ // If d2_mgr_ isn't in sending, it will throw.
+ // Swallow the exception and return -1.
+ }
+
+ return (size);
+ }
+
+ /// @brief Check that leaseX-del checks update-ddns input.
+ void testLeaseXDelBadUpdateDdnsParam();
+};
+
+} // end of anonymous namespace