diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc | 2807 |
1 files changed, 2807 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc new file mode 100644 index 0000000..a0986d9 --- /dev/null +++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc @@ -0,0 +1,2807 @@ +// Copyright (C) 2012-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/asio_wrapper.h> +#include <asiolink/interval_timer.h> +#include <asiolink/io_address.h> +#include <dhcp/duid.h> +#include <dhcp/iface_mgr.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/csv_lease_file4.h> +#include <dhcpsrv/csv_lease_file6.h> +#include <dhcpsrv/lease_mgr.h> +#include <dhcpsrv/lease_mgr_factory.h> +#include <dhcpsrv/memfile_lease_mgr.h> +#include <dhcpsrv/timer_mgr.h> +#include <dhcpsrv/testutils/lease_file_io.h> +#include <dhcpsrv/testutils/test_utils.h> +#include <dhcpsrv/tests/generic_lease_mgr_unittest.h> +#include <testutils/gtest_utils.h> +#include <util/multi_threading_mgr.h> +#include <util/pid_file.h> +#include <util/range_utilities.h> +#include <util/stopwatch.h> + +#include <gtest/gtest.h> + +#include <cstdlib> +#include <iostream> +#include <fstream> +#include <queue> +#include <sstream> + +#include <unistd.h> + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::db; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::util; + +namespace { + +/// @brief Class derived from @c Memfile_LeaseMgr to test LFC timer. +/// +/// This class provides a custom callback function which is invoked +/// when the timer for Lease File Cleanup goes off. It is used to +/// test that the timer is correctly installed. +class LFCMemfileLeaseMgr : public Memfile_LeaseMgr { +public: + + /// @brief Constructor. + /// + /// Sets the counter for callbacks to 0. + LFCMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters) + : Memfile_LeaseMgr(parameters), lfc_cnt_(0) { + } + + /// @brief Returns the number of callback executions. + int getLFCCount() { + return (lfc_cnt_); + } + +protected: + + /// @brief Custom callback. + /// + /// This callback function increases the counter of callback executions. + /// By examining the counter value a test may verify that the callback + /// was triggered an expected number of times. + virtual void lfcCallback() { + ++lfc_cnt_; + } + +private: + + /// @brief Counter of callback function executions. + int lfc_cnt_; + +}; + +/// @brief A derivation of the lease manager exposing protected methods. +class NakedMemfileLeaseMgr : public Memfile_LeaseMgr { +public: + + /// @brief Constructor. + /// + /// Creates instance of the lease manager. + NakedMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters) + : Memfile_LeaseMgr(parameters) { + } + + using Memfile_LeaseMgr::lfcCallback; +}; + +/// @brief Test fixture class for @c Memfile_LeaseMgr +class MemfileLeaseMgrTest : public GenericLeaseMgrTest { +public: + + /// @brief memfile lease mgr test constructor + /// + /// Creates memfile and stores it in lmptr_ pointer + MemfileLeaseMgrTest() : + io4_(getLeaseFilePath("leasefile4_0.csv")), + io6_(getLeaseFilePath("leasefile6_0.csv")), + io_service_(getIOService()), + timer_mgr_(TimerMgr::instance()) { + + timer_mgr_->setIOService(io_service_); + LeaseMgr::setIOService(io_service_); + + std::ostringstream s; + s << KEA_LFC_BUILD_DIR << "/kea-lfc"; + setenv("KEA_LFC_EXECUTABLE", s.str().c_str(), 1); + + // Remove lease files and products of Lease File Cleanup. + removeFiles(getLeaseFilePath("leasefile4_0.csv")); + removeFiles(getLeaseFilePath("leasefile6_0.csv")); + + MultiThreadingMgr::instance().setMode(false); + } + + /// @brief Reopens the connection to the backend. + /// + /// This function is called by unit tests to force reconnection of the + /// backend to check that the leases are stored and can be retrieved + /// from the storage. + /// + /// @param u Universe (V4 or V6) + virtual void reopen(Universe u) { + LeaseMgrFactory::destroy(); + startBackend(u); + } + + /// @brief destructor + /// + /// destroys lease manager backend. + virtual ~MemfileLeaseMgrTest() { + // Stop TimerMgr worker thread if it is running. + // Make sure there are no timers registered. + timer_mgr_->unregisterTimers(); + LeaseMgrFactory::destroy(); + // Remove lease files and products of Lease File Cleanup. + removeFiles(getLeaseFilePath("leasefile4_0.csv")); + removeFiles(getLeaseFilePath("leasefile6_0.csv")); + // Disable multi-threading. + MultiThreadingMgr::instance().setMode(false); + } + + /// @brief Remove files being products of Lease File Cleanup. + /// + /// @param base_name Path to the lease file name. This file is removed + /// and all files which names are created from this name (having specific + /// suffixes used by Lease File Cleanup mechanism). + void removeFiles(const std::string& base_name) const { + // Generate suffixes and append them to the base name. The + // resulting file names are the ones that may exist as a + // result of LFC. + for (int i = static_cast<int>(Memfile_LeaseMgr::FILE_CURRENT); + i <= static_cast<int>(Memfile_LeaseMgr::FILE_FINISH); + ++i) { + Memfile_LeaseMgr::LFCFileType type = static_cast< + Memfile_LeaseMgr::LFCFileType>(i); + LeaseFileIO io(Memfile_LeaseMgr::appendSuffix(base_name, type)); + io.removeFile(); + } + } + + /// @brief Return path to the lease file used by unit tests. + /// + /// @param filename Name of the lease file appended to the path to the + /// directory where test data is held. + /// + /// @return Full path to the lease file. + static std::string getLeaseFilePath(const std::string& filename) { + std::ostringstream s; + s << TEST_DATA_BUILDDIR << "/" << filename; + return (s.str()); + } + + /// @brief Returns the configuration string for the backend. + /// + /// This string configures the @c LeaseMgrFactory to create the memfile + /// backend and use leasefile4_0.csv and leasefile6_0.csv files as + /// storage for leases. + /// + /// @param no_universe Indicates whether universe parameter should be + /// included (false), or not (true). + /// + /// @return Configuration string for @c LeaseMgrFactory. + static std::string getConfigString(Universe u) { + std::ostringstream s; + s << "type=memfile " << (u == V4 ? "universe=4 " : "universe=6 ") + << "name=" + << getLeaseFilePath(u == V4 ? "leasefile4_0.csv" : "leasefile6_0.csv") + << " lfc-interval=0"; + return (s.str()); + } + + /// @brief Creates instance of the backend. + /// + /// @param u Universe (v4 or V6). + void startBackend(Universe u) { + try { + LeaseMgrFactory::create(getConfigString(u)); + } catch (...) { + std::cerr << "*** ERROR: unable to create instance of the Memfile" + " lease database backend.\n"; + throw; + } + lmptr_ = &(LeaseMgrFactory::instance()); + } + + /// @brief Runs IOService and stops after a specified time. + /// + /// @param ms Duration in milliseconds. + void setTestTime(const uint32_t ms) { + IntervalTimer timer(*io_service_); + timer.setup([this]() { + io_service_->stop(); + }, ms, IntervalTimer::ONE_SHOT); + + io_service_->run(); + io_service_->get_io_service().reset(); + } + + /// @brief Waits for the specified process to finish. + /// + /// @param process An object which started the process. + /// @param timeout Timeout in seconds. + /// + /// @return true if the process ended, false otherwise + bool waitForProcess(const Memfile_LeaseMgr& lease_mgr, + const uint8_t timeout) { + const uint32_t iterations_max = timeout * 1000; + IntervalTimer fast_path_timer(*io_service_); + IntervalTimer timer(*io_service_); + bool elapsed = false; + timer.setup([&]() { + io_service_->stop(); + elapsed = true; + }, iterations_max, IntervalTimer::ONE_SHOT); + + fast_path_timer.setup([&]() { + if (!lease_mgr.isLFCRunning()) { + io_service_->stop(); + } + }, 1, IntervalTimer::REPEATING); + + io_service_->run(); + io_service_->get_io_service().reset(); + return (!elapsed); + } + + /// @brief Single instance of IOService. + static asiolink::IOServicePtr getIOService() { + static asiolink::IOServicePtr io_service(new asiolink::IOService()); + return (io_service); + } + + /// @brief Generates a DHCPv4 lease with random content. + /// + /// The following lease parameters are randomly generated: + /// - HW address, + /// - client identifier, + /// - hostname, + /// - subnet identifier, + /// - client last transmission time, + /// + /// The following lease parameters are set to constant values: + /// - valid lifetime = 1200, + /// - DNS update forward flag = false, + /// - DNS update reverse flag = false, + /// + /// The lease address is set to address passed as function + /// argument. + /// + /// @param address Lease address. + /// + /// @return new lease with random content + Lease4Ptr initiateRandomLease4(const IOAddress& address) { + + // Randomize HW address. + vector<uint8_t> mac(6); + fillRandom(mac.begin(), mac.end()); + HWAddrPtr hwaddr(new HWAddr(mac, HTYPE_ETHER)); + + // Let's generate clientid of random length + vector<uint8_t> clientid(4 + random()%20); + // And then fill it with random value. + fillRandom(clientid.begin(), clientid.end()); + + uint32_t valid_lft = 1200; + time_t timestamp = time(NULL) - 86400 + random()%86400; + bool fqdn_fwd = false; + bool fqdn_rev = false; + uint32_t subnet_id = 1000 + random()%16; + + std::ostringstream hostname; + hostname << "hostname" << (random() % 2048); + + // Return created lease. + return (Lease4Ptr(new Lease4(address, hwaddr, &clientid[0], + clientid.size(), valid_lft, + timestamp, subnet_id, fqdn_fwd, + fqdn_rev, hostname.str()))); + } + + /// @brief Generates a DHCPv6 lease with random content. + /// + /// The following lease parameters are randomly generated: + /// - DUID, + /// - IAID, + /// - hostname, + /// - subnet identifier, + /// - client last transmission time, + /// + /// The following lease parameters are set to constant values: + /// - lease type = IA_NA + /// - valid lifetime = 1200, + /// - preferred lifetime = 1000 + /// - DNS update forward flag = false, + /// - DNS update reverse flag = false, + /// + /// The lease address is set to address passed as function + /// argument. + /// + /// @param address Lease address. + /// + /// @return new lease with random content + Lease6Ptr initiateRandomLease6(const IOAddress& address) { + // Let's generate DUID of random length. + std::vector<uint8_t> duid_vec(8 + random()%20); + // And then fill it with random value. + fillRandom(duid_vec.begin(), duid_vec.end()); + DuidPtr duid(new DUID(duid_vec)); + + Lease::Type lease_type = Lease::TYPE_NA; + uint32_t iaid = 1 + random()%100; + uint32_t valid_lft = 1200; + uint32_t preferred_lft = 1000; + time_t timestamp = time(NULL) - 86400 + random()%86400; + bool fqdn_fwd = false; + bool fqdn_rev = false; + uint32_t subnet_id = 1000 + random()%16; + + std::ostringstream hostname; + hostname << "hostname" << (random() % 2048); + + // Return created lease. + Lease6Ptr lease(new Lease6(lease_type, address, duid, iaid, + preferred_lft, valid_lft, + subnet_id, fqdn_fwd, fqdn_rev, + hostname.str())); + lease->cltt_ = timestamp; + return (lease); + } + + /// @brief Object providing access to v4 lease IO. + LeaseFileIO io4_; + + /// @brief Object providing access to v6 lease IO. + LeaseFileIO io6_; + + /// @brief Pointer to the IO service used by the tests. + IOServicePtr io_service_; + + /// @brief Pointer to the instance of the @c TimerMgr. + TimerMgrPtr timer_mgr_; +}; + +/// @brief This test checks if the LeaseMgr can be instantiated and that it +/// parses parameters string properly. +TEST_F(MemfileLeaseMgrTest, constructor) { + DatabaseConnection::ParameterMap pmap; + pmap["universe"] = "4"; + pmap["persist"] = "false"; + boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr; + + EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap))); + + pmap["lfc-interval"] = "10"; + pmap["persist"] = "true"; + pmap["name"] = getLeaseFilePath("leasefile4_1.csv"); + pmap["max-row-errors"] = "5"; + EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap))); + + // Expecting that persist parameter is yes or no. Everything other than + // that is wrong. + pmap["persist"] = "bogus"; + pmap["name"] = getLeaseFilePath("leasefile4_1.csv"); + EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue); + + // The lfc-interval must be an integer. + pmap["persist"] = "true"; + pmap["lfc-interval"] = "bogus"; + EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue); + + // The max-row-errors must be an integer. + pmap["persist"] = "true"; + pmap["max-row-errors"] = "bogus"; + EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue); + + // The max-row-errors must be >= 0. + pmap["persist"] = "true"; + pmap["max-row-errors"] = "-1"; + EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue); +} + +/// @brief Checks if there is no lease manager NoLeaseManager is thrown. +TEST_F(MemfileLeaseMgrTest, noLeaseManager) { + LeaseMgrFactory::destroy(); + EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager); +} + +/// @brief Checks if the getType() and getName() methods both return "memfile". +TEST_F(MemfileLeaseMgrTest, getTypeAndName) { + startBackend(V4); + EXPECT_EQ(std::string("memfile"), lmptr_->getType()); + EXPECT_EQ(std::string("memory"), lmptr_->getName()); +} + +/// @brief Checks if the path to the lease files is initialized correctly. +TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) { + // Initialize IO objects, so as the test csv files get removed after the + // test (when destructors are called). + LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv")); + LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv")); + + DatabaseConnection::ParameterMap pmap; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_1.csv"); + boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap)); + + EXPECT_EQ(pmap["name"], + lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4)); + + pmap["persist"] = "false"; + lease_mgr.reset(new Memfile_LeaseMgr(pmap)); + EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4).empty()); + EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V6).empty()); +} + +/// @brief Check if the persistLeases correctly checks that leases should not be written +/// to disk when disabled through configuration. +TEST_F(MemfileLeaseMgrTest, persistLeases) { + // Initialize IO objects, so as the test csv files get removed after the + // test (when destructors are called). + LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv")); + LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv")); + + DatabaseConnection::ParameterMap pmap; + pmap["universe"] = "4"; + pmap["lfc-interval"] = "0"; + // Specify the names of the lease files. Leases will be written. + pmap["name"] = getLeaseFilePath("leasefile4_1.csv"); + boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap)); + + lease_mgr.reset(new Memfile_LeaseMgr(pmap)); + EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4)); + EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6)); + + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_1.csv"); + lease_mgr.reset(new Memfile_LeaseMgr(pmap)); + EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4)); + EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6)); + + // This should disable writes of leases to disk. + pmap["persist"] = "false"; + lease_mgr.reset(new Memfile_LeaseMgr(pmap)); + EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4)); + EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6)); +} + +/// @brief Check if it is possible to schedule the timer to perform the Lease +/// File Cleanup periodically. +TEST_F(MemfileLeaseMgrTest, lfcTimer) { + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + // Specify the names of the lease files. Leases will be written. + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + pmap["lfc-interval"] = "1"; + + boost::scoped_ptr<LFCMemfileLeaseMgr> + lease_mgr(new LFCMemfileLeaseMgr(pmap)); + + // Run the test for at most 2.9 seconds. + setTestTime(2900); + + // Within 2.9 we should record two LFC executions. + EXPECT_EQ(2, lease_mgr->getLFCCount()); +} + +/// @brief This test checks if the LFC timer is disabled (doesn't trigger) +/// cleanups when the lfc-interval is set to 0. +TEST_F(MemfileLeaseMgrTest, lfcTimerDisabled) { + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + // Set the LFC interval to 0, which should effectively disable it. + pmap["lfc-interval"] = "0"; + + boost::scoped_ptr<LFCMemfileLeaseMgr> + lease_mgr(new LFCMemfileLeaseMgr(pmap)); + + // Run the test for at most 1.9 seconds. + setTestTime(1900); + + // There should be no LFC execution recorded. + EXPECT_EQ(0, lease_mgr->getLFCCount()); +} + +/// @brief This test checks that the callback function executing the cleanup of the +/// DHCPv4 lease file works as expected. +TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) { + // This string contains the lease file header, which matches + // the contents of the new file in which no leases have been + // stored. + std::string new_file_contents = + "address,hwaddr,client_id,valid_lifetime,expire," + "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context\n"; + + // This string contains the contents of the lease file with exactly + // one lease, but two entries. One of the entries should be removed + // as a result of lease file cleanup. + std::string current_file_contents = new_file_contents + + "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,{ \"foo\": true }\n" + "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv")); + current_file.writeFile(current_file_contents); + + std::string previous_file_contents = new_file_contents + + "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,\n" + "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1,{ \"bar\": true }\n"; + LeaseFileIO previous_file(getLeaseFilePath("leasefile4_0.csv.2")); + previous_file.writeFile(previous_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + pmap["lfc-interval"] = "1"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Try to run the lease file cleanup. + ASSERT_NO_THROW(lease_mgr->lfcCallback()); + + // The new lease file should have been created and it should contain + // no leases. + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(new_file_contents, current_file.readFile()); + + // Wait for the LFC process to complete. + ASSERT_TRUE(waitForProcess(*lease_mgr, 2)); + + // And make sure it has returned an exit status of 0. + EXPECT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // Check if we can still write to the lease file. + std::vector<uint8_t> hwaddr_vec(6); + HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER)); + Lease4Ptr new_lease(new Lease4(IOAddress("192.0.2.45"), hwaddr, + static_cast<const uint8_t*>(0), 0, + 100, 0, 1)); + ASSERT_NO_THROW(lease_mgr->addLease(new_lease)); + + std::string updated_file_contents = new_file_contents + + "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,,0,\n"; + EXPECT_EQ(updated_file_contents, current_file.readFile()); + + // This string contains the contents of the lease file we + // expect after the LFC run. It has two leases with one + // entry each. + std::string result_file_contents = new_file_contents + + "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1,\n" + "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1,{ \"bar\": true }\n"; + + // The LFC should have created a file with the two leases and moved it + // to leasefile4_0.csv.2 + LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.2"), false); + ASSERT_TRUE(input_file.exists()); + // And this file should contain the contents of the result file. + EXPECT_EQ(result_file_contents, input_file.readFile()); +} + +/// @brief This test checks that the callback function executing the cleanup of the +/// DHCPv6 lease file works as expected. +TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) { + // This string contains the lease file header, which matches + // the contents of the new file in which no leases have been + // stored. + std::string new_file_contents = + "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"; + + // This string contains the contents of the lease file with exactly + // one lease, but two entries. One of the entries should be removed + // as a result of lease file cleanup. + std::string current_file_contents = new_file_contents + + "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,,,1,,,\n" + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv")); + current_file.writeFile(current_file_contents); + + std::string previous_file_contents = new_file_contents + + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,200," + "8,100,0,7,0,1,1,,,1,{ \"bar\": true },,\n" + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,,1,,\n"; + LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2")); + previous_file.writeFile(previous_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_0.csv"); + pmap["lfc-interval"] = "1"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Try to run the lease file cleanup. + ASSERT_NO_THROW(lease_mgr->lfcCallback()); + + // The new lease file should have been created and it should contain + // no leases. + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(new_file_contents, current_file.readFile()); + + // Wait for the LFC process to complete. + ASSERT_TRUE(waitForProcess(*lease_mgr, 2)); + + // And make sure it has returned an exit status of 0. + EXPECT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // Check if we can still write to the lease file. + std::vector<uint8_t> duid_vec(13); + DuidPtr duid(new DUID(duid_vec)); + Lease6Ptr new_lease(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"), duid, + 123, 300, 400, 2)); + new_lease->cltt_ = 0; + ASSERT_NO_THROW(lease_mgr->addLease(new_lease)); + + std::string update_file_contents = new_file_contents + + "3000::1,00:00:00:00:00:00:00:00:00:00:00:00:00,400," + "400,2,300,0,123,128,0,0,,,0,,,\n"; + EXPECT_EQ(update_file_contents, current_file.readFile()); + + // This string contains the contents of the lease file we + // expect after the LFC run. It has two leases with one + // entry each. + std::string result_file_contents = new_file_contents + + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,\n" + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,,1,,,\n"; + + // The LFC should have created a file with the two leases and moved it + // to leasefile6_0.csv.2 + LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.2"), false); + ASSERT_TRUE(input_file.exists()); + // And this file should contain the contents of the result file. + EXPECT_EQ(result_file_contents, input_file.readFile()); +} + +/// @brief This test verifies that EXIT_FAILURE status code is returned when +/// the LFC process fails to start. +TEST_F(MemfileLeaseMgrTest, leaseFileCleanupStartFail) { + // This string contains the lease file header, which matches + // the contents of the new file in which no leases have been + // stored. + std::string new_file_contents = + "address,hwaddr,client_id,valid_lifetime,expire," + "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context\n"; + + // Create the lease file to be used by the backend. + std::string current_file_contents = new_file_contents + + "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv")); + current_file.writeFile(current_file_contents); + + // Specify invalid path to the kea-lfc executable. This should result + // in failure status code returned by the child process. + setenv("KEA_LFC_EXECUTABLE", "foobar", 1); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + pmap["lfc-interval"] = "1"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr; + ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)), ProcessSpawnError); +} + +/// @brief This test checks that the callback function executing the cleanup of the +/// files doesn't move the current file if the finish file exists +TEST_F(MemfileLeaseMgrTest, leaseFileFinish) { + // This string contains the lease file header, which matches + // the contents of the new file in which no leases have been + // stored. + std::string new_file_contents = + "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"; + + // This string contains the contents of the current lease file. + // It should not be moved. + std::string current_file_contents = new_file_contents + + "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,,,1,,,\n" + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,,1,,,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv")); + current_file.writeFile(current_file_contents); + + // This string contains the contents of the finish file. It should + // be moved to the previous file. + std::string finish_file_contents = new_file_contents + + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,,1,\n"; + LeaseFileIO finish_file(getLeaseFilePath("leasefile6_0.csv.completed")); + finish_file.writeFile(finish_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_0.csv"); + pmap["lfc-interval"] = "1"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Try to run the lease file cleanup. + ASSERT_NO_THROW(lease_mgr->lfcCallback()); + + // The current lease file should not have been touched. + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(current_file_contents, current_file.readFile()); + + // Wait for the LFC process to complete. + ASSERT_TRUE(waitForProcess(*lease_mgr, 5)); + + // And make sure it has returned an exit status of 0. + EXPECT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // The LFC should have moved the finish file to the previous file - + // leasefile6_0.csv.2 + LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"), false); + ASSERT_TRUE(previous_file.exists()); + // And this file should contain the contents of the finish file. + EXPECT_EQ(finish_file_contents, previous_file.readFile()); + + // The finish file should have been removed + ASSERT_FALSE(finish_file.exists()); +} + +/// @brief This test checks that the callback function executing the cleanup of the +/// files doesn't move the current file if the copy file exists +TEST_F(MemfileLeaseMgrTest, leaseFileCopy) { + // This string contains the lease file header, which matches + // the contents of the new file in which no leases have been + // stored. + std::string new_file_contents = + "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"; + + // This string contains the contents of the current lease file. + // It should not be moved. + std::string current_file_contents = new_file_contents + + "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,,,1,,,\n" + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv")); + current_file.writeFile(current_file_contents); + + // This string contains the contents of the copy file. It should + // be processed and moved to the previous file. As there is only + // one lease the processing should result in the previous file being + // the same. + std::string input_file_contents = new_file_contents + + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,\n"; + LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.1")); + input_file.writeFile(input_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_0.csv"); + pmap["lfc-interval"] = "1"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Try to run the lease file cleanup. + ASSERT_NO_THROW(lease_mgr->lfcCallback()); + + // The current lease file should not have been touched. + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(current_file_contents, current_file.readFile()); + + // Wait for the LFC process to complete. + ASSERT_TRUE(waitForProcess(*lease_mgr, 5)); + + // And make sure it has returned an exit status of 0. + EXPECT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // The LFC should have processed the lease and moved it to the previous + // file - leasefile6_0.csv.2 + LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"), false); + ASSERT_TRUE(previous_file.exists()); + // And this file should contain the contents of the copy file. + EXPECT_EQ(input_file_contents, previous_file.readFile()); + + // The input file should have been removed + ASSERT_FALSE(input_file.exists()); +} + +/// @brief Checks that adding/getting/deleting a Lease6 object works. +TEST_F(MemfileLeaseMgrTest, addGetDelete6) { + startBackend(V6); + testAddGetDelete6(); +} + +/// @brief Checks that adding/getting/deleting a Lease6 object works. +TEST_F(MemfileLeaseMgrTest, addGetDelete6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testAddGetDelete6(); +} + +/// @brief Basic Lease4 Checks +/// +/// Checks that the addLease, getLease4 (by address) and deleteLease (with an +/// IPv4 address) works. +TEST_F(MemfileLeaseMgrTest, basicLease4) { + startBackend(V4); + testBasicLease4(); +} + +/// @brief Basic Lease4 Checks +TEST_F(MemfileLeaseMgrTest, basicLease4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testBasicLease4(); +} + +/// @todo Write more memfile tests + +/// @brief Simple test about lease4 retrieval through client id method +TEST_F(MemfileLeaseMgrTest, getLease4ClientId) { + startBackend(V4); + testGetLease4ClientId(); +} + +/// @brief Simple test about lease4 retrieval through client id method +TEST_F(MemfileLeaseMgrTest, getLease4ClientIdMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4ClientId(); +} + +/// @brief Checks that lease4 retrieval client id is null is working +TEST_F(MemfileLeaseMgrTest, getLease4NullClientId) { + startBackend(V4); + testGetLease4NullClientId(); +} + +/// @brief Checks that lease4 retrieval client id is null is working +TEST_F(MemfileLeaseMgrTest, getLease4NullClientIdMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4NullClientId(); +} + +/// @brief Checks lease4 retrieval through HWAddr +TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1) { + startBackend(V4); + testGetLease4HWAddr1(); +} + +/// @brief Checks lease4 retrieval through HWAddr +TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4HWAddr1(); +} + +/// @brief Check GetLease4 methods - access by Hardware Address +/// +/// Adds leases to the database and checks that they can be accessed via +/// a combination of DUID and IAID. +TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2) { + startBackend(V4); + testGetLease4HWAddr2(); +} + +/// @brief Check GetLease4 methods - access by Hardware Address +TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4HWAddr2(); +} + +/// @brief Basic Lease4 Checks +/// +/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id), +/// updateLease4() and deleteLease can handle NULL client-id. +/// (client-id is optional and may not be present) +TEST_F(MemfileLeaseMgrTest, lease4NullClientId) { + startBackend(V4); + testLease4NullClientId(); +} + +/// @brief Basic Lease4 Checks +TEST_F(MemfileLeaseMgrTest, lease4NullClientIdMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testLease4NullClientId(); +} + +/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID +/// +/// Adds leases to the database and checks that they can be accessed via +/// a combination of hardware address and subnet ID +TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetId) { + /// @todo: fails on memfile. It's probably a memfile bug. + startBackend(V4); + testGetLease4HWAddrSubnetId(); +} + +/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID +TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetIdMultiThread) { + /// @todo: fails on memfile. It's probably a memfile bug. + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4HWAddrSubnetId(); +} + +/// @brief Check GetLease4 methods - access by Client ID +/// +/// Adds leases to the database and checks that they can be accessed via +/// the Client ID. +TEST_F(MemfileLeaseMgrTest, getLease4ClientId2) { + startBackend(V4); + testGetLease4ClientId2(); +} + +/// @brief Check GetLease4 methods - access by Client ID +TEST_F(MemfileLeaseMgrTest, getLease4ClientId2MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4ClientId2(); +} + +/// @brief Get Lease4 by client ID +/// +/// Check that the system can cope with a client ID of any size. +TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSize) { + startBackend(V4); + testGetLease4ClientIdSize(); +} + +/// @brief Get Lease4 by client ID +TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSizeMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4ClientIdSize(); +} + +/// @brief Check GetLease4 methods - access by Client ID & Subnet ID +/// +/// Adds leases to the database and checks that they can be accessed via +/// a combination of client and subnet IDs. +TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetId) { + startBackend(V4); + testGetLease4ClientIdSubnetId(); +} + +/// @brief Check GetLease4 methods - access by Client ID & Subnet ID +TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetIdMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLease4ClientIdSubnetId(); +} + +/// @brief This test checks that all IPv4 leases for a specified subnet id are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4SubnetId) { + startBackend(V4); + testGetLeases4SubnetId(); +} + +/// @brief This test checks that all IPv4 leases for a specified subnet id are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4SubnetIdMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLeases4SubnetId(); +} + +/// @brief This test checks that all IPv4 leases with a specified hostname are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4Hostname) { + startBackend(V4); + testGetLeases4Hostname(); +} + +/// @brief This test checks that all IPv4 leases with a specified hostname are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4HostnameMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLeases4Hostname(); +} + +/// @brief This test checks that all IPv4 leases are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4) { + startBackend(V4); + testGetLeases4(); +} + +/// @brief This test checks that all IPv4 leases are returned. +TEST_F(MemfileLeaseMgrTest, getLeases4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLeases4(); +} + +/// @brief Test that a range of IPv4 leases is returned with paging. +TEST_F(MemfileLeaseMgrTest, getLeases4Paged) { + startBackend(V4); + testGetLeases4Paged(); +} + +/// @brief Test that a range of IPv4 leases is returned with paging. +TEST_F(MemfileLeaseMgrTest, getLeases4PagedMultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetLeases4Paged(); +} + +/// @brief This test checks that all IPv6 leases for a specified subnet id are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6SubnetId) { + startBackend(V6); + testGetLeases6SubnetId(); +} + +/// @brief This test checks that all IPv6 leases for a specified subnet id are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6SubnetIdMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6SubnetId(); +} + +/// @brief This test checks that all IPv6 leases with a specified hostname are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6Hostname) { + startBackend(V6); + testGetLeases6Hostname(); +} + +/// @brief This test checks that all IPv6 leases with a specified hostname are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6HostnameMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6Hostname(); +} + +/// @brief This test adds 3 leases and verifies fetch by DUID. +/// Verifies retrieval of non existant DUID fails +TEST_F(MemfileLeaseMgrTest, getLeases6Duid) { + startBackend(V6); + testGetLeases6Duid(); +} + +/// @brief This test adds 3 leases and verifies fetch by DUID. +TEST_F(MemfileLeaseMgrTest, getLeases6DuidMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6Duid(); +} + +/// @brief This test checks that all IPv6 leases are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6) { + startBackend(V6); + testGetLeases6(); +} + +/// @brief This test checks that all IPv6 leases are returned. +TEST_F(MemfileLeaseMgrTest, getLeases6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6(); +} + +/// @brief Test that a range of IPv6 leases is returned with paging. +TEST_F(MemfileLeaseMgrTest, getLeases6Paged) { + startBackend(V6); + testGetLeases6Paged(); +} + +/// @brief Test that a range of IPv6 leases is returned with paging. +TEST_F(MemfileLeaseMgrTest, getLeases6PagedMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6Paged(); +} + +/// @brief Basic Lease6 Checks +/// +/// Checks that the addLease, getLease6 (by address) and deleteLease (with an +/// IPv6 address) works. +TEST_F(MemfileLeaseMgrTest, basicLease6) { + startBackend(V6); + testBasicLease6(); +} + +/// @brief Basic Lease6 Checks +TEST_F(MemfileLeaseMgrTest, basicLease6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testBasicLease6(); +} + +/// @brief Check GetLease6 methods - access by DUID/IAID +/// +/// Adds leases to the database and checks that they can be accessed via +/// a combination of DUID and IAID. +/// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type, +/// const DUID& duid, uint32_t iaid) const is not implemented yet. +TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaid) { + startBackend(V6); + testGetLeases6DuidIaid(); +} + +/// @brief Check GetLease6 methods - access by DUID/IAID +TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaidMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6DuidIaid(); +} + +/// @brief Check that the system can cope with a DUID of allowed size. +TEST_F(MemfileLeaseMgrTest, getLeases6DuidSize) { + startBackend(V6); + testGetLeases6DuidSize(); +} + +/// @brief Check that the system can cope with a DUID of allowed size. +TEST_F(MemfileLeaseMgrTest, getLeases6DuidSizeMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLeases6DuidSize(); +} + +/// @brief Check that the expired DHCPv4 leases can be retrieved. +/// +/// This test adds a number of leases to the lease database and marks +/// some of them as expired. Then it queries for expired leases and checks +/// whether only expired leases are returned, and that they are returned in +/// the order from most to least expired. It also checks that the lease +/// which is marked as 'reclaimed' is not returned. +TEST_F(MemfileLeaseMgrTest, getExpiredLeases4) { + startBackend(V4); + testGetExpiredLeases4(); +} + +/// @brief Check that the expired DHCPv4 leases can be retrieved. +TEST_F(MemfileLeaseMgrTest, getExpiredLeases4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetExpiredLeases4(); +} + +/// @brief Check that the expired DHCPv6 leases can be retrieved. +/// +/// This test adds a number of leases to the lease database and marks +/// some of them as expired. Then it queries for expired leases and checks +/// whether only expired leases are returned, and that they are returned in +/// the order from most to least expired. It also checks that the lease +/// which is marked as 'reclaimed' is not returned. +TEST_F(MemfileLeaseMgrTest, getExpiredLeases6) { + startBackend(V6); + testGetExpiredLeases6(); +} + +/// @brief Check that the expired DHCPv6 leases can be retrieved. +TEST_F(MemfileLeaseMgrTest, getExpiredLeases6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetExpiredLeases6(); +} + +/// @brief Check that expired reclaimed DHCPv6 leases are removed. +TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases6) { + startBackend(V6); + testDeleteExpiredReclaimedLeases6(); +} + +/// @brief Check that expired reclaimed DHCPv6 leases are removed. +TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testDeleteExpiredReclaimedLeases6(); +} + +/// @brief Check that expired reclaimed DHCPv4 leases are removed. +TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases4) { + startBackend(V4); + testDeleteExpiredReclaimedLeases4(); +} + +/// @brief Check that expired reclaimed DHCPv4 leases are removed. +TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testDeleteExpiredReclaimedLeases4(); +} + +/// @brief Check that getLease6 methods discriminate by lease type. +/// +/// Adds six leases, two per lease type all with the same duid and iad but +/// with alternating subnet_ids. +/// It then verifies that all of getLeases6() method variants correctly +/// discriminate between the leases based on lease type alone. +/// @todo: Disabled, because type parameter in Memfile_LeaseMgr::getLease6 +/// (Lease::Type, const isc::asiolink::IOAddress& addr) const is not used. +TEST_F(MemfileLeaseMgrTest, lease6LeaseTypeCheck) { + startBackend(V6); + testLease6LeaseTypeCheck(); +} + +/// @brief Check that getLease6 methods discriminate by lease type. +TEST_F(MemfileLeaseMgrTest, lease6LeaseTypeCheckMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testLease6LeaseTypeCheck(); +} + +/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID +/// +/// Adds leases to the database and checks that they can be accessed via +/// a combination of DIUID and IAID. +TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetId) { + startBackend(V6); + testGetLease6DuidIaidSubnetId(); +} + +/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID +TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLease6DuidIaidSubnetId(); +} + +/// @brief Checks that getLease6(type, duid, iaid, subnet-id) works with different +/// DUID sizes +TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSize) { + startBackend(V6); + testGetLease6DuidIaidSubnetIdSize(); +} + +/// @brief Checks that getLease6(type, duid, iaid, subnet-id) works with different +/// DUID sizes +TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSizeMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetLease6DuidIaidSubnetIdSize(); +} + +/// @brief Lease4 update tests +/// +/// Checks that we are able to update a lease in the database. +/// @todo: Disabled, because memfile does not throw when lease is updated. +/// We should reconsider if lease{4,6} structures should have a limit +/// implemented in them. +TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4) { + startBackend(V4); + testUpdateLease4(); +} + +/// @brief Lease4 update tests +TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testUpdateLease4(); +} + +/// @brief Lease6 update tests +/// +/// Checks that we are able to update a lease in the database. +/// @todo: Disabled, because memfile does not throw when lease is updated. +/// We should reconsider if lease{4,6} structures should have a limit +/// implemented in them. +TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6) { + startBackend(V6); + testUpdateLease6(); +} + +/// @brief Lease6 update tests +TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testUpdateLease6(); +} + +/// @brief DHCPv4 Lease recreation tests +/// +/// Checks that the lease can be created, deleted and recreated with +/// different parameters. It also checks that the re-created lease is +/// correctly stored in the lease database. +TEST_F(MemfileLeaseMgrTest, testRecreateLease4) { + startBackend(V4); + testRecreateLease4(); +} + +/// @brief DHCPv4 Lease recreation tests +TEST_F(MemfileLeaseMgrTest, testRecreateLease4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testRecreateLease4(); +} + +/// @brief DHCPv6 Lease recreation tests +/// +/// Checks that the lease can be created, deleted and recreated with +/// different parameters. It also checks that the re-created lease is +/// correctly stored in the lease database. +TEST_F(MemfileLeaseMgrTest, testRecreateLease6) { + startBackend(V6); + testRecreateLease6(); +} + +/// @brief DHCPv6 Lease recreation tests +TEST_F(MemfileLeaseMgrTest, testRecreateLease6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testRecreateLease6(); +} + +// The following tests are not applicable for memfile. When adding +// new tests to the list here, make sure to provide brief explanation +// why they are not applicable: +// +// testGetLease4HWAddrSubnetIdSize() - memfile just keeps Lease structure +// and does not do any checks of HWAddr content + +/// @brief Checks that null DUID is not allowed. +/// Test is disabled as Memfile does not currently defend against a null DUID. +TEST_F(MemfileLeaseMgrTest, DISABLED_nullDuid) { + // Create leases, although we need only one. + vector<Lease6Ptr> leases = createLeases6(); + + leases[1]->duid_.reset(); + ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); +} + +/// @brief Checks that null DUID is not allowed. +TEST_F(MemfileLeaseMgrTest, DISABLED_nullDuidMultiThread) { + MultiThreadingMgr::instance().setMode(true); + // Create leases, although we need only one. + vector<Lease6Ptr> leases = createLeases6(); + + leases[1]->duid_.reset(); + ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); +} + +/// @brief Tests whether memfile can store and retrieve hardware addresses +TEST_F(MemfileLeaseMgrTest, testLease6Mac) { + startBackend(V6); + testLease6MAC(); +} + +/// @brief Tests whether memfile can store and retrieve hardware addresses +TEST_F(MemfileLeaseMgrTest, testLease6MacMultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testLease6MAC(); +} + +/// @brief Check that memfile reports version correctly. +TEST_F(MemfileLeaseMgrTest, versionCheck) { + // Check that V4 backend reports versions correctly. + startBackend(V4); + testVersion(Memfile_LeaseMgr::MAJOR_VERSION_V4, + Memfile_LeaseMgr::MINOR_VERSION_V4); + LeaseMgrFactory::destroy(); + + // Check that V6 backends reports them ok, too. + startBackend(V6); + testVersion(Memfile_LeaseMgr::MAJOR_VERSION_V6, + Memfile_LeaseMgr::MINOR_VERSION_V6); + LeaseMgrFactory::destroy(); +} + +/// @brief Checks that declined IPv4 leases can be returned correctly. +TEST_F(MemfileLeaseMgrTest, getDeclined4) { + startBackend(V4); + testGetDeclinedLeases4(); +} + +/// @brief Checks that declined IPv4 leases can be returned correctly. +TEST_F(MemfileLeaseMgrTest, getDeclined4MultiThread) { + startBackend(V4); + MultiThreadingMgr::instance().setMode(true); + testGetDeclinedLeases4(); +} + +/// @brief Checks that declined IPv6 leases can be returned correctly. +TEST_F(MemfileLeaseMgrTest, getDeclined6) { + startBackend(V6); + testGetDeclinedLeases6(); +} + +/// @brief Checks that declined IPv6 leases can be returned correctly. +TEST_F(MemfileLeaseMgrTest, getDeclined6MultiThread) { + startBackend(V6); + MultiThreadingMgr::instance().setMode(true); + testGetDeclinedLeases6(); +} + +/// @brief This test checks that the backend reads DHCPv4 lease data from multiple +/// files. +TEST_F(MemfileLeaseMgrTest, load4MultipleLeaseFiles) { + LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2")); + io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,\n" + "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1,\n"); + + LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1")); + io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1,\n" + "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1,\n" + "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv")); + io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1,\n" + "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1,\n"); + + startBackend(V4); + + // This lease only exists in the second file and the cltt should + // be 0. + Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.1")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // This lease only exists in the first file and the cltt should + // be 0. + lease = lmptr_->getLease4(IOAddress("192.0.2.2")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // This lease only exists in the third file and the cltt should + // be 0. + lease = lmptr_->getLease4(IOAddress("192.0.2.10")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // This lease exists in the first and second file and the cltt + // should be calculated using the expiration time and the + // valid lifetime from the second file. + lease = lmptr_->getLease4(IOAddress("192.0.2.11")); + ASSERT_TRUE(lease); + EXPECT_EQ(200, lease->cltt_); + + // This lease exists in the second and third file and the cltt + // should be calculated using the expiration time and the + // valid lifetime from the third file. + lease = lmptr_->getLease4(IOAddress("192.0.2.12")); + ASSERT_TRUE(lease); + EXPECT_EQ(200, lease->cltt_); +} + +/// @brief This test checks that the lease database backend loads the file with +/// the .completed postfix instead of files with postfixes .1 and .2 if +/// the file with .completed postfix exists. +TEST_F(MemfileLeaseMgrTest, load4CompletedFile) { + LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2")); + io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,\n" + "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1,\n"); + + LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1")); + io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1,\n" + "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1,\n" + "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv")); + io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1,\n" + "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1,\n"); + + LeaseFileIO ioc(getLeaseFilePath("leasefile4_0.csv.completed")); + ioc.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + "192.0.2.13,ff:ff:ff:ff:ff:ff,,200,200,8,1,1,,1,\n"); + + startBackend(V4); + + // We expect that this file only holds leases that belong to the + // lease file or to the file with .completed postfix. + Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.10")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + lease = lmptr_->getLease4(IOAddress("192.0.2.12")); + ASSERT_TRUE(lease); + EXPECT_EQ(200, lease->cltt_); + + // This lease is in the .completed file. + lease = lmptr_->getLease4(IOAddress("192.0.2.13")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // Leases from the .1 and .2 files should not be loaded. + EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.11"))); + EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.1"))); +} + +/// @brief This test checks that backend constructor refuses to load leases from the +/// lease files if the LFC is in progress. +TEST_F(MemfileLeaseMgrTest, load4LFCInProgress) { + // Create the backend configuration. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + pmap["lfc-interval"] = "1"; + + // Create a pid file holding the PID of the current process. Choosing the + // pid of the current process guarantees that when the backend starts up + // the process is alive. + PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID)); + pid_file.write(); + + // There is a pid file and the process which pid is in the file is + // running, so the backend should refuse to start. + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr; + ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)), + DbOpenError); + + // Remove the pid file, and retry. The backend should be created. + pid_file.deleteFile(); + ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap))); +} + +/// @brief This test checks that the backend reads DHCPv6 lease data from multiple +/// files. +TEST_F(MemfileLeaseMgrTest, load6MultipleLeaseFiles) { + LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2")); + io2.writeFile("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" + "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1")); + io1.writeFile("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" + "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "300,800,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + io.writeFile("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" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "400,1000,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + startBackend(V6); + + // This lease only exists in the first file and the cltt should be 0. + Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, + IOAddress("2001:db8:1::1")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // This lease exists in the first and second file and the cltt should + // be calculated using the expiration time and the valid lifetime + // from the second file. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")); + ASSERT_TRUE(lease); + EXPECT_EQ(500, lease->cltt_); + + // This lease only exists in the second file and the cltt should be 0. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // This lease exists in the second and third file and the cltt should + // be calculated using the expiration time and the valid lifetime + // from the third file. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4")); + ASSERT_TRUE(lease); + EXPECT_EQ(600, lease->cltt_); + + // This lease only exists in the third file and the cltt should be 0. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); +} + +/// @brief This test checks that the backend reads DHCPv6 lease data from the +/// leasefile without the postfix and the file with a .1 postfix when +/// the file with the .2 postfix is missing. +TEST_F(MemfileLeaseMgrTest, load6MultipleNoSecondFile) { + LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1")); + io1.writeFile("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" + "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "300,800,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + io.writeFile("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" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "400,1000,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + startBackend(V6); + + // Check that leases from the leasefile6_0 and leasefile6_0.1 have + // been loaded. + Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")); + ASSERT_TRUE(lease); + EXPECT_EQ(500, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4")); + ASSERT_TRUE(lease); + EXPECT_EQ(600, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // Make sure that a lease which is not in those files is not loaded. + EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"))); +} + +/// @brief This test checks that the backend reads DHCPv6 lease data from the +/// leasefile without the postfix and the file with a .2 postfix when +/// the file with the .1 postfix is missing. +TEST_F(MemfileLeaseMgrTest, load6MultipleNoFirstFile) { + LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2")); + io2.writeFile("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" + "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + io.writeFile("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" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "400,1000,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + startBackend(V6); + + // Verify that leases which belong to the leasefile6_0.csv and + // leasefile6_0.2 are loaded. + Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, + IOAddress("2001:db8:1::1")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4")); + ASSERT_TRUE(lease); + EXPECT_EQ(600, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + // A lease which doesn't belong to these files should not be loaded. + EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"))); +} + +/// @brief This test checks that the lease database backend loads the file with +/// the .completed postfix instead of files with postfixes .1 and .2 if +/// the file with .completed postfix exists. +TEST_F(MemfileLeaseMgrTest, load6CompletedFile) { + LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2")); + io2.writeFile("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" + "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1")); + io1.writeFile("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" + "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03," + "200,200,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "300,800,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + io.writeFile("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" + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "400,1000,8,100,0,7,0,1,1,,,1,,,\n" + "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05," + "200,200,8,100,0,7,0,1,1,,,1,,,\n"); + + LeaseFileIO ioc(getLeaseFilePath("leasefile6_0.csv.completed")); + ioc.writeFile("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" + "2001:db8:1::125,ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff," + "400,1000,8,100,0,7,0,1,1,,,1,,,\n"); + + startBackend(V6); + + // We expect that this file only holds leases that belong to the + // lease file or to the file with .completed postfix. + Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4")); + ASSERT_TRUE(lease); + EXPECT_EQ(600, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5")); + ASSERT_TRUE(lease); + EXPECT_EQ(0, lease->cltt_); + + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::125")); + ASSERT_TRUE(lease); + EXPECT_EQ(600, lease->cltt_); + + // Leases from the .1 and .2 files should not be loaded. + EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"))); + EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"))); + EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"))); +} + +/// @brief This test checks that backend constructor refuses to load leases from the +/// lease files if the LFC is in progress. +TEST_F(MemfileLeaseMgrTest, load6LFCInProgress) { + // Create the backend configuration. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_0.csv"); + pmap["lfc-interval"] = "1"; + + // Create a pid file holding the PID of the current process. Choosing the + // pid of the current process guarantees that when the backend starts up + // the process is alive. + PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID)); + pid_file.write(); + + // There is a pid file and the process which pid is in the file is + // running, so the backend should refuse to start. + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr; + ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)), + DbOpenError); + + // Remove the pid file, and retry. The backend should be created. + pid_file.deleteFile(); + ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap))); +} + +/// @brief Verifies that LFC is automatically run during MemfileLeasemMgr construction +/// when the lease file(s) being loaded need to be upgraded. +TEST_F(MemfileLeaseMgrTest, leaseUpgrade4) { + // Create header strings for each schema + std::string header_1_0 = + "address,hwaddr,client_id,valid_lifetime,expire," + "subnet_id,fqdn_fwd,fqdn_rev,hostname\n"; + + std::string header_2_0 = + "address,hwaddr,client_id,valid_lifetime,expire," + "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context\n"; + + // Create 1.0 Schema current lease file with two entries for + // the same lease + std::string current_file_contents = header_1_0 + + "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,\n" + "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv")); + current_file.writeFile(current_file_contents); + + // Create 1.0 Schema previous lease file, with two entries for + // a another lease + std::string previous_file_contents = header_1_0 + + "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,\n" + "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,\n"; + LeaseFileIO previous_file(getLeaseFilePath("leasefile4_0.csv.2")); + previous_file.writeFile(previous_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "4"; + pmap["name"] = getLeaseFilePath("leasefile4_0.csv"); + pmap["lfc-interval"] = "0"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Since lease files are loaded during lease manager + // constructor, LFC should get launched automatically. + // The new lease file should be 2.0 schema and have no entries + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(header_2_0, current_file.readFile()); + + // Wait for the LFC process to complete and + // make sure it has returned an exit status of 0. + ASSERT_TRUE(waitForProcess(*lease_mgr, 2)); + + ASSERT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // The LFC should have created a 2.0 schema completion file with the + // one entry for each lease and moved it to leasefile4_0.csv.2 + LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.2"), false); + ASSERT_TRUE(input_file.exists()); + + // Verify cleaned, converted contents + std::string result_file_contents = header_2_0 + + "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,0,\n" + "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,0,\n"; + EXPECT_EQ(result_file_contents, input_file.readFile()); +} + +/// @brief Verifies that LFC is automatically run during MemfileLeasemMgr construction +/// when the lease file(s) being loaded need to be upgraded. +TEST_F(MemfileLeaseMgrTest, leaseUpgrade6) { + // Create header strings for all schemas. + std::string header_1_0 = + "address,duid,valid_lifetime,expire,subnet_id," + "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd," + "fqdn_rev,hostname\n"; + + std::string header_2_0 = + "address,duid,valid_lifetime,expire,subnet_id," + "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd," + "fqdn_rev,hostname,hwaddr\n"; + + std::string header_3_0 = + "address,duid,valid_lifetime,expire,subnet_id," + "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd," + "fqdn_rev,hostname,hwaddr,state,user_context\n"; + + std::string header_4_0 = + "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"; + + // The current lease file is schema 1.0 and has two entries for + // the same lease + std::string current_file_contents = header_1_0 + + "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,,\n" + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,\n"; + LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv")); + current_file.writeFile(current_file_contents); + + // The previous lease file is schema 2.0 and has two entries for + // a different lease + std::string previous_file_contents = header_2_0 + + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,200," + "8,100,0,7,0,1,1,,11:22:33:44:55\n" + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,11:22:33:44:55\n"; + LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2")); + previous_file.writeFile(previous_file_contents); + + // Create the backend. + DatabaseConnection::ParameterMap pmap; + pmap["type"] = "memfile"; + pmap["universe"] = "6"; + pmap["name"] = getLeaseFilePath("leasefile6_0.csv"); + pmap["lfc-interval"] = "0"; + boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap)); + + // Since lease files are loaded during lease manager + // constructor, LFC should get launched automatically. + // The new lease file should been 4.0 and contain no leases. + ASSERT_TRUE(current_file.exists()); + EXPECT_EQ(header_4_0, current_file.readFile()); + + // Wait for the LFC process to complete and + // make sure it has returned an exit status of 0. + ASSERT_TRUE(waitForProcess(*lease_mgr, 2)); + + ASSERT_EQ(0, lease_mgr->getLFCExitStatus()) + << "Executing the LFC process failed: make sure that" + " the kea-lfc program has been compiled."; + + // The LFC should have created a 4.0 schema cleaned file with one entry + // for each lease as leasefile6_0.csv.2 + LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.2"), false); + ASSERT_TRUE(input_file.exists()); + + // Verify cleaned, converted contents + std::string result_file_contents = header_4_0 + + "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800," + "8,100,0,7,0,1,1,,,0,,,\n" + "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800," + "8,100,0,7,0,1,1,,11:22:33:44:55,0,,1,0\n"; + EXPECT_EQ(result_file_contents, input_file.readFile()); +} + +/// @brief This test verifies that the indexes of the container holding +/// DHCPv4 leases are updated correctly when a lease is updated. +TEST_F(MemfileLeaseMgrTest, lease4ContainerIndexUpdate) { + + const uint32_t seed = 12345678; // Used to initialize the random generator + const uint32_t leases_cnt = 100; // Number of leases generated per round. + const uint32_t updates_cnt = 5; // Number of times existing leases are updated + + const string leasefile(getLeaseFilePath("leasefile4_0.csv")); + + // Parameters for the lease file. Make sure the leases are persistent, so they + // are written to disk. + DatabaseConnection::ParameterMap pmap; + pmap["universe"] = "4"; + pmap["name"] = leasefile; + pmap["persist"] = "true"; + pmap["lfc-interval"] = "0"; + + srand(seed); + + IOAddress addr("10.0.0.1"); // Let's generate leases sequentially + + // Recreate Memfile_LeaseMgr. + LeaseMgrFactory::destroy(); + ASSERT_NO_THROW(lmptr_ = new Memfile_LeaseMgr(pmap)); + + // We will store addresses here, so it will be easier to randomly + // pick a lease. + std::vector<IOAddress> lease_addresses; + + // Generate random leases. We remember their addresses in + // lease_addresses. + for (uint32_t i = 0; i < leases_cnt; ++i) { + Lease4Ptr lease = initiateRandomLease4(addr); + lease_addresses.push_back(addr); + ASSERT_NO_THROW(lmptr_->addLease(lease)); + addr = IOAddress::increase(addr); + } + + // Check that we inserted correct number of leases. + ASSERT_EQ(leases_cnt, lease_addresses.size()); + + // Now, conduct updates. We call initiateRandomLease4(), so most + // of the fields are randomly changed. The only constant field + // is the address. + for (uint32_t i = 0; i < updates_cnt; ++i) { + uint32_t offset = random() % lease_addresses.size(); + Lease4Ptr existing(lmptr_->getLease4(lease_addresses[offset])); + Lease4Ptr updated(initiateRandomLease4(lease_addresses[offset])); + + // Update a lease with new data but preserve lease address. + // This update should also cause lease container indexes to + // be updated. + ASSERT_NO_THROW(lmptr_->updateLease4(updated)) + << "Attempt " << i << " out of " << updates_cnt + << ":Failed to update lease for address " + << lease_addresses[offset]; + } + + // Re-create lease manager to cause it to reload leases + // from a lease file. We want to make sure that lease + // container is rebuilt correctly and the indexes are + // consistent with lease information held. + ASSERT_NO_THROW({ + LeaseMgrFactory::destroy(); + lmptr_ = new Memfile_LeaseMgr(pmap); + }); + + // Ok, let's check if the leases are really accessible. + // First, build an array of leases. Get them by address. + // This should work in general, as we haven't updated the addresses. + std::vector<Lease4Ptr> leases; + for (uint32_t i = 0; i < lease_addresses.size(); ++i) { + Lease4Ptr from_mgr = lmptr_->getLease4(lease_addresses[i]); + ASSERT_TRUE(from_mgr) << "Lease for address " << lease_addresses[i].toText() + << " not found"; + leases.push_back(from_mgr); + } + + ASSERT_EQ(leases_cnt, leases.size()); + + // Now do the actual checks. + for (uint32_t i = 0; i < leases.size(); ++i) { + Lease4Ptr tested = leases[i]; + + // Get the lease by different access patterns. + // In properly working lease manager all queries should return + // exactly the same lease. + + std::string error_desc = " which indicates that the lease indexes were" + " not updated correctly when the lease was updated."; + + // Retrieve lease by address. + Lease4Ptr lease_by_address = lmptr_->getLease4(tested->addr_); + ASSERT_TRUE(lease_by_address) + << "Lease " << tested->addr_.toText() + << " not found by getLease4(addr)" + << error_desc; + detailCompareLease(tested, lease_by_address); + + // Retrieve lease by HW address and subnet id. + Lease4Ptr lease_by_hwaddr_subnet = lmptr_->getLease4(*tested->hwaddr_, + tested->subnet_id_); + ASSERT_TRUE(lease_by_hwaddr_subnet) + << "Lease " << tested->addr_.toText() + << " not found by getLease4(hwaddr, subnet_id)" + << error_desc; + detailCompareLease(tested, lease_by_hwaddr_subnet); + + // Retrieve lease by client identifier and subnet id. + Lease4Ptr lease_by_clientid_subnet = lmptr_->getLease4(*tested->client_id_, + tested->subnet_id_); + ASSERT_TRUE(lease_by_clientid_subnet) + << "Lease " << tested->addr_.toText() + << " not found by getLease4(clientid, subnet_id)" + << error_desc; + detailCompareLease(tested, lease_by_clientid_subnet); + + // Retrieve lease by HW address. + Lease4Collection leases_by_hwaddr = lmptr_->getLease4(*tested->hwaddr_); + ASSERT_EQ(1, leases_by_hwaddr.size()); + detailCompareLease(tested, leases_by_hwaddr[0]); + + // Retrieve lease by client identifier. + Lease4Collection leases_by_client_id = lmptr_->getLease4(*tested->client_id_); + ASSERT_EQ(1, leases_by_client_id.size()); + detailCompareLease(tested, leases_by_client_id[0]); + } +} + +/// @brief This test verifies that the indexes of the container holding +/// DHCPv4 leases are updated correctly when a lease is updated. +TEST_F(MemfileLeaseMgrTest, lease6ContainerIndexUpdate) { + + const uint32_t seed = 12345678; // Used to initialize the random generator + const uint32_t leases_cnt = 100; // Number of leases generated per round. + const uint32_t updates_cnt = 5; // Number of times existing leases are updated + + const string leasefile(getLeaseFilePath("leasefile6_0.csv")); + + // Parameters for the lease file. Make sure the leases are persistent, so they + // are written to disk. + DatabaseConnection::ParameterMap pmap; + pmap["universe"] = "6"; + pmap["name"] = leasefile; + pmap["persist"] = "true"; + pmap["lfc-interval"] = "0"; + + srand(seed); + + IOAddress addr("2001:db8:1::1"); // Let's generate leases sequentially + + // Recreate Memfile_LeaseMgr. + LeaseMgrFactory::destroy(); + ASSERT_NO_THROW(lmptr_ = new Memfile_LeaseMgr(pmap)); + + // We will store addresses here, so it will be easier to randomly + // pick a lease. + std::vector<IOAddress> lease_addresses; + + // Generate random leases. We remember their addresses in + // lease_addresses. + for (uint32_t i = 0; i < leases_cnt; ++i) { + Lease6Ptr lease = initiateRandomLease6(addr); + lease_addresses.push_back(addr); + ASSERT_NO_THROW(lmptr_->addLease(lease)); + addr = IOAddress::increase(addr); + } + + // Check that we inserted correct number of leases. + ASSERT_EQ(leases_cnt, lease_addresses.size()); + + // Now, conduct updates. We call initiateRandomLease6(), so most + // of the fields are randomly changed. The only constant field + // is the address. + for (uint32_t i = 0; i < updates_cnt; ++i) { + uint32_t offset = random() % lease_addresses.size(); + Lease6Ptr existing(lmptr_->getLease6(Lease::TYPE_NA, + lease_addresses[offset])); + Lease6Ptr updated(initiateRandomLease6(lease_addresses[offset])); + + // Update a lease with new data but preserve lease address. + // This update should also cause lease container indexes to + // be updated. + ASSERT_NO_THROW(lmptr_->updateLease6(updated)) + << "Attempt " << i << " out of " << updates_cnt + << ":Failed to update lease for address " + << lease_addresses[offset]; + } + + // Re-create lease manager to cause it to reload leases + // from a lease file. We want to make sure that lease + // container is rebuilt correctly and the indexes are + // consistent with lease information held. + ASSERT_NO_THROW({ + LeaseMgrFactory::destroy(); + lmptr_ = new Memfile_LeaseMgr(pmap); + }); + + // Ok, let's check if the leases are really accessible. + // First, build an array of leases. Get them by address. + // This should work in general, as we haven't updated the addresses. + std::vector<Lease6Ptr> leases; + for (uint32_t i = 0; i < lease_addresses.size(); ++i) { + Lease6Ptr from_mgr = lmptr_->getLease6(Lease::TYPE_NA, + lease_addresses[i]); + ASSERT_TRUE(from_mgr) << "Lease for address " << lease_addresses[i].toText() + << " not found"; + leases.push_back(from_mgr); + } + + ASSERT_EQ(leases_cnt, leases.size()); + + // Now do the actual checks. + for (uint32_t i = 0; i < leases.size(); ++i) { + Lease6Ptr tested = leases[i]; + + // Get the lease by different access patterns. + // In properly working lease manager all queries should return + // exactly the same lease. + + std::string error_desc = " which indicates that the lease indexes were" + " not updated correctly when the lease was updated."; + + // Retrieve lease by address. + Lease6Ptr lease_by_address = lmptr_->getLease6(Lease::TYPE_NA, + tested->addr_); + ASSERT_TRUE(lease_by_address) + << "Lease " << tested->addr_.toText() + << " not found by getLease6(addr)" + << error_desc; + detailCompareLease(tested, lease_by_address); + + // Retrieve lease by type, DUID, IAID. + Lease6Collection leases_by_duid_iaid = lmptr_->getLeases6(tested->type_, + *tested->duid_, + tested->iaid_); + ASSERT_EQ(1, leases_by_duid_iaid.size()); + ASSERT_TRUE(leases_by_duid_iaid[0]) + << "Lease " << tested->addr_.toText() + << " not found by getLease6(type, duid, iaid)" + << error_desc; + detailCompareLease(tested, leases_by_duid_iaid[0]); + + // Retrieve lease by type, DUID, IAID, subnet identifier. + Lease6Collection leases_by_duid_iaid_subnet = + lmptr_->getLeases6(tested->type_, *tested->duid_, + tested->iaid_, tested->subnet_id_); + ASSERT_EQ(1, leases_by_duid_iaid_subnet.size()); + ASSERT_TRUE(leases_by_duid_iaid_subnet[0]) + << "Lease " << tested->addr_.toText() + << " not found by getLease6(type, duid, iaid, subnet_id)" + << error_desc; + detailCompareLease(tested, leases_by_duid_iaid_subnet[0]); + } +} + +/// @brief Verifies that IPv4 lease statistics can be recalculated. +TEST_F(MemfileLeaseMgrTest, recountLeaseStats4) { + startBackend(V4); + testRecountLeaseStats4(); +} + +/// @brief Verifies that IPv6 lease statistics can be recalculated. +TEST_F(MemfileLeaseMgrTest, recountLeaseStats6) { + startBackend(V6); + testRecountLeaseStats6(); +} + +/// @brief Tests that leases from specific subnet can be removed. +TEST_F(MemfileLeaseMgrTest, wipeLeases4) { + startBackend(V4); + testWipeLeases4(); +} + +/// @brief Tests that leases from specific subnet can be removed. +TEST_F(MemfileLeaseMgrTest, wipeLeases6) { + startBackend(V6); + testWipeLeases6(); +} + +/// @brief Tests v4 lease stats query variants. +TEST_F(MemfileLeaseMgrTest, leaseStatsQuery4) { + startBackend(V4); + testLeaseStatsQuery4(); +} + +/// @brief Tests v6 lease stats query variants. +TEST_F(MemfileLeaseMgrTest, leaseStatsQuery6) { + startBackend(V6); + testLeaseStatsQuery6(); +} + +/// @brief Tests v4 lease stats to be attributed to the wrong subnet. +TEST_F(MemfileLeaseMgrTest, leaseStatsQueryAttribution4) { + startBackend(V4); + testLeaseStatsQueryAttribution4(); +} + +/// @brief Tests v6 lease stats to be attributed to the wrong subnet. +TEST_F(MemfileLeaseMgrTest, leaseStatsQueryAttribution6) { + startBackend(V6); + testLeaseStatsQueryAttribution6(); +} + +TEST_F(MemfileLeaseMgrTest, checkVersion4) { + // Create the backend. + DatabaseConnection::ParameterMap parameters; + parameters["type"] = "memfile"; + parameters["universe"] = "4"; + parameters["name"] = getLeaseFilePath("leasefile4_0.csv"); + std::unique_ptr<NakedMemfileLeaseMgr> lease_mgr; + ASSERT_NO_THROW_LOG(lease_mgr.reset(new NakedMemfileLeaseMgr(parameters))); + + // Get the backend version. + auto const& backend_version(lease_mgr->getVersion()); + std::stringstream s; + s << backend_version.first << '.' << backend_version.second; + + // Get the CSV version. + CSVLeaseFile4 f(getLeaseFilePath("leasefile4_0.csv")); + + // They should match. + EXPECT_EQ(s.str(), f.getSchemaVersion()); + + // DBVersion too. + EXPECT_EQ("Memfile backend " + s.str(), + lease_mgr->getDBVersion(Memfile_LeaseMgr::V4)); +} + +TEST_F(MemfileLeaseMgrTest, checkVersion6) { + // Create the backend. + DatabaseConnection::ParameterMap parameters; + parameters["type"] = "memfile"; + parameters["universe"] = "6"; + parameters["name"] = getLeaseFilePath("leasefile6_0.csv"); + std::unique_ptr<NakedMemfileLeaseMgr> lease_mgr; + ASSERT_NO_THROW_LOG(lease_mgr.reset(new NakedMemfileLeaseMgr(parameters))); + + // Get the backend version. + auto const& backend_version(lease_mgr->getVersion()); + std::stringstream s; + s << backend_version.first << '.' << backend_version.second; + + // Get the CSV version. + CSVLeaseFile6 f(getLeaseFilePath("leasefile6_0.csv")); + + // They should match. + EXPECT_EQ(s.str(), f.getSchemaVersion()); + + // DBVersion too. + EXPECT_EQ("Memfile backend " + s.str(), + lease_mgr->getDBVersion(Memfile_LeaseMgr::V6)); +} + +/// @brief Checks that complex user context can be read in v4. +TEST_F(MemfileLeaseMgrTest, v4UserContext) { + // Add some leases to the CSV file. + LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv")); + io.writeFile( + "address,hwaddr,client_id,valid_lifetime,expire,subnet_id," + "fqdn_fwd,fqdn_rev,hostname,state,user_context\n" + + "192.0.0.1,01:01:01:01:01:01,,100,100,1,1,1,,1," + "{}\n" + + "192.0.0.2,02:02:02:02:02:02,,200,400,1,1,1,,0," + "{ \"comment\": \"this lease is for the kitchen computer\"}\n" + + // The next lines have escaped commas in user context. + + "192.0.0.4,04:04:04:04:04:04,,400,1600,1,1,1,,0," + "{ \"comment\": \"this lease is for the mainframe computer\"," + " \"comment2\": \"don't release it\" }\n" + + "192.0.0.8,08:08:08:08:08:08,,800,6400,1,1,1,,0," + "{ \"a\": \"b\", \"c\": { \"d\": 1,\"e\": 2 } }\n" + ); + + startBackend(V4); + + // Check the lease with no key-value pairs in the user context. + Lease4Ptr lease(lmptr_->getLease4(IOAddress("192.0.0.1"))); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Address: 192.0.0.1\n" + "Valid life: 100\n" + "Cltt: 0\n" + "Hardware addr: 01:01:01:01:01:01\n" + "Client id: (none)\n" + "Subnet ID: 1\n" + "State: declined\n" + "User context: { }\n" + ); + + // Check the lease with one key-value pair in the user context. + lease = lmptr_->getLease4(IOAddress("192.0.0.2")); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Address: 192.0.0.2\n" + "Valid life: 200\n" + "Cltt: 200\n" + "Hardware addr: 02:02:02:02:02:02\n" + "Client id: (none)\n" + "Subnet ID: 1\n" + "State: default\n" + "User context: { \"comment\": \"this lease is for the kitchen computer\" }\n" + ); + + // Check the lease with two key-value pairs in the user context. + lease = lmptr_->getLease4(IOAddress("192.0.0.4")); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Address: 192.0.0.4\n" + "Valid life: 400\n" + "Cltt: 1200\n" + "Hardware addr: 04:04:04:04:04:04\n" + "Client id: (none)\n" + "Subnet ID: 1\n" + "State: default\n" + "User context: " + "{ \"comment\": \"this lease is for the mainframe computer\", " + "\"comment2\": \"don't release it\" }\n" + ); + + // Check the lease with nested key-value pairs in the user context. + lease = lmptr_->getLease4(IOAddress("192.0.0.8")); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Address: 192.0.0.8\n" + "Valid life: 800\n" + "Cltt: 5600\n" + "Hardware addr: 08:08:08:08:08:08\n" + "Client id: (none)\n" + "Subnet ID: 1\n" + "State: default\n" + "User context: { \"a\": \"b\", \"c\": { \"d\": 1, \"e\": 2 } }\n" + ); +} + +/// @brief Checks that complex user context can be read in v4. +TEST_F(MemfileLeaseMgrTest, v6UserContext) { + // Add some leases to the CSV file. + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + io.writeFile( + "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" + + "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01," + "400,1000,8,100,0,7,0,1,1,,,1," + "{},,\n" + + "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02," + "200,200,8,100,0,7,0,1,1,,,1," + "{ \"comment\": \"this lease is for the kitchen computer\"},,\n" + + // The next lines have escaped commas in user context. + + "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04," + "200,200,8,100,0,7,0,1,1,,,1," + "{ \"comment\": \"this lease is for the mainframe computer\"," + " \"comment2\": \"don't release it\" },,\n" + + "2001:db8:1::8,08:08:08:08:08:08:08:08:08:08:08:08:08," + "200,200,8,100,0,7,0,1,1,,,1," + "{ \"a\": \"b\", \"c\": { \"d\": 1,\"e\": 2 } }\n" + ); + + startBackend(V6); + + // Check the lease with no key-value pairs in the user context. + Lease6Ptr lease( + lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"))); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Type: IA_NA(0)\n" + "Address: 2001:db8:1::1\n" + "Prefix length: 0\n" + "IAID: 7\n" + "Pref life: 100\n" + "Valid life: 400\n" + "Cltt: 600\n" + "DUID: 01:01:01:01:01:01:01:01:01:01:01:01:01\n" + "Hardware addr: (none)\n" + "Subnet ID: 8\n" + "State: declined\n" + "User context: { }\n" + ); + + // Check the lease with one key-value pair in the user context. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")); + ASSERT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Type: IA_NA(0)\n" + "Address: 2001:db8:1::2\n" + "Prefix length: 0\n" + "IAID: 7\n" + "Pref life: 100\n" + "Valid life: 200\n" + "Cltt: 0\n" + "DUID: 02:02:02:02:02:02:02:02:02:02:02:02:02\n" + "Hardware addr: (none)\n" + "Subnet ID: 8\n" + "State: declined\n" + "User context: { \"comment\": \"this lease is for the kitchen computer\" }\n" + ); + + // Check the lease with two key-value pairs in the user context. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4")); + EXPECT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Type: IA_NA(0)\n" + "Address: 2001:db8:1::4\n" + "Prefix length: 0\n" + "IAID: 7\n" + "Pref life: 100\n" + "Valid life: 200\n" + "Cltt: 0\n" + "DUID: 04:04:04:04:04:04:04:04:04:04:04:04:04\n" + "Hardware addr: (none)\n" + "Subnet ID: 8\n" + "State: declined\n" + "User context: { \"comment\": \"this lease is for the mainframe computer\"," + " \"comment2\": \"don't release it\" }\n" + ); + + // Check the lease with nested key-value pairs in the user context. + lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::8")); + EXPECT_TRUE(lease); + EXPECT_EQ( + lease->toText(), + "Type: IA_NA(0)\n" + "Address: 2001:db8:1::8\n" + "Prefix length: 0\n" + "IAID: 7\n" + "Pref life: 100\n" + "Valid life: 200\n" + "Cltt: 0\n" + "DUID: 08:08:08:08:08:08:08:08:08:08:08:08:08\n" + "Hardware addr: (none)\n" + "Subnet ID: 8\n" + "State: declined\n" + "User context: { \"a\": \"b\", \"c\": { \"d\": 1, \"e\": 2 } }\n" + ); +} + +/// @brief Checks that various hardware information is correctly imported from a +/// CSV file. +TEST_F(MemfileLeaseMgrTest, testHWAddr) { + // Add some leases to the CSV file. + LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv")); + std::stringstream contents; + contents << "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"; + + // Create combinations with all valid values except for 255 which is an + // unused value for both hwtype and hwaddr_source, but lease construction + // doesn't complain in that case either. + std::vector<uint16_t> const hwtypes{0, 1, 6, 8, 255}; + std::vector<uint32_t> const hwsources{ + 0xffffffff, 0x00000000, 0x00000001, 0x00000002, 0x00000004, 0x00000008, + 0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x000000ff}; + int i = 0; + for (uint16_t hwtype : hwtypes) { + for (uint32_t hwsource : hwsources) { + std::stringstream hex; + hex << std::hex << std::setw(2) << std::setfill('0') << i; + contents << "2001:db8:1::" << hex.str() + << ",00:00:00:00:00:" << hex.str() + << std::dec << ",7200,8000,1,3600,0,1,128,0,0,,ff:ff:ff:ff:ff:" + << hex.str() << ",1,," << hwtype << "," << hwsource << "\n"; + ++i; + } + } + + io.writeFile(contents.str()); + startBackend(V6); + + // Check leases. + i = 0; + for (uint16_t hwtype : hwtypes) { + for (uint32_t hwsource : hwsources) { + std::stringstream hex; + hex << std::hex << std::setw(2) << std::setfill('0') << i; + IOAddress address("2001:db8:1::" + hex.str()); + Lease6Ptr lease(lmptr_->getLease6(Lease::TYPE_NA, address.toText())); + + ASSERT_TRUE(lease); + EXPECT_EQ(lease->toText(), + "Type: IA_NA(0)\n" + "Address: " + address.toText() + "\n" + "Prefix length: 128\n" + "IAID: 1\n" + "Pref life: 3600\n" + "Valid life: 7200\n" + "Cltt: 800\n" + "DUID: 00:00:00:00:00:" + hex.str() + "\n" + "Hardware addr: ff:ff:ff:ff:ff:" + hex.str() + "\n" + "Subnet ID: 1\n" + "State: declined\n"); + ASSERT_TRUE(lease->hwaddr_); + EXPECT_EQ(lease->hwaddr_->htype_, hwtype); + EXPECT_EQ(lease->hwaddr_->source_, hwsource); + ++i; + } + } +} + +// Verifies that isJsonSupported() returns true for Memfile. +TEST_F(MemfileLeaseMgrTest, isJsonSupported4) { + startBackend(V4); + bool json_supported; + ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported()); + ASSERT_TRUE(json_supported); +} + +// Verifies that isJsonSupported() returns true for Memfile. +TEST_F(MemfileLeaseMgrTest, isJsonSupported6) { + startBackend(V6); + bool json_supported; + ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported()); + ASSERT_TRUE(json_supported); +} + +// Verifies that v4 class lease counts are correctly adjusted +// when leases have class lists. +TEST_F(MemfileLeaseMgrTest, classLeaseCount4) { + startBackend(V4); + testClassLeaseCount4(); +} + +// Verifies that v6 IA_NA class lease counts are correctly adjusted +// when leases have class lists. +TEST_F(MemfileLeaseMgrTest, classLeaseCount6_NA) { + startBackend(V6); + testClassLeaseCount6(Lease::TYPE_NA); +} + +// Verifies that v6 IA_PD class lease counts are correctly adjusted +// when leases have class lists. +TEST_F(MemfileLeaseMgrTest, classLeaseCount6_PD) { + startBackend(V6); + testClassLeaseCount6(Lease::TYPE_PD); +} + +// brief Checks that a null user context allows allocation. +TEST_F(MemfileLeaseMgrTest, checkLimitsNull4) { + startBackend(V4); + std::string text; + ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(nullptr)); + EXPECT_TRUE(text.empty()); +} + +// brief Checks that a null user context allows allocation. +TEST_F(MemfileLeaseMgrTest, checkLimitsNull6) { + startBackend(V6); + std::string text; + ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(nullptr)); + EXPECT_TRUE(text.empty()); +} + +// Checks a few v4 lease limit checking scenarios. +TEST_F(MemfileLeaseMgrTest, checkLimits4) { + startBackend(V4); + testLeaseLimits4(); +} + +// Checks a few v6 lease limit checking scenarios. +TEST_F(MemfileLeaseMgrTest, checkLimits6) { + startBackend(V6); + testLeaseLimits6(); +} + +// Verifies that v4 class lease counts can be recounted. +TEST_F(MemfileLeaseMgrTest, classLeaseRecount4) { + startBackend(V4); + + // Create a subnet + CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4(); + Subnet4Ptr subnet; + Pool4Ptr pool; + + subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 777)); + pool.reset(new Pool4(IOAddress("192.0.1.0"), 24)); + subnet->addPool(pool); + cfg->add(subnet); + + // We need a Memfile_LeaseMgr pointer to access recount and clear functions. + Memfile_LeaseMgr* memfile_mgr = dynamic_cast<Memfile_LeaseMgr*>(lmptr_); + ASSERT_TRUE(memfile_mgr); + + // Verify class lease counts are zero. + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water")); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon")); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice")); + + // Structure that describes a recipe for a lease. + struct Recipe { + std::string address_; + uint32_t state_; + std::list<ClientClass> classes_; + }; + + // List of lease recipes. + std::list<Recipe> recipes{ + { "192.0.1.100", Lease::STATE_DEFAULT, {"water", "slice"} }, + { "192.0.1.101", Lease::STATE_DEFAULT, {"melon"} }, + { "192.0.1.102", Lease::STATE_DEFAULT, {"melon", "slice"} } + }; + + // Bake all the leases. + for ( auto recipe : recipes ) { + ElementPtr ctx = makeContextWithClasses(recipe.classes_); + ASSERT_TRUE(makeLease4(recipe.address_, 777, recipe.state_, ctx)); + } + + // Verify counts are as expected. + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water")); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon")); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice")); + + // Clear counts + ASSERT_NO_THROW_LOG(memfile_mgr->clearClassLeaseCounts()); + + // Verify counts are zero. + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water")); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon")); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice")); + + // Recount + ASSERT_NO_THROW_LOG(memfile_mgr->recountClassLeases4()); + + // Verify counts are recounted correctly. + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water")); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon")); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice")); +} + +// Verifies that v6 class lease counts can be recounted. +TEST_F(MemfileLeaseMgrTest, classLeaseRecount6) { + startBackend(V6); + + // Create a subnet + CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6(); + Subnet6Ptr subnet; + Pool6Ptr pool; + + subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, 777)); + pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"), + IOAddress("3001:1::FF"))); + subnet->addPool(pool); + + pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112)); + subnet->addPool(pool); + cfg->add(subnet); + + // We need a Memfile_LeaseMgr pointer to access recount and clear functions. + Memfile_LeaseMgr* memfile_mgr = dynamic_cast<Memfile_LeaseMgr*>(lmptr_); + ASSERT_TRUE(memfile_mgr); + + // Verify class lease counts are zero. + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA)); + + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD)); + + // Structure that describes a recipe for a lease. + struct Recipe { + Lease::Type ltype_; + std::string address_; + uint32_t prefix_len_; + uint32_t state_; + std::list<ClientClass> classes_; + }; + + // List of lease recipes. + std::list<Recipe> recipes{ + { Lease::TYPE_NA, "3001::1", 0, Lease::STATE_DEFAULT, {"water", "slice"} }, + { Lease::TYPE_NA, "3001::2", 0, Lease::STATE_DEFAULT, {"melon"} }, + { Lease::TYPE_NA, "3001::3", 0, Lease::STATE_DEFAULT, {"melon", "slice"} }, + + { Lease::TYPE_PD, "3001:1:2:0100::", 112, Lease::STATE_DEFAULT, {"grapes", "slice"} }, + { Lease::TYPE_PD, "3001:1:2:0200::", 112, Lease::STATE_DEFAULT, {"wrath"} }, + { Lease::TYPE_PD, "3001:1:2:0300::", 112, Lease::STATE_DEFAULT, {"wrath", "slice"} }, + }; + + // Bake all the leases. + for ( auto recipe : recipes ) { + ElementPtr ctx = makeContextWithClasses(recipe.classes_); + ASSERT_TRUE(makeLease6(recipe.ltype_, recipe.address_, recipe.prefix_len_, 777, recipe.state_, ctx)); + } + + // Verify counts are as expected. + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA)); + + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD)); + + // Clear counts + ASSERT_NO_THROW_LOG(memfile_mgr->clearClassLeaseCounts()); + + // Verify counts are zero. + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA)); + + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD)); + EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD)); + + // Recount + ASSERT_NO_THROW_LOG(memfile_mgr->recountClassLeases6()); + + // Verify counts are recounted correctly. + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA)); + + EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD)); + EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD)); +} + +} // namespace |