// Copyright (C) 2015-2023 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 LIBDHCPSRV_ALLOC_ENGINE_UTILS_H #define LIBDHCPSRV_ALLOC_ENGINE_UTILS_H #include #include #include #include #include #include #include #include namespace isc { namespace dhcp { namespace test { /// @file alloc_engine_utils.h /// /// @brief This is a header file for all Allocation Engine tests. /// /// There used to be one, huge (over 3kloc) alloc_engine_unittest.cc. It is now /// split into serveral smaller files: /// alloc_engine_utils.h - contains test class definitions (this file) /// alloc_engine_utils.cc - contains test class implementation /// alloc_engine4_unittest.cc - all unit-tests dedicated to IPv4 /// alloc_engine6_unittest.cc - all unit-tests dedicated to IPv6 /// alloc_engine_hooks_unittest.cc - all unit-tests dedicated to hooks /// @brief Test that statistic manager holds a given value. /// /// This function may be used in many allocation tests and there's no /// single base class for it. @todo consider moving it src/lib/util. /// /// It checks statistics by name, either the global version when the subnet ID /// is SUBNET_ID_UNUSED or the subnet statistic, including the pool 0 respective /// statistic. /// /// @param stat_name Statistic name. /// @param exp_value Expected value. /// @param subnet_id subnet_id of the desired subnet, if not zero /// @param fail_missing flag which indicate if test should fail if the statistic /// does not exist, or simply ignore it. /// /// @return true if the statistic manager holds a particular value, /// false otherwise. bool testStatistics(const std::string& stat_name, const int64_t exp_value, const SubnetID subnet_id = SUBNET_ID_UNUSED, bool fail_missing = true); /// @brief Get a value held by statistic manager. /// /// This function may be used in some allocation tests and there's no /// single base class for it. @todo consider moving it src/lib/util. /// /// @param stat_name Statistic name. /// @param subnet_id subnet_id of the desired subnet, if not zero. /// /// @return the value held by the statistic manager or zero. int64_t getStatistics(const std::string& stat_name, const SubnetID subnet_id = SUBNET_ID_UNUSED); /// @brief IterativeAllocator with internal methods exposed class NakedIterativeAllocator : public IterativeAllocator { public: /// @brief constructor /// /// @param type pool types that will be iterated through NakedIterativeAllocator(Lease::Type type, const WeakSubnetPtr& subnet) : IterativeAllocator(type, subnet) { } using IterativeAllocator::increaseAddress; using IterativeAllocator::increasePrefix; }; /// @brief Allocation engine with some internal methods exposed class NakedAllocEngine : public AllocEngine { public: /// @brief the sole constructor /// /// @param attempts number of lease selection attempts before giving up NakedAllocEngine(unsigned int attempts) : AllocEngine(attempts) { } // Expose internal classes for testing purposes using AllocEngine::updateLease4ExtendedInfo; /// @brief Wrapper method for invoking AllocEngine4::updateLease4ExtendedInfo(). /// /// @param lease lease to update /// @param ctx current packet processing context /// /// @return true if extended information was changed bool callUpdateLease4ExtendedInfo(const Lease4Ptr& lease, AllocEngine::ClientContext4& ctx) const { return (updateLease4ExtendedInfo(lease, ctx)); } /// @brief Wrapper method for invoking AllocEngine6::updateLease6ExtendedInfo(). /// /// @param lease lease to update /// @param ctx current packet processing context void callUpdateLease6ExtendedInfo(const Lease6Ptr& lease, AllocEngine::ClientContext6& ctx) const { updateLease6ExtendedInfo(lease, ctx); } }; /// @brief Used in Allocation Engine tests for IPv6 class AllocEngine6Test : public ::testing::Test { public: /// @brief Specified expected result of a given operation enum ExpectedResult { SHOULD_PASS, SHOULD_FAIL }; /// @brief Default constructor /// /// Sets duid_, iaid_, subnet_, pool_ fields to example values used /// in many tests, initializes cfg_mgr configuration and creates /// lease database. AllocEngine6Test(); /// @brief Configures a subnet and adds one pool to it. /// /// This function removes existing v6 subnets before configuring /// a new one. /// /// @param subnet Address of a subnet to be configured. /// @param pool_start First address in the address pool. /// @param pool_end Last address in the address pool. /// @param pd_pool_prefix Prefix for the prefix delegation pool. It /// defaults to 0 which means that PD pool is not specified. /// @param pd_pool_length Length of the PD pool prefix. /// @param pd_delegated_length Delegated prefix length. void initSubnet(const asiolink::IOAddress& subnet, const asiolink::IOAddress& pool_start, const asiolink::IOAddress& pool_end, const asiolink::IOAddress& pd_pool_prefix = asiolink::IOAddress::IPV6_ZERO_ADDRESS(), const uint8_t pd_pool_length = 0, const uint8_t pd_delegated_length = 0); /// @brief Initializes FQDN data for a test. /// /// The initialized values are used by the test fixture class members to /// verify the correctness of a lease. /// /// @param hostname Hostname to be assigned to a lease. /// @param fqdn_fwd Indicates whether or not to perform forward DNS update /// for a lease. /// @param fqdn_fwd Indicates whether or not to perform reverse DNS update /// for a lease. void initFqdn(const std::string& hostname, const bool fqdn_fwd, const bool fqdn_rev) { hostname_ = hostname; fqdn_fwd_ = fqdn_fwd; fqdn_rev_ = fqdn_rev; } /// @brief Wrapper around call to AllocEngine6::findReservation /// /// If a reservation is found by the engine, the function sets /// ctx.hostname_ accordingly. /// /// @param engine allocation engine to use /// @param ctx client context to pass into engine's findReservation method void findReservation(AllocEngine& engine, AllocEngine::ClientContext6& ctx); /// @brief attempts to convert leases collection to a single lease /// /// This operation makes sense if there is at most one lease in the /// collection. Otherwise it will throw. /// /// @param col collection of leases (zero or one leases allowed) /// @throw MultipleRecords if there is more than one lease /// /// @return Lease6 pointer (or NULL if collection was empty) Lease6Ptr expectOneLease(const Lease6Collection& col) { if (col.size() > 1) { isc_throw(db::MultipleRecords, "More than one lease found in collection"); } if (col.empty()) { return (Lease6Ptr()); } return (*col.begin()); } /// @brief checks if Lease6 matches expected configuration /// /// @param duid pointer to the client's DUID. /// @param lease lease to be checked /// @param exp_type expected lease type /// @param exp_pd_len expected prefix length /// @param expected_in_subnet whether the lease is expected to be in subnet /// @param expected_in_pool whether the lease is expected to be in dynamic void checkLease6(const DuidPtr& duid, const Lease6Ptr& lease, Lease::Type exp_type, uint8_t exp_pd_len = 128, bool expected_in_subnet = true, bool expected_in_pool = true) { // that is belongs to the right subnet EXPECT_EQ(lease->subnet_id_, subnet_->getID()); if (expected_in_subnet) { EXPECT_TRUE(subnet_->inRange(lease->addr_)) << " address: " << lease->addr_.toText(); } else { EXPECT_FALSE(subnet_->inRange(lease->addr_)) << " address: " << lease->addr_.toText(); } if (expected_in_pool) { EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_)); } else { EXPECT_FALSE(subnet_->inPool(exp_type, lease->addr_)); } // that it have proper parameters EXPECT_EQ(exp_type, lease->type_); EXPECT_EQ(iaid_, lease->iaid_); if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) { EXPECT_EQ(subnet_->getValid(), lease->valid_lft_); } else { EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_); EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_); } if (subnet_->getPreferred().getMin() == subnet_->getPreferred().getMax()) { EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_); } else { EXPECT_LE(subnet_->getPreferred().getMin(), lease->preferred_lft_); EXPECT_GE(subnet_->getPreferred().getMax(), lease->preferred_lft_); } EXPECT_EQ(exp_pd_len, lease->prefixlen_); EXPECT_EQ(fqdn_fwd_, lease->fqdn_fwd_); EXPECT_EQ(fqdn_rev_, lease->fqdn_rev_); EXPECT_EQ(hostname_, lease->hostname_); EXPECT_TRUE(*lease->duid_ == *duid); EXPECT_EQ(0, lease->reuseable_valid_lft_); /// @todo: check cltt } /// @brief Checks if specified address or prefix has been recorded as /// allocated to the client. /// /// @param lease Allocated lease. /// @param ctx Context structure in which this function should check if /// leased address is stored as allocated resource. void checkAllocatedResources(const Lease6Ptr& lease, AllocEngine::ClientContext6& ctx) { EXPECT_TRUE(ctx.isAllocated(lease->addr_, lease->prefixlen_)); } /// @brief Checks if specified address is increased properly /// /// Method uses gtest macros to mark check failure. This is a proxy /// method, since increaseAddress was moved to IOAddress class. /// /// @param alloc IterativeAllocator that is tested /// @param input address to be increased /// @param exp_output expected address after increase void checkAddrIncrease(NakedIterativeAllocator& alloc, std::string input, std::string exp_output) { EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input), false, 0).toText()); } /// @brief Checks if increasePrefix() works as expected /// /// Method uses gtest macros to mark check failure. /// /// @param alloc allocator to be tested /// @param input IPv6 prefix (as a string) /// @param prefix_len prefix len /// @param exp_output expected output (string) void checkPrefixIncrease(NakedIterativeAllocator& alloc, std::string input, uint8_t prefix_len, std::string exp_output) { EXPECT_EQ(exp_output, alloc.increasePrefix(asiolink::IOAddress(input), prefix_len).toText()); } /// @brief Checks if the simple allocation can succeed /// /// The type of lease is determined by pool type (pool->getType()) /// /// @param pool pool from which the lease will be allocated from /// @param hint address to be used as a hint /// @param fake true - this is fake allocation (SOLICIT) /// @param in_pool specifies whether the lease is expected to be in pool /// /// @return allocated lease (or NULL) Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool, const asiolink::IOAddress& hint, bool fake, bool in_pool = true); /// @brief Checks if the simple allocation can succeed with lifetimes. /// /// The type of lease is determined by pool type (pool->getType()) /// /// @param pool pool from which the lease will be allocated from /// @param hint address to be used as a hint /// @param preferred preferred lifetime to be used as a hint /// @param valid valid lifetime to be used as a hint /// @param exp_preferred expected lease preferred lifetime /// @param exp_valid expected lease valid lifetime /// @param class_def class definition to add to the context /// /// @return allocated lease (or NULL) Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool, const asiolink::IOAddress& hint, uint32_t preferred, uint32_t valid, uint32_t exp_preferred, uint32_t exp_valid, ClientClassDefPtr class_def = ClientClassDefPtr()); /// @brief Checks if the simple allocation can succeed for custom DUID. /// /// The type of lease is determined by pool type (pool->getType()) /// /// @param pool pool from which the lease will be allocated from /// @param duid pointer to the DUID used for allocation. /// @param hint address to be used as a hint /// @param fake true - this is fake allocation (SOLICIT) /// @param in_pool specifies whether the lease is expected to be in pool /// /// @return allocated lease (or NULL) Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool, const DuidPtr& duid, const asiolink::IOAddress& hint, bool fake, bool in_pool = true); /// @brief Checks if the allocation can succeed. /// /// The type of lease is determined by pool type (pool->getType()). /// This test is particularly useful in connection with @ref renewTest. /// /// @param engine a reference to Allocation Engine /// @param pool pool from which the lease will be allocated from /// @param hint address to be used as a hint /// @param fake true - this is fake allocation (SOLICIT) /// @param in_pool specifies whether the lease is expected to be in pool /// @param hint_prefix_length The hint prefix length that the client /// provided. /// /// @return allocated lease(s) (may be empty) Lease6Collection allocateTest(AllocEngine& engine, const Pool6Ptr& pool, const asiolink::IOAddress& hint, bool fake, bool in_pool = true, uint8_t hint_prefix_length = 128); /// @brief Checks if the allocation can be renewed. /// /// The type of lease is determined by pool type (pool->getType()). /// This test is particularly useful as a follow up to @ref allocateTest. /// /// @param engine a reference to Allocation Engine /// @param pool pool from which the lease will be allocated from /// @param hints address to be used as a hint /// @param in_subnet whether the lease is expected to be in subnet /// @param in_pool specifies whether the lease is expected to be in pool /// /// @return allocated lease(s) (may be empty) Lease6Collection renewTest(AllocEngine& engine, const Pool6Ptr& pool, AllocEngine::HintContainer& hints, bool in_subnet, bool in_pool); /// @brief Checks if the address allocation with a hint that is in range, /// in pool, but is currently used, can succeed /// /// Method uses gtest macros to mark check failure. /// /// @param type lease type /// @param used_addr address should be preallocated (simulates prior /// allocation by some other user) /// @param requested address requested by the client /// @param expected_pd_len expected PD len (128 for addresses) void allocWithUsedHintTest(Lease::Type type, asiolink::IOAddress used_addr, asiolink::IOAddress requested, uint8_t expected_pd_len); /// @brief Generic test used for IPv6 lease allocation and reuse /// /// This test inserts existing_lease (if specified, may be null) into the /// LeaseMgr, then conducts lease allocation (pretends that client /// sent either Solicit or Request, depending on fake_allocation). /// Allocated lease is then returned (using result) for further inspection. /// /// @param alloc_engine allocation engine /// @param existing_lease optional lease to be inserted in the database /// @param addr address to be requested by client /// @param fake_allocation true = SOLICIT, false = REQUEST /// @param exp_result expected result /// @param result [out] allocated lease void testReuseLease6(const AllocEnginePtr& alloc_engine, Lease6Ptr& existing_lease, const std::string& addr, const bool fake_allocation, ExpectedResult exp_result, Lease6Ptr& result); /// @brief Creates a declined IPv6 lease with specified expiration time /// /// expired parameter controls probation period. Positive value /// means that the lease will expire in X seconds. Negative means /// that the lease expired X seconds ago. 0 means it expires now. /// Probation period is a parameter that specifies for how long /// a lease will stay unavailable after decline. /// /// @param addr address of the lease /// @param probation_period expressed in seconds /// @param expired number of seconds when the lease will expire /// /// @return generated lease Lease6Ptr generateDeclinedLease(const std::string& addr, time_t probation_period, int32_t expired); /// @brief checks if bogus hint can be ignored and the allocation succeeds /// /// This test checks if the allocation with a hing that is out of the blue /// can succeed. The invalid hint should be ignored completely. /// /// @param type Lease type /// @param hint hint (as sent by a client) /// @param expected_pd_len (used in validation) void allocBogusHint6(Lease::Type type, asiolink::IOAddress hint, uint8_t expected_pd_len); /// @brief Utility function that creates a host reservation (duid) /// /// @param add_to_host_mgr true if the reservation should be added /// @param type specifies reservation type /// @param addr specifies reserved address or prefix /// @param prefix_len prefix length (should be 128 for addresses) /// /// @return created Host object. HostPtr createHost6(bool add_to_host_mgr, IPv6Resrv::Type type, const asiolink::IOAddress& addr, uint8_t prefix_len) { HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(), asiolink::IOAddress("0.0.0.0"))); IPv6Resrv resv(type, addr, prefix_len); host->addReservation(resv); if (add_to_host_mgr) { addHost(host); } return (host); } /// @brief Add a host reservation to the current configuration /// /// Adds the given host reservation to the current configuration by /// casting it to non-const. We do it this way rather than adding it to /// staging and then committing as that wipes out the current configuration /// such as subnets. /// /// @param host host reservation to add void addHost(HostPtr& host) { SrvConfigPtr cfg = boost::const_pointer_cast(CfgMgr::instance().getCurrentCfg()); cfg->getCfgHosts()->add(host); } /// @brief Utility function that creates a host reservation (hwaddr) /// /// @param add_to_host_mgr true if the reservation should be added /// @param type specifies reservation type /// @param hwaddr hardware address to be reserved to /// @param addr specifies reserved address or prefix /// @param prefix_len prefix length (should be 128 for addresses) /// /// @return created Host object. HostPtr createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type, HWAddrPtr& hwaddr, const asiolink::IOAddress& addr, uint8_t prefix_len); /// @brief Utility function that decrements cltt of a persisted lease /// /// This function is used to simulate the passage of time by decrementing /// the lease's cltt, currently by 1. It fetches the desired lease from the /// lease manager, decrements the cltt, then updates the lease in the lease /// manager. Next, it refetches the lease and verifies the update took place. /// /// @param[in][out] lease pointer reference to the lease to modify. Upon /// return it will point to the newly updated lease. void rollbackPersistedCltt(Lease6Ptr& lease) { ASSERT_TRUE(lease) << "rollbackPersistedCltt lease is empty"; // Fetch it, so we can update it. Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_); ASSERT_TRUE(from_mgr) << "rollbackPersistedCltt: lease not found?"; // Decrement cltt then update it in the manager. --from_mgr->cltt_; ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease6(from_mgr)); // Fetch it fresh. lease = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_); // Make sure it stuck. ASSERT_EQ(lease->cltt_, from_mgr->cltt_); } virtual ~AllocEngine6Test() { factory_.destroy(); } DuidPtr duid_; ///< client-identifier (value used in tests) HWAddrPtr hwaddr_; ///< client's hardware address uint32_t iaid_; ///< IA identifier (value used in tests) Subnet6Ptr subnet_; ///< subnet6 (used in tests) Pool6Ptr pool_; ///< NA pool belonging to subnet_ Pool6Ptr pd_pool_; ///< PD pool belonging to subnet_ std::string hostname_; ///< Hostname bool fqdn_fwd_; ///< Perform forward update for a lease. bool fqdn_rev_; ///< Perform reverse update for a lease. LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory ClientClasses cc_; ///< client classes }; /// @brief Used in Allocation Engine tests for IPv4 class AllocEngine4Test : public ::testing::Test { public: /// @brief Specified expected result of a given operation enum ExpectedResult { SHOULD_PASS, SHOULD_FAIL }; /// @brief Default constructor /// /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values /// used in many tests, initializes cfg_mgr configuration and creates /// lease database. /// /// It also re-initializes the Host Manager. AllocEngine4Test(); /// @brief checks if Lease4 matches expected configuration /// /// @param lease lease to be checked void checkLease4(const Lease4Ptr& lease) { // Check that is belongs to the right subnet EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); // Check that it has proper parameters if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) { EXPECT_EQ(subnet_->getValid(), lease->valid_lft_); } else { EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_); EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_); } if (lease->client_id_ && !clientid_) { ADD_FAILURE() << "Lease4 has a client-id, while it should have none."; } else if (!lease->client_id_ && clientid_) { ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one."; } else if (lease->client_id_ && clientid_) { EXPECT_TRUE(*lease->client_id_ == *clientid_); } EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); EXPECT_EQ(0, lease->reuseable_valid_lft_); /// @todo: check cltt } /// @brief Generic test used for IPv4 lease allocation and reuse /// /// This test inserts existing_lease (if specified, may be null) into the /// LeaseMgr, then conducts lease allocation (pretends that client /// sent either Discover or Request, depending on fake_allocation). /// Allocated lease is then returned (using result) for further inspection. /// /// @param alloc_engine allocation engine /// @param existing_lease optional lease to be inserted in the database /// @param addr address to be requested by client /// @param fake_allocation true = DISCOVER, false = REQUEST /// @param exp_result expected result /// @param result [out] allocated lease void testReuseLease4(const AllocEnginePtr& alloc_engine, Lease4Ptr& existing_lease, const std::string& addr, const bool fake_allocation, ExpectedResult exp_result, Lease4Ptr& result); /// @brief Creates a declined IPv4 lease with specified expiration time /// /// expired parameter controls probation period. Positive value /// means that the lease will expire in X seconds. Negative means /// that the lease expired X seconds ago. 0 means it expires now. /// Probation period is a parameter that specifies for how long /// a lease will stay unavailable after decline. /// /// @param addr address of the lease /// @param probation_period expressed in seconds /// @param expired number of seconds when the lease will expire /// /// @return generated lease Lease4Ptr generateDeclinedLease(const std::string& addr, time_t probation_period, int32_t expired); /// @brief Create a subnet with a specified pool of addresses. /// /// @param pool_start First address in the pool. /// @param pool_end Last address in the pool. void initSubnet(const asiolink::IOAddress& pool_start, const asiolink::IOAddress& pool_end); /// @brief Default constructor virtual ~AllocEngine4Test() { factory_.destroy(); } ClientIdPtr clientid_; ///< Client-identifier (value used in tests) ClientIdPtr clientid2_; ///< Alternative client-identifier. HWAddrPtr hwaddr_; ///< Hardware address (value used in tests) HWAddrPtr hwaddr2_; ///< Alternative hardware address. Subnet4Ptr subnet_; ///< Subnet4 (used in tests) Pool4Ptr pool_; ///< Pool belonging to subnet_ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory AllocEngine::ClientContext4 ctx_; ///< Context information passed to various ClientClasses cc_; ///< Client classes ///< allocation engine functions. }; } // namespace test } // namespace dhcp } // namespace isc #endif