diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc new file mode 100644 index 0000000..ee25e16 --- /dev/null +++ b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc @@ -0,0 +1,881 @@ +// Copyright (C) 2015-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 <asiolink/io_address.h> +#include <dhcpsrv/csv_lease_file4.h> +#include <dhcpsrv/csv_lease_file6.h> +#include <dhcpsrv/memfile_lease_storage.h> +#include <dhcpsrv/lease_file_loader.h> +#include <dhcpsrv/testutils/lease_file_io.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/cfg_consistency.h> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> +#include <sstream> +#include <string> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Test fixture class for @c LeaseFileLoader class. +class LeaseFileLoaderTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Initializes filename used for unit tests and creates an + /// IO object to be used to write to this file. + LeaseFileLoaderTest(); + + /// @brief Destructor + /// + /// Removes any configuration that may have been added in CfgMgr. + virtual ~LeaseFileLoaderTest(); + + /// @brief Prepends the absolute path to the file specified + /// as an argument. + /// + /// @param filename Name of the file. + /// @return Absolute path to the test file. + static std::string absolutePath(const std::string& filename); + + /// @brief Retrieves the lease from the storage using an IP address. + /// + /// This method returns the pointer to the @c Lease4 or @c Lease6 + /// object representing the lease in the container. This is used to + /// check if the lease was parsed in the lease file and added to the + /// container by the @c LeaseFileLoader::load method. + /// + /// @param address A string representation of the leased address. + /// @param storage A reference to the container in which the lease + /// is to be searched. + /// @tparam LeasePtrType Type of the returned object: @c Lease4Ptr + /// @c Lease6Ptr. + /// @tparam LeaseStorage Type of the container: @c Lease4Container + /// @c Lease6Container. + /// + /// @return A pointer to the lease or NULL if no lease found. + template<typename LeasePtrType, typename LeaseStorage> + static LeasePtrType getLease(const std::string& address, const LeaseStorage& storage) { + typedef typename LeaseStorage::template nth_index<0>::type SearchIndex; + // Both Lease4Storage and Lease6Storage use index 0 to retrieve the + // lease using an IP address. + const SearchIndex& idx = storage.template get<0>(); + typename SearchIndex::iterator lease = idx.find(IOAddress(address)); + // Lease found. Return it. + if (lease != idx.end()) { + return (*lease); + } + // No lease found. + return (LeasePtrType()); + } + + /// @brief Tests the write function. + /// + /// This method writes the leases from the storage container to the lease file + /// then compares the output to the string provided in the arguments to verify + /// the write was correct. The order of the leases in the output will depend + /// on the order in which the container provides the leases. + /// + /// @param lease_file A reference to the file to write to + /// @param storage A reference to the container to be written to the file + /// @param compare The string to compare to what was read from the file + /// + /// @tparam LeaseObjectType A @c Lease4 or @c Lease6. + /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6. + /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage. + /// + template<typename LeaseObjectType, typename LeaseFileType, + typename StorageType> + void writeLeases(LeaseFileType& lease_file, + const StorageType& storage, + const std::string& compare) { + // Prepare for a new file, close and remove the old + lease_file.close(); + io_.removeFile(); + + // Write the current leases to the file + LeaseFileLoader::write<LeaseObjectType, LeaseFileType, StorageType> + (lease_file, storage); + + // Compare to see if we got what we expected. + EXPECT_EQ(compare, io_.readFile()); + } + + /// @brief Checks the stats for the file + /// + /// This method is passed a leasefile and the values for the statistics it + /// should have for comparison. + /// + /// @param lease_file A reference to the file we are using + /// @param reads the number of attempted reads + /// @param read_leases the number of valid leases read + /// @param read_errs the number of errors while reading leases + /// @param writes the number of attempted writes + /// @param write_leases the number of leases successfully written + /// @param write_errs the number of errors while writing + /// + /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6. + template<typename LeaseFileType> + void checkStats(LeaseFileType& lease_file, + uint32_t reads, uint32_t read_leases, + uint32_t read_errs, uint32_t writes, + uint32_t write_leases, uint32_t write_errs) const { + EXPECT_EQ(reads, lease_file.getReads()); + EXPECT_EQ(read_leases, lease_file.getReadLeases()); + EXPECT_EQ(read_errs, lease_file.getReadErrs()); + EXPECT_EQ(writes, lease_file.getWrites()); + EXPECT_EQ(write_leases, lease_file.getWriteLeases()); + EXPECT_EQ(write_errs, lease_file.getWriteErrs()); + } + + /// @brief Name of the test lease file. + std::string filename_; + + /// @brief Object providing access to lease file IO. + LeaseFileIO io_; + + std::string v4_hdr_; ///< String for the header of the v4 csv test file + std::string v6_hdr_; ///< String for the header of the v6 csv test file + + Lease4Storage storage4_; ///< Storage for IPv4 leases + Lease6Storage storage6_; ///< Storage for IPv4 leases + + /// @brief Creates IPv4 subnet with specified parameters + /// + /// @param subnet_txt subnet in textual form, e.g. 192.0.2.0/24 + /// @param subnet_id id to be used. + /// @return A pointer to Subnet4 object + Subnet4Ptr createSubnet4(std::string subnet_txt, SubnetID id) { + size_t pos = subnet_txt.find("/"); + isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos)); + size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1)); + + return (Subnet4Ptr(new Subnet4(addr, len, 1000, 2000, 3000, id))); + } + + /// @brief Creates IPv6 subnet with specified parameters + /// + /// @param subnet_txt subnet in textual form, e.g. 2001:db8::/32 + /// @param subnet_id id to be used. + /// @return A pointer to Subnet4 object + Subnet6Ptr createSubnet6(std::string subnet_txt, SubnetID id) { + size_t pos = subnet_txt.find("/"); + isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos)); + size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1)); + + return (Subnet6Ptr(new Subnet6(addr, len, 1000, 2000, 3000, 4000, id))); + } + + /// @brief Checks if IPv4 lease loaded from file is sanity checked. + /// + /// This method writes a simple lease file with one lease in it, + /// then sets sanity checks to tested level, then tries to load + /// the lease file and finally checks whether the lease was loaded + /// or not. + /// + /// @param lease address of the lease in text form + /// @param lease_id subnet-id to be used in a lease + /// @param subnet_txt Text representation of the subnet, e.g. 192.0.2.0/24 + /// @param subnet_id Subnet-id of the subnet to be created + /// @param sanity level of sanity checks + /// @param exp_present is the lease expected to be loaded (true = yes) + /// @param exp_id expected subnet-id of the loaded lease + void sanityChecks4(std::string lease, SubnetID lease_id, + std::string subnet_txt, SubnetID subnet_id, + CfgConsistency::LeaseSanity sanity, + bool exp_present, SubnetID exp_id) { + + // Create the subnet and add it to configuration. + if (!subnet_txt.empty()) { + Subnet4Ptr subnet = createSubnet4(subnet_txt, subnet_id); + ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet)); + } + + std::stringstream file_content; + file_content << v4_hdr_ << lease << ",dd:de:ba:0d:1b:2e," + << "0a:00:01:04,100,100," << static_cast<int>(lease_id) + << ",0,0,,1,\n"; + + ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getConsistency() + ->setLeaseSanityCheck(sanity)); + + io_.writeFile(file_content.str()); + + boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Load leases from the file. + ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage4_, 10)); + + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 2, 1, 0, 0, 0, 0); + } + + // Check how many leases were actually loaded. + ASSERT_EQ( (exp_present ? 1 : 0), storage4_.size()); + + Lease4Ptr l = getLease<Lease4Ptr>(lease, storage4_); + + if (exp_present) { + ASSERT_TRUE(l) << "lease not found, but expected"; + EXPECT_EQ(l->subnet_id_, exp_id); + } else { + EXPECT_FALSE(l) << "lease found, but was not expected"; + } + } + + /// @brief Checks if IPv6 lease loaded from file is sanity checked. + /// + /// This method writes a simple lease file with one lease in it, + /// then sets sanity checks to tested level, then tries to load + /// the lease file and finally checks whether the lease was loaded + /// or not. + /// + /// @param lease address of the lease in text form + /// @param lease_id subnet-id to be used in a lease + /// @param subnet_txt Text representation of the subnet, e.g. 192.0.2.0/24 + /// @param subnet_id Subnet-id of the subnet to be created + /// @param sanity level of sanity checks + /// @param exp_present is the lease expected to be loaded (true = yes) + /// @param exp_id expected subnet-id of the loaded lease + /// @param prefix_len length of the prefix if the lease created should be + /// a PD lease. Defaults to 0. + void sanityChecks6(std::string lease, SubnetID lease_id, + std::string subnet_txt, SubnetID subnet_id, + CfgConsistency::LeaseSanity sanity, + bool exp_present, SubnetID exp_id, + unsigned int prefix_len = 0) { + + // Create the subnet and add it to configuration. + if (!subnet_txt.empty()) { + Subnet6Ptr subnet = createSubnet6(subnet_txt, subnet_id); + ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet)); + } + + std::stringstream file_content; + + file_content << v6_hdr_ << lease << ",dd:de:ba:0d:1b:2e," + << "300,300," << static_cast<int>(lease_id) << ",150," + << (prefix_len > 0 ? Lease::TYPE_PD : Lease::TYPE_NA) + << ",8," << prefix_len << ",0,0,,,1,\n"; + + ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getConsistency() + ->setLeaseSanityCheck(sanity)); + + io_.writeFile(file_content.str()); + + boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Load leases from the file. + ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage6_, 10)); + + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 2, 1, 0, 0, 0, 0); + } + + // Check how many leases were actually loaded. + ASSERT_EQ( (exp_present ? 1 : 0), storage6_.size()); + + Lease6Ptr l = getLease<Lease6Ptr>(lease, storage6_); + + if (exp_present) { + ASSERT_TRUE(l) << "lease not found, but expected"; + EXPECT_EQ(exp_id, l->subnet_id_); + } else { + EXPECT_FALSE(l) << "lease found, but was not expected"; + } + } + +protected: + /// @brief Sets up the header strings + virtual void SetUp() { + v4_hdr_ = "address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n"; + + v6_hdr_ = "address,duid,valid_lifetime,expire,subnet_id," + "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd," + "fqdn_rev,hostname,hwaddr,state,user_context," + "hwtype,hwaddr_source\n"; + } +}; + +LeaseFileLoaderTest::LeaseFileLoaderTest() + : filename_(absolutePath("leases.csv")), io_(filename_) { + CfgMgr::instance().clear(); +} + +LeaseFileLoaderTest::~LeaseFileLoaderTest() { + CfgMgr::instance().clear(); +} + +std::string +LeaseFileLoaderTest::absolutePath(const std::string& filename) { + std::ostringstream s; + s << DHCP_DATA_DIR << "/" << filename; + return (s.str()); +} + +// This test verifies that the DHCPv4 leases can be loaded from the lease +// file and that only the most recent entry for each lease is loaded and +// the previous entries are discarded. +// +// It also tests the write function by writing the storage to a file +// and comparing that with the expected value. +TEST_F(LeaseFileLoaderTest, loadWrite4) { + std::string test_str; + std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,," + "200,200,8,1,1,host.example.com,1," + "{ \"foobar\": true }\n"; + std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,," + "200,500,8,1,1,host.example.com,1," + "{ \"foobar\": true }\n"; + + std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04," + "100,100,7,0,0,,1,\n"; + std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04," + "100,135,7,0,0,,1,\n"; + + std::string c_1 = "192.0.2.3,,," + "200,200,8,1,1,host.example.com,0,\n"; + + // Create lease file with leases for 192.0.2.1, 192.0.3.15. The lease + // entry for the 192.0.2.3 is invalid (lacks HW address and client id) + // and should be discarded. + test_str = v4_hdr_ + a_1 + b_1 + c_1 + b_2 + a_2; + io_.writeFile(test_str); + + boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Load leases from the file. + Lease4Storage storage; + ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10)); + + // We should have made 6 attempts to read, with 4 leases read and 1 error + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 6, 4, 1, 0, 0, 0); + } + + // There are two unique leases. + ASSERT_EQ(2, storage.size()); + + // The lease for 192.0.2.1 should exist and the cltt should be + // set to the expire-valid_lifetime for the second entry for + // this lease, i.e. 500 - 200 = 300. + Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(300, lease->cltt_); + + // The lease for 192.0.2.1 should have user context. + ASSERT_TRUE(lease->getContext()); + EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str()); + + // The invalid entry should not be loaded. + lease = getLease<Lease4Ptr>("192.0.2.3", storage); + ASSERT_FALSE(lease); + + // The other lease should be present and the cltt time should + // be set according to the values in the second entry for this + // lease. + lease = getLease<Lease4Ptr>("192.0.3.15", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(35, lease->cltt_); + EXPECT_FALSE(lease->getContext()); + + test_str = v4_hdr_ + a_2 + b_2; + writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str); + + // We should have made 2 attempts to write, with 2 leases written and 0 errors + { + SCOPED_TRACE("Write leases"); + checkStats(*lf, 0, 0, 0, 2, 2, 0); + } +} + +// This test verifies that the lease with a valid lifetime of 0 +// is removed from the storage. The valid lifetime of 0 is set +// for the released leases. +// +// It also tests the write function by writing the storage to a file +// and comparing that with the expected value. +TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) { + std::string test_str; + std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,," + "200,200,8,1,1,host.example.com,1,\n"; + std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,," + "0,500,8,1,1,host.example.com,1,\n"; + + std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04," + "100,100,7,0,0,,1,\n"; + std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04," + "100,135,7,0,0,,1,\n"; + + + // Create lease file in which one of the entries for 192.0.2.1 + // has a valid_lifetime of 0 and results in the deletion of the + // lease. + test_str = v4_hdr_ + a_1 + b_1 + b_2 + a_2; + io_.writeFile(test_str); + + boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_)); + ASSERT_NO_THROW(lf->open()); + + Lease4Storage storage; + ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10)); + + // We should have made 5 attempts to read, with 4 leases read and 0 error + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 5, 4, 0, 0, 0, 0); + } + + // There should only be one lease. The one with the valid_lifetime + // of 0 should be removed. + ASSERT_EQ(1, storage.size()); + + Lease4Ptr lease = getLease<Lease4Ptr>("192.0.3.15", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(35, lease->cltt_); + + test_str = v4_hdr_ + b_2; + writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str); + + // We should have made 1 attempts to write, with 1 leases written and 0 errors + { + SCOPED_TRACE("Write leases"); + checkStats(*lf, 0, 0, 0, 1, 1, 0); + } +} + +// This test verifies that max-row-errors works correctly for +// DHCPv4 lease files +TEST_F(LeaseFileLoaderTest, maxRowErrors4) { + // We have 9 rows: 2 that are good, 7 that are flawed (too few fields). + std::vector<std::string> rows = { + "192.0.2.100,08:00:27:25:d3:f4,31:31:31:31,3600,1565356064,1,0,0,,0,\n", + "192.0.2.101,FF:FF:FF:FF:FF:01,32:32:32:31,3600,1565356073,1,0,0\n", + "192.0.2.102,FF:FF:FF:FF:FF:02,32:32:32:32,3600,1565356073,1,0,0\n", + "192.0.2.103,FF:FF:FF:FF:FF:03,32:32:32:33,3600,1565356073,1,0,0\n", + "192.0.2.104,FF:FF:FF:FF:FF:04,32:32:32:34,3600,1565356073,1,0,0\n", + "192.0.2.105,FF:FF:FF:FF:FF:05,32:32:32:35,3600,1565356073,1,0,0\n", + "192.0.2.106,FF:FF:FF:FF:FF:06,32:32:32:36,3600,1565356073,1,0,0\n", + "192.0.2.107,FF:FF:FF:FF:FF:07,32:32:32:37,3600,1565356073,1,0,0\n", + "192.0.2.108,08:00:27:25:d3:f4,32:32:32:32,3600,1565356073,1,0,0,,0,\n" + }; + + std::ostringstream os; + os << v4_hdr_; + for (auto row : rows) { + os << row; + } + + io_.writeFile(os.str()); + + boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Let's limit the number of errors to 5 (we have 7 in the data) and + // try to load the leases. + uint32_t max_errors = 5; + Lease4Storage storage; + ASSERT_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, max_errors), util::CSVFileError); + + // We should have made 7 reads, with 1 lease read, and 6 errors. + { + SCOPED_TRACE("Failed load stats"); + checkStats(*lf, 7, 1, 6, 0, 0, 0); + } + + // Now let's disable the error limit and try again. + max_errors = 0; + + // Load leases from the file. Note, we have to reopen the file. + ASSERT_NO_THROW(lf->open()); + ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, max_errors)); + + // We should have made 10 reads, with 2 leases read, and 7 errors. + { + SCOPED_TRACE("Good load stats"); + checkStats(*lf, 10, 2, 7, 0, 0, 0); + } +} + +// This test verifies that the DHCPv6 leases can be loaded from the lease +// file and that only the most recent entry for each lease is loaded and +// the previous entries are discarded. +// +// It also tests the write function by writing the storage to a file +// and comparing that with the expected value. +TEST_F(LeaseFileLoaderTest, loadWrite6) { + std::string test_str; + std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f," + "200,200,8,100,0,7,0,1,1,host.example.com,,1," + "{ \"foobar\": true },,\n"; + std::string a_2 = "2001:db8:1::1,," + "200,200,8,100,0,7,0,1,1,host.example.com,,1," + "{ \"foobar\": true },,\n"; + std::string a_3 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f," + "200,400,8,100,0,7,0,1,1,host.example.com,,1," + "{ \"foobar\": true },,\n"; + std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05," + "300,300,6,150,0,8,0,0,0,,,1,,,\n"; + std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05," + "300,800,6,150,0,8,0,0,0,,,1,,,\n"; + + std::string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f," + "100,200,8,0,2,16,64,0,0,,,1,,,\n"; + + + // Create a lease file with three valid leases: 2001:db8:1::1, + // 3000:1:: and 2001:db8:2::10. + test_str = v6_hdr_ + a_1 + a_2 + b_1 + c_1 + b_2 + a_3; + io_.writeFile(test_str); + + boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Load leases from the lease file. + Lease6Storage storage; + ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10)); + + // We should have made 7 attempts to read, with 5 leases read and 1 error + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 7, 5, 1, 0, 0, 0); + } + + // There should be 3 unique leases. + ASSERT_EQ(3, storage.size()); + + // The 2001:db8:1::1 should be present and its cltt should be + // calculated according to the expiration time and the valid + // lifetime from the last entry for this lease: 400 - 200 = 200. + Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:1::1", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(200, lease->cltt_); + + // The 2001:db8:1::1 should have user context. + ASSERT_TRUE(lease->getContext()); + EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str()); + + // The 3000:1:: lease should be present. + lease = getLease<Lease6Ptr>("3000:1::", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(100, lease->cltt_); + EXPECT_FALSE(lease->getContext()); + + // The 2001:db8:2::10 should be present and the cltt should be + // calculated according to the last entry in the lease file. + lease = getLease<Lease6Ptr>("2001:db8:2::10", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(500, lease->cltt_); + EXPECT_FALSE(lease->getContext()); + + test_str = v6_hdr_ + a_3 + b_2 + c_1; + writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str); + + // We should have made 3 attempts to write, with 3 leases written and 0 errors + { + SCOPED_TRACE("Write leases"); + checkStats(*lf, 0, 0, 0, 3, 3, 0); + } +} + +// This test verifies that the lease with a valid lifetime of 0 +// is removed from the storage. The valid lifetime of 0 set set +// for the released leases. +// +// It also tests the write function by writing the storage to a file +// and comparing that with the expected value. +TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) { + std::string test_str; + std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f," + "200,200,8,100,0,7,0,1,1,host.example.com,,1,,,\n"; + std::string a_2 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f," + "0,400,8,100,0,7,0,1,1,host.example.com,,1,,,\n"; + + std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05," + "300,300,6,150,0,8,0,0,0,,,1,,,\n"; + std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05," + "300,800,6,150,0,8,0,0,0,,,1,,,\n"; + + // Create lease file in which one of the entries for the 2001:db8:1::1 + // has valid lifetime set to 0, in which case the lease should be + // deleted. + test_str = v6_hdr_ + a_1 + b_1 + b_2 + a_2; + io_.writeFile(test_str); + + boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Loaded leases. + Lease6Storage storage; + ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10)); + + // We should have made 5 attempts to read, with 4 leases read and 0 error + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 5, 4, 0, 0, 0, 0); + } + + // There should be only one lease for 2001:db8:2::10. The other one + // should have been deleted (or rather not loaded). + ASSERT_EQ(1, storage.size()); + + Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:2::10", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(500, lease->cltt_); + + test_str = v6_hdr_ + b_2; + writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str); + + // We should have made 1 attempts to write, with 1 leases written and 0 errors + { + SCOPED_TRACE("Write leases"); + checkStats(*lf, 0, 0, 0, 1, 1, 0); + } +} + +// This test verifies that max-row-errors works correctly for +// DHCPv6 lease files +TEST_F(LeaseFileLoaderTest, maxRowErrors6) { + // We have 9 rows: 2 that are good, 7 that are flawed (too few fields). + std::vector<std::string> rows = { + "3002::01,00:03:00:01:08:00:27:25:d3:01,30,1565361388,2,20,0," + "11189196,128,0,0,,08:00:27:25:d3:f4,0,\n", + "3002::02,00:03:00:01:08:00:27:25:d3:02,30,1565361388,2,20,0\n", + "3002::03,00:03:00:01:08:00:27:25:d3:03,30,1565361388,2,20,0\n", + "3002::04,00:03:00:01:08:00:27:25:d3:04,30,1565361388,2,20,0\n", + "3002::05,00:03:00:01:08:00:27:25:d3:05,30,1565361388,2,20,0\n", + "3002::06,00:03:00:01:08:00:27:25:d3:06,30,1565361388,2,20,0\n", + "3002::07,00:03:00:01:08:00:27:25:d3:07,30,1565361388,2,20,0\n", + "3002::08,00:03:00:01:08:00:27:25:d3:08,30,1565361388,2,20,0\n", + "3002::09,00:03:00:01:08:00:27:25:d3:09,30,1565361388,2,20,0," + "11189196,128,0,0,,08:00:27:25:d3:f4,0,\n" + }; + + std::ostringstream os; + os << v6_hdr_; + for (auto row : rows) { + os << row; + } + + io_.writeFile(os.str()); + + boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Let's limit the number of errors to 5 (we have 7 in the data) and + // try to load the leases. + uint32_t max_errors = 5; + Lease6Storage storage; + ASSERT_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, max_errors), util::CSVFileError); + + // We should have made 7 reads, with 1 lease read, and 6 errors. + { + SCOPED_TRACE("Failed load stats"); + checkStats(*lf, 7, 1, 6, 0, 0, 0); + } + + // Now let's disable the error limit and try again. + max_errors = 0; + + // Load leases from the file. Note, we have to reopen the file. + ASSERT_NO_THROW(lf->open()); + ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, max_errors)); + + // We should have made 10 reads, with 2 leases read, and 7 errors. + { + SCOPED_TRACE("Good load stats"); + checkStats(*lf, 10, 2, 7, 0, 0, 0); + } +} + +// This test verifies that the lease with a valid lifetime set to 0 is +// not loaded if there are no previous entries for this lease in the +// lease file. +// +// It also tests the write function by writing the storage to a file +// and comparing that with the expected value. +TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) { + std::string test_str; + std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,,1,\n"; + std::string b_2 = "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,,1,\n"; + + // Create lease file. The second lease has a valid lifetime of 0. + test_str = v4_hdr_ + a_1 + b_2; + io_.writeFile(test_str); + + boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_)); + ASSERT_NO_THROW(lf->open()); + + // Set the error count to 0 to make sure that lease with a zero + // lifetime doesn't cause an error. + Lease4Storage storage; + ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 0)); + + // We should have made 3 attempts to read, with 2 leases read and 0 error + { + SCOPED_TRACE("Read leases"); + checkStats(*lf, 3, 2, 0, 0, 0, 0); + } + + // The first lease should be present. + Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // The lease with a valid lifetime of 0 should not be loaded. + EXPECT_FALSE(getLease<Lease4Ptr>("192.0.2.3", storage)); + + test_str = v4_hdr_ + a_1; + writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str); + + // We should have made 1 attempts to write, with 1 leases written and 0 errors + { + SCOPED_TRACE("Write leases"); + checkStats(*lf, 0, 0, 0, 1, 1, 0); + } +} + +// This test checks if the lease can be loaded, even though there are no +// subnets configured that it would match. +// Scenario: print a warning, there's no subnet, +// expected outcome: add the lease as is +TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsWarn) { + sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_WARN, true, 1); +} + +// This test checks if the lease can be fixed. +// Scenario: try to fix the lease, there's no subnet, +// expected outcome: add the lease as is +TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsFix) { + sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX, true, 1); +} + +// This test checks if the lease can be discarded if it's impossible to fix. +// Scenario: try to fix the lease, there's no subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsFixDel) { + sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1); +} + +// This test checks if the lease can be discarded. +// Scenario: try to fix the lease, there's no subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsDel) { + sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_DEL, false, 1); +} + +// This test checks if the lease can be fixed. +// Scenario: try to fix the lease, there's a subnet, +// expected outcome: correct the lease +TEST_F(LeaseFileLoaderTest, sanityChecker4Fix) { + sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_FIX, true, 2); +} + +// This test checks if the lease can be fixed when it's possible. +// Scenario: try to fix the lease, there's a subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker4FixDel1) { + sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, true, 2); +} + +// This test checks if the lease is discarded, when fix is not possible. +// Scenario: try to fix the lease, there's a subnet, but it doesn't match, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker4FixDel2) { + sanityChecks4("192.0.2.1", 1, "192.0.3.0/24", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1); +} + +// This test checks if the lease is discarded. +// Scenario: delete the lease, there's a subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker4Del) { + sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_DEL, false, 1); +} + +// This test checks if the lease can be loaded, even though there are no +// subnets configured that it would match. +TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsWarn) { + sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_WARN, true, 1); +} + +// This test checks if the lease can be fixed. +// Scenario: try to fix the lease, there's no subnet, +// expected outcome: add the lease as is +TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsFix) { + sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX, true, 1); +} + +// This test checks if the lease can be discarded if it's impossible to fix. +// Scenario: try to fix the lease, there's no subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsFixDel) { + sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1); +} + +// This test checks if the lease can be discarded. Note we are +// verifying whether the checks are conducted at all when loading a file. +// Thorough tests were conducted in sanity_checks_unittest.cc. +TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsDel) { + sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_DEL, false, 1); +} + +// This test checks if the lease can be fixed. +// Scenario: try to fix the lease, there's a subnet, +// expected outcome: correct the lease +TEST_F(LeaseFileLoaderTest, sanityChecker6Fix) { + sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_FIX, true, 2); +} + +// This test checks if the lease can be fixed when it's possible. +// Scenario: try to fix the lease, there's a subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker6FixDel1) { + sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, true, 2); +} + +// This test checks if the lease is discarded, when fix is not possible. +// Scenario: try to fix the lease, there's a subnet, but it doesn't match, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker6FixDel2) { + sanityChecks6("2001::1", 1, "2002::/16", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1); +} + +// This test checks if the lease is discarded. +// Scenario: delete the lease, there's a subnet, +// expected outcome: the lease is not loaded +TEST_F(LeaseFileLoaderTest, sanityChecker6Del) { + sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_DEL, false, 1); +} + +// This test checks to make sure PD leases are not sanity checked, +// and thus not discarded. +TEST_F(LeaseFileLoaderTest, sanityChecker6PD) { + int prefix_len = 64; + + // We check a prefix lease whose subnet-id does not exist and + // is clearly outside the only known subnet. + sanityChecks6("2001:1::", 2, // create prefix lease in subnet 2 + "3001::/64", 1, // create subnet 1 + CfgConsistency::LEASE_CHECK_DEL, + true, 2, // lease should still exist with subnet id of 2 + prefix_len); +} + +} // end of anonymous namespace |