diff options
Diffstat (limited to 'src/bin/d2/tests/d2_update_mgr_unittests.cc')
-rw-r--r-- | src/bin/d2/tests/d2_update_mgr_unittests.cc | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc new file mode 100644 index 0000000..d79b453 --- /dev/null +++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc @@ -0,0 +1,989 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_service.h> +#include <d2srv/testutils/nc_test_utils.h> +#include <d2/d2_update_mgr.h> +#include <d2/nc_add.h> +#include <d2/nc_remove.h> +#include <d2/simple_add.h> +#include <d2/simple_remove.h> +#include <process/testutils/d_test_stubs.h> +#include <util/time_utilities.h> + +#include <gtest/gtest.h> +#include <algorithm> +#include <vector> + +using namespace std; +using namespace isc; +using namespace isc::dhcp_ddns; +using namespace isc::d2; +using namespace isc::process; +using namespace isc::util; + +namespace { + +/// @brief Wrapper class for D2UpdateMgr providing access to non-public methods. +/// +/// This class facilitates testing by making non-public methods accessible so +/// they can be invoked directly in test routines. +class D2UpdateMgrWrapper : public D2UpdateMgr { +public: + /// @brief Constructor + /// + /// Parameters match those needed by D2UpdateMgr. + D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr, + asiolink::IOServicePtr& io_service, + const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT) + : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) { + } + + /// @brief Destructor + virtual ~D2UpdateMgrWrapper() { + } + + // Expose the protected methods to be tested. + using D2UpdateMgr::checkFinishedTransactions; + using D2UpdateMgr::pickNextJob; + using D2UpdateMgr::makeTransaction; +}; + +/// @brief Defines a pointer to a D2UpdateMgr instance. +typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr; + +/// @brief Test fixture for testing D2UpdateMgr. +/// +/// Note this class uses D2UpdateMgrWrapper class to exercise non-public +/// aspects of D2UpdateMgr. D2UpdateMgr depends on both D2QueueMgr and +/// D2CfgMgr. This fixture provides an instance of each, plus a canned, +/// valid DHCP_DDNS configuration sufficient to test D2UpdateMgr's basic +/// functions. +class D2UpdateMgrTest : public TimedIO, public ConfigParseTest { +public: + D2QueueMgrPtr queue_mgr_; + D2CfgMgrPtr cfg_mgr_; + D2UpdateMgrWrapperPtr update_mgr_; + std::vector<NameChangeRequestPtr> canned_ncrs_; + size_t canned_count_; + + D2UpdateMgrTest() { + queue_mgr_.reset(new D2QueueMgr(io_service_)); + cfg_mgr_.reset(new D2CfgMgr()); + update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_, + io_service_)); + makeCannedNcrs(); + makeCannedConfig(); + } + + ~D2UpdateMgrTest() { + } + + /// @brief Creates a list of valid NameChangeRequest. + /// + /// This method builds a list of NameChangeRequests from a single + /// JSON string request. Each request is assigned a unique DHCID. + void makeCannedNcrs() { + const char* msg_str = + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"my.example.com.\" , " + " \"ip-address\" : \"192.168.1.2\" , " + " \"dhcid\" : \"0102030405060708\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " + "}"; + + const char* dhcids[] = { "111111", "222222", "333333", "444444" }; + canned_count_ = 4; + for (int i = 0; i < canned_count_; i++) { + dhcp_ddns::NameChangeRequestPtr ncr = NameChangeRequest:: + fromJSON(msg_str); + ncr->setDhcid(dhcids[i]); + ncr->setChangeType(i % 2 == 0 ? + dhcp_ddns::CHG_ADD : dhcp_ddns::CHG_REMOVE); + canned_ncrs_.push_back(ncr); + } + } + + /// @brief Seeds configuration manager with a valid DHCP_DDNS configuration. + void makeCannedConfig() { + std::string canned_config_ = + "{ " + "\"ip-address\" : \"192.168.1.33\" , " + "\"port\" : 88 , " + "\"tsig-keys\": [] ," + "\"forward-ddns\" : {" + " \"ddns-domains\": [ " + " { \"name\": \"example.com.\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } " + " ] }," + " { \"name\": \"org.\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.1\" } " + " ] }" + " ] }, " + "\"reverse-ddns\" : { " + " \"ddns-domains\": [ " + " { \"name\": \"1.168.192.in-addr.arpa.\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } " + " ] }, " + " { \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , " + " \"dns-servers\" : [ " + " { \"ip-address\": \"127.0.0.1\" } " + " ] } " + " ] } }"; + + // If this configuration fails to parse most tests will fail. + ASSERT_TRUE(fromJSON(canned_config_)); + answer_ = cfg_mgr_->simpleParseConfig(config_set_); + ASSERT_TRUE(checkAnswer(0)); + } + + /// @brief Fakes the completion of a given transaction. + /// + /// @param index index of the request from which the transaction was formed. + /// @param status completion status to assign to the request + void completeTransaction(const size_t index, + const dhcp_ddns::NameChangeStatus& status) { + // add test on index + if (index >= canned_count_) { + ADD_FAILURE() << "request index is out of range: " << index; + } + + const dhcp_ddns::D2Dhcid key = canned_ncrs_[index]->getDhcid(); + + // locate the transaction based on the request DHCID + TransactionList::iterator pos = update_mgr_->findTransaction(key); + if (pos == update_mgr_->transactionListEnd()) { + ADD_FAILURE() << "cannot find transaction for key: " << key.toStr(); + } + + NameChangeTransactionPtr trans = (*pos).second; + // Update the status of the request + trans->getNcr()->setStatus(status); + // End the model. + trans->endModel(); + } + + /// @brief Determines if any transactions are waiting for IO completion. + /// + /// @returns True if isModelWaiting() is true for at least one of the current + /// transactions. + bool anyoneWaiting() { + TransactionList::iterator it = update_mgr_->transactionListBegin(); + while (it != update_mgr_->transactionListEnd()) { + if (((*it).second)->isModelWaiting()) { + return true; + } + } + + return false; + } + + /// @brief Process events until all requests have been completed. + /// + /// This method iteratively calls D2UpdateMgr::sweep and executes + /// IOService calls until both the request queue and transaction list + /// are empty or a timeout occurs. Note that in addition to the safety + /// timer, the number of passes through the loop is also limited to + /// a given number. This is a failsafe to guard against an infinite loop + /// in the test. + void processAll(size_t max_passes = 100) { + // Loop until all the transactions have been dequeued and run through to + // completion. + size_t passes = 0; + size_t handlers = 0; + + // Set the timeout to slightly more than DNSClient timeout to allow + // timeout processing to occur naturally. + size_t timeout = cfg_mgr_->getD2Params()->getDnsServerTimeout() + 100; + while (update_mgr_->getQueueCount() || + update_mgr_->getTransactionCount()) { + ++passes; + update_mgr_->sweep(); + // If any transactions are waiting on IO, run the service. + if (anyoneWaiting()) { + int cnt = runTimedIO(timeout); + + // If cnt is zero then the service stopped unexpectedly. + if (cnt == 0) { + ADD_FAILURE() + << "processALL: IO service stopped unexpectedly," + << " passes: " << passes << ", handlers executed: " + << handlers; + } + + handlers += cnt; + } + + // This is a last resort fail safe to ensure we don't go around + // forever. We cut it off the number of passes at 100 (default + // value). This is roughly ten times the number for the longest + // test (currently, multiTransactionTimeout). + if (passes > max_passes) { + FAIL() << "processALL failed, too many passes: " + << passes << ", total handlers executed: " << handlers; + } + } + } + +}; + +/// @brief Tests the D2UpdateMgr construction. +/// This test verifies that: +/// 1. Construction with invalid queue manager is not allowed +/// 2. Construction with invalid configuration manager is not allowed +/// 3. Construction with max transactions of zero is not allowed +/// 4. Default construction works and max transactions is defaulted properly +/// 5. Construction with custom max transactions works properly +TEST(D2UpdateMgr, construction) { + asiolink::IOServicePtr io_service(new isc::asiolink::IOService()); + D2QueueMgrPtr queue_mgr; + D2CfgMgrPtr cfg_mgr; + D2UpdateMgrPtr update_mgr; + + // Verify that constructor fails if given an invalid queue manager. + ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr())); + EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service), + D2UpdateMgrError); + + // Verify that constructor fails if given an invalid config manager. + ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service))); + ASSERT_NO_THROW(cfg_mgr.reset()); + EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service), + D2UpdateMgrError); + + ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr())); + + // Verify that constructor fails with invalid io_service. + io_service.reset(); + EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service), + D2UpdateMgrError); + io_service.reset(new isc::asiolink::IOService()); + + // Verify that max transactions cannot be zero. + EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0), + D2UpdateMgrError); + + // Verify that given valid values, constructor works. + ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr, + io_service))); + + // Verify that max transactions defaults properly. + EXPECT_EQ(D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT, + update_mgr->getMaxTransactions()); + + + // Verify that constructor permits custom max transactions. + ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr, + io_service, 100))); + + // Verify that max transactions is correct. + EXPECT_EQ(100, update_mgr->getMaxTransactions()); +} + +/// @brief Tests the D2UpdateManager's transaction list services +/// This test verifies that: +/// 1. A transaction can be added to the list. +/// 2. Finding a transaction in the list by key works correctly. +/// 3. Looking for a non-existent transaction works properly. +/// 4. Attempting to add a transaction for a DHCID already in the list fails. +/// 5. Removing a transaction by key works properly. +/// 6. Attempting to remove an non-existent transaction does no harm. +TEST_F(D2UpdateMgrTest, transactionList) { + // Grab a canned request for test purposes. + NameChangeRequestPtr& ncr = canned_ncrs_[0]; + TransactionList::iterator pos; + + // Verify that we can add a transaction. + EXPECT_NO_THROW(update_mgr_->makeTransaction(ncr)); + EXPECT_EQ(1, update_mgr_->getTransactionCount()); + + // Verify that we can find a transaction by key. + EXPECT_NO_THROW(pos = update_mgr_->findTransaction(ncr->getDhcid())); + EXPECT_TRUE(pos != update_mgr_->transactionListEnd()); + + // Verify that convenience method has same result. + EXPECT_TRUE(update_mgr_->hasTransaction(ncr->getDhcid())); + + // Verify that we will not find a transaction that isn't there. + dhcp_ddns::D2Dhcid bogus_id("FFFF"); + EXPECT_NO_THROW(pos = update_mgr_->findTransaction(bogus_id)); + EXPECT_TRUE(pos == update_mgr_->transactionListEnd()); + + // Verify that convenience method has same result. + EXPECT_FALSE(update_mgr_->hasTransaction(bogus_id)); + + // Verify that adding a transaction for the same key fails. + EXPECT_THROW(update_mgr_->makeTransaction(ncr), D2UpdateMgrError); + EXPECT_EQ(1, update_mgr_->getTransactionCount()); + + // Verify the we can remove a transaction by key. + EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid())); + EXPECT_EQ(0, update_mgr_->getTransactionCount()); + + // Verify the we can try to remove a non-existent transaction without harm. + EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid())); +} + +/// @brief Checks transaction creation when both update directions are enabled. +/// Verifies that when both directions are enabled and servers are matched to +/// the request, that the transaction is created with both directions turned on. +TEST_F(D2UpdateMgrTest, bothEnabled) { + // Grab a canned request for test purposes. + NameChangeRequestPtr& ncr = canned_ncrs_[0]; + ncr->setReverseChange(true); + + // Verify we are requesting both directions. + ASSERT_TRUE(ncr->isForwardChange()); + ASSERT_TRUE(ncr->isReverseChange()); + + // Verify both both directions are enabled. + ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled()); + ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled()); + + // Attempt to make a transaction. + ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr)); + + // Verify we create a transaction with both directions turned on. + EXPECT_EQ(1, update_mgr_->getTransactionCount()); + EXPECT_TRUE(ncr->isForwardChange()); + EXPECT_TRUE(ncr->isReverseChange()); +} + +/// @brief Checks transaction creation when reverse updates are disabled. +/// Verifies that when reverse updates are disabled, and there matching forward +/// servers, that the transaction is still created but with only the forward +/// direction turned on. +TEST_F(D2UpdateMgrTest, reverseDisable) { + // Make a NCR which requests both directions. + NameChangeRequestPtr& ncr = canned_ncrs_[0]; + ncr->setReverseChange(true); + + // Wipe out forward domain list. + DdnsDomainMapPtr emptyDomains(new DdnsDomainMap()); + cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains); + + // Verify enable methods are correct. + ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled()); + ASSERT_FALSE(cfg_mgr_->reverseUpdatesEnabled()); + + // Attempt to make a transaction. + ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr)); + + // Verify we create a transaction with only forward turned on. + EXPECT_EQ(1, update_mgr_->getTransactionCount()); + EXPECT_TRUE(ncr->isForwardChange()); + EXPECT_FALSE(ncr->isReverseChange()); +} + +/// @brief Checks transaction creation when forward updates are disabled. +/// Verifies that when forward updates are disabled, and there matching reverse +/// servers, that the transaction is still created but with only the reverse +/// direction turned on. +TEST_F(D2UpdateMgrTest, forwardDisabled) { + // Make a NCR which requests both directions. + NameChangeRequestPtr& ncr = canned_ncrs_[0]; + ncr->setReverseChange(true); + + // Wipe out forward domain list. + DdnsDomainMapPtr emptyDomains(new DdnsDomainMap()); + cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains); + + // Verify enable methods are correct. + ASSERT_FALSE(cfg_mgr_->forwardUpdatesEnabled()); + ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled()); + + // Attempt to make a transaction. + ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr)); + + // Verify we create a transaction with only reverse turned on. + EXPECT_EQ(1, update_mgr_->getTransactionCount()); + EXPECT_FALSE(ncr->isForwardChange()); + EXPECT_TRUE(ncr->isReverseChange()); +} + + +/// @brief Checks transaction creation when neither update direction is enabled. +/// Verifies that transactions are not created when both forward and reverse +/// directions are disabled. +TEST_F(D2UpdateMgrTest, bothDisabled) { + // Grab a canned request for test purposes. + NameChangeRequestPtr& ncr = canned_ncrs_[0]; + ncr->setReverseChange(true); + TransactionList::iterator pos; + + // Wipe out both forward and reverse domain lists. + DdnsDomainMapPtr emptyDomains(new DdnsDomainMap()); + cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains); + cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains); + + // Verify enable methods are correct. + EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled()); + EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled()); + + // Attempt to make a transaction. + ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr)); + + // Verify that do not create a transaction. + EXPECT_EQ(0, update_mgr_->getTransactionCount()); +} + +/// @brief Tests D2UpdateManager's checkFinishedTransactions method. +/// This test verifies that: +/// 1. Completed transactions are removed from the transaction list. +/// 2. Failed transactions are removed from the transaction list. +/// @todo This test will need to expand if and when checkFinishedTransactions +/// method expands to do more than remove them from the list. +TEST_F(D2UpdateMgrTest, checkFinishedTransaction) { + // Ensure we have at least 4 canned requests with which to work. + ASSERT_TRUE(canned_count_ >= 4); + + // Create a transaction for each canned request. + for (int i = 0; i < canned_count_; i++) { + EXPECT_NO_THROW(update_mgr_->makeTransaction(canned_ncrs_[i])); + } + // Verify we have that the transaction count is correct. + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + + // Verify that all four transactions have been started. + TransactionList::iterator pos; + EXPECT_NO_THROW(pos = update_mgr_->transactionListBegin()); + while (pos != update_mgr_->transactionListEnd()) { + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_EQ(dhcp_ddns::ST_PENDING, trans->getNcrStatus()); + ASSERT_TRUE(trans->isModelRunning()); + ++pos; + } + + // Verify that invoking checkFinishedTransactions does not throw. + EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions()); + + // Since nothing is running IOService, the all four transactions should + // still be in the list. + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + + // Now "complete" two of the four. + // Simulate a successful completion. + completeTransaction(1, dhcp_ddns::ST_COMPLETED); + + // Simulate a failed completion. + completeTransaction(3, dhcp_ddns::ST_FAILED); + + // Verify that invoking checkFinishedTransactions does not throw. + EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions()); + + // Verify that the list of transactions has decreased by two. + EXPECT_EQ(canned_count_ - 2, update_mgr_->getTransactionCount()); + + // Verify that the transaction list is correct. + EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[0]->getDhcid())); + EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[1]->getDhcid())); + EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[2]->getDhcid())); + EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[3]->getDhcid())); +} + +/// @brief Tests D2UpdateManager's pickNextJob method. +/// This test verifies that: +/// 1. pickNextJob will select and make transactions from NCR queue. +/// 2. Requests are removed from the queue once selected +/// 3. Requests for DHCIDs with transactions already in progress are not +/// selected. +/// 4. Requests with no matching servers are removed from the queue and +/// discarded. +TEST_F(D2UpdateMgrTest, pickNextJob) { + // Ensure we have at least 4 canned requests with which to work. + ASSERT_TRUE(canned_count_ >= 4); + + // Put each transaction on the queue. + for (int i = 0; i < canned_count_; i++) { + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i])); + } + + // Invoke pickNextJob canned_count_ times which should create a + // transaction for each canned ncr. + for (int i = 0; i < canned_count_; i++) { + EXPECT_NO_THROW(update_mgr_->pickNextJob()); + EXPECT_EQ(i + 1, update_mgr_->getTransactionCount()); + EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid())); + } + + // Verify that the queue has been drained. + EXPECT_EQ(0, update_mgr_->getQueueCount()); + + // Now verify that a subsequent request for a DCHID for which a + // transaction is in progress, is not dequeued. + // First add the "subsequent" request. + dhcp_ddns::NameChangeRequestPtr + subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2]))); + EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr)); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Verify that invoking pickNextJob: + // 1. Does not throw + // 2. Does not make a new transaction + // 3. Does not dequeue the entry + EXPECT_NO_THROW(update_mgr_->pickNextJob()); + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Clear out the queue and transaction list. + queue_mgr_->clearQueue(); + update_mgr_->clearTransactionList(); + + // Make a forward change NCR with an FQDN that has no forward match. + dhcp_ddns::NameChangeRequestPtr + bogus_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0]))); + bogus_ncr->setForwardChange(true); + bogus_ncr->setReverseChange(false); + bogus_ncr->setFqdn("bogus.forward.domain.com"); + + // Put it on the queue up + ASSERT_NO_THROW(queue_mgr_->enqueue(bogus_ncr)); + + // Verify that invoking pickNextJob: + // 1. Does not throw + // 2. Does not make a new transaction + // 3. Does dequeue the entry + EXPECT_NO_THROW(update_mgr_->pickNextJob()); + EXPECT_EQ(0, update_mgr_->getTransactionCount()); + EXPECT_EQ(0, update_mgr_->getQueueCount()); + + // Make a reverse change NCR with an FQDN that has no reverse match. + bogus_ncr.reset(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0]))); + bogus_ncr->setForwardChange(false); + bogus_ncr->setReverseChange(true); + bogus_ncr->setIpAddress("77.77.77.77"); + + // Verify that invoking pickNextJob: + // 1. does not throw + // 2. Does not make a new transaction + // 3. Does dequeue the entry + EXPECT_NO_THROW(update_mgr_->pickNextJob()); + EXPECT_EQ(0, update_mgr_->getTransactionCount()); + EXPECT_EQ(0, update_mgr_->getQueueCount()); +} + +/// @brief Tests D2UpdateManager's sweep method. +/// Since sweep is primarily a wrapper around checkFinishedTransactions and +/// pickNextJob, along with checks on maximum transaction limits, it mostly +/// verifies that these three pieces work together to move process jobs. +/// Most of what is tested here is tested above. +TEST_F(D2UpdateMgrTest, sweep) { + // Ensure we have at least 4 canned requests with which to work. + ASSERT_TRUE(canned_count_ >= 4); + + // Set max transactions to same as current transaction count. + EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_)); + EXPECT_EQ(canned_count_, update_mgr_->getMaxTransactions()); + + // Put each transaction on the queue. + for (int i = 0; i < canned_count_; i++) { + EXPECT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i])); + } + + // Invoke sweep canned_count_ times which should create a + // transaction for each canned ncr. + for (int i = 0; i < canned_count_; i++) { + EXPECT_NO_THROW(update_mgr_->sweep()); + EXPECT_EQ(i + 1, update_mgr_->getTransactionCount()); + EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid())); + } + + // Verify that the queue has been drained. + EXPECT_EQ(0, update_mgr_->getQueueCount()); + + // Verify max transactions can't be less than current transaction count. + EXPECT_THROW(update_mgr_->setMaxTransactions(1), D2UpdateMgrError); + + // Queue up a request for a DHCID which has a transaction in progress. + dhcp_ddns::NameChangeRequestPtr + subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2]))); + EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr)); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Verify that invoking sweep, does not dequeue the job nor make a + // transaction for it. + EXPECT_NO_THROW(update_mgr_->sweep()); + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Mark the transaction complete. + completeTransaction(2, dhcp_ddns::ST_COMPLETED); + + // Verify that invoking sweep, cleans up the completed transaction, + // dequeues the queued job and adds its transaction to the list. + EXPECT_NO_THROW(update_mgr_->sweep()); + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + EXPECT_EQ(0, update_mgr_->getQueueCount()); + + // Queue up a request from a new DHCID. + dhcp_ddns::NameChangeRequestPtr + another_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0]))); + another_ncr->setDhcid("AABBCCDDEEFF"); + EXPECT_NO_THROW(queue_mgr_->enqueue(another_ncr)); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Verify that sweep does not dequeue the new request as we are at + // maximum transaction count. + EXPECT_NO_THROW(update_mgr_->sweep()); + EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount()); + EXPECT_EQ(1, update_mgr_->getQueueCount()); + + // Set max transactions to same as current transaction count. + EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_ + 1)); + + // Verify that invoking sweep, dequeues the request and creates + // a transaction for it. + EXPECT_NO_THROW(update_mgr_->sweep()); + EXPECT_EQ(canned_count_ + 1, update_mgr_->getTransactionCount()); + EXPECT_EQ(0, update_mgr_->getQueueCount()); + + // Verify that clearing transaction list works. + EXPECT_NO_THROW(update_mgr_->clearTransactionList()); + EXPECT_EQ(0, update_mgr_->getTransactionCount()); +} + +/// @brief Tests integration of NameAddTransaction +/// This test verifies that update manager can create and manage a +/// NameAddTransaction from start to finish. It utilizes a fake server +/// which responds to all requests sent with NOERROR, simulating a +/// successful addition. The transaction processes both forward and +/// reverse changes. +TEST_F(D2UpdateMgrTest, addTransaction) { + // Put each transaction on the queue. + canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD); + canned_ncrs_[0]->setReverseChange(true); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + // Verify the correct type of transaction was created. + NameAddTransaction* t = dynamic_cast<NameAddTransaction*>(trans.get()); + ASSERT_TRUE(t); + + // At this point the transaction should have constructed + // and sent the DNS request. + ASSERT_TRUE(trans->getCurrentServer()); + ASSERT_TRUE(trans->isModelRunning()); + ASSERT_EQ(1, trans->getUpdateAttempts()); + ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent()); + + // Create a server based on the transaction's current server, and + // start it listening. + FauxServer server(*io_service_, *(trans->getCurrentServer())); + server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Run sweep and IO until everything is done. + processAll(); + + // Verify that model succeeded. + EXPECT_FALSE(trans->didModelFail()); + + // Both completion flags should be true. + EXPECT_TRUE(trans->getForwardChangeCompleted()); + EXPECT_TRUE(trans->getReverseChangeCompleted()); + + // Verify that we went through success state. + EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST, + trans->getPrevState()); + EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT, + trans->getLastEvent()); +} + +/// @brief Tests integration of NameRemoveTransaction +/// This test verifies that update manager can create and manage a +/// NameRemoveTransaction from start to finish. It utilizes a fake server +/// which responds to all requests sent with NOERROR, simulating a +/// successful addition. The transaction processes both forward and +/// reverse changes. +TEST_F(D2UpdateMgrTest, removeTransaction) { + // Put each transaction on the queue. + canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE); + canned_ncrs_[0]->setReverseChange(true); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + // Verify the correct type of transaction was created. + NameRemoveTransaction* t = dynamic_cast<NameRemoveTransaction*>(trans.get()); + ASSERT_TRUE(t); + + // At this point the transaction should have constructed + // and sent the DNS request. + ASSERT_TRUE(trans->getCurrentServer()); + ASSERT_TRUE(trans->isModelRunning()); + ASSERT_EQ(1, trans->getUpdateAttempts()); + ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent()); + + // Create a server based on the transaction's current server, + // and start it listening. + FauxServer server(*io_service_, *(trans->getCurrentServer())); + server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Run sweep and IO until everything is done. + processAll(); + + // Verify that model succeeded. + EXPECT_FALSE(trans->didModelFail()); + + // Both completion flags should be true. + EXPECT_TRUE(trans->getForwardChangeCompleted()); + EXPECT_TRUE(trans->getReverseChangeCompleted()); + + // Verify that we went through success state. + EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST, + trans->getPrevState()); + EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT, + trans->getLastEvent()); +} + + +/// @brief Tests handling of a transaction which fails. +/// This test verifies that update manager correctly concludes a transaction +/// which fails to complete successfully. The failure simulated is repeated +/// corrupt responses from the server, which causes an exhaustion of the +/// available servers. +TEST_F(D2UpdateMgrTest, errorTransaction) { + // Put each transaction on the queue. + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + ASSERT_TRUE(trans->isModelRunning()); + ASSERT_EQ(1, trans->getUpdateAttempts()); + ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent()); + ASSERT_TRUE(trans->getCurrentServer()); + + // Create a server and start it listening. + FauxServer server(*io_service_, *(trans->getCurrentServer())); + server.receive(FauxServer::CORRUPT_RESP); + + // Run sweep and IO until everything is done. + processAll(); + + // Verify that model succeeded. + EXPECT_FALSE(trans->didModelFail()); + + // Both completion flags should be false. + EXPECT_FALSE(trans->getForwardChangeCompleted()); + EXPECT_FALSE(trans->getReverseChangeCompleted()); + + // Verify that we went through success state. + EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + trans->getPrevState()); + EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT, + trans->getLastEvent()); + + +} + +/// @brief Tests processing of multiple transactions. +/// This test verifies that update manager can create and manage a multiple +/// transactions, concurrently. It uses a fake server that responds to all +/// requests sent with NOERROR, simulating successful DNS updates. The +/// transactions are a mix of both adds and removes. +TEST_F(D2UpdateMgrTest, multiTransaction) { + // Queue up all the requests. + int test_count = canned_count_; + for (int i = test_count; i > 0; i--) { + canned_ncrs_[i-1]->setReverseChange(true); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1])); + } + + // Create a server and start it listening. Note this relies on the fact + // that all of configured servers have the same address. + // and start it listening. + asiolink::IOAddress server_ip("127.0.0.1"); + FauxServer server(*io_service_, server_ip, 5301); + server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Run sweep and IO until everything is done. + processAll(); + + for (int i = 0; i < test_count; i++) { + EXPECT_EQ(dhcp_ddns::ST_COMPLETED, canned_ncrs_[i]->getStatus()); + } +} + +/// @brief Tests processing of multiple transactions. +/// This test verifies that update manager can create and manage a multiple +/// transactions, concurrently. It uses a fake server that responds to all +/// requests sent with NOERROR, simulating successful DNS updates. The +/// transactions are a mix of both adds and removes. +TEST_F(D2UpdateMgrTest, multiTransactionTimeout) { + // Queue up all the requests. + int test_count = canned_count_; + for (int i = test_count; i > 0; i--) { + canned_ncrs_[i-1]->setReverseChange(true); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1])); + } + + // No server is running, so everything will time out. + + // Run sweep and IO until everything is done. + processAll(); + + for (int i = 0; i < test_count; i++) { + EXPECT_EQ(dhcp_ddns::ST_FAILED, canned_ncrs_[i]->getStatus()); + } +} + +/// @brief Tests integration of SimpleAddTransaction +/// This test verifies that update manager can create and manage a +/// SimpleAddTransaction from start to finish. It utilizes a fake server +/// which responds to all requests sent with NOERROR, simulating a +/// successful addition. The transaction processes both forward and +/// reverse changes. +TEST_F(D2UpdateMgrTest, simpleAddTransaction) { + // Put each transaction on the queue. + canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD); + canned_ncrs_[0]->setReverseChange(true); + canned_ncrs_[0]->setConflictResolution(false); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + // Verify the correct type of transaction was created. + SimpleAddTransaction* t = dynamic_cast<SimpleAddTransaction*>(trans.get()); + ASSERT_TRUE(t); + + // At this point the transaction should have constructed + // and sent the DNS request. + ASSERT_TRUE(trans->getCurrentServer()); + ASSERT_TRUE(trans->isModelRunning()); + ASSERT_EQ(1, trans->getUpdateAttempts()); + ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent()); + + // Create a server based on the transaction's current server, and + // start it listening. + FauxServer server(*io_service_, *(trans->getCurrentServer())); + server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Run sweep and IO until everything is done. + processAll(); + + // Verify that model succeeded. + EXPECT_FALSE(trans->didModelFail()); + + // Both completion flags should be true. + EXPECT_TRUE(trans->getForwardChangeCompleted()); + EXPECT_TRUE(trans->getReverseChangeCompleted()); + + // Verify that we went through success state. + EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST, + trans->getPrevState()); + EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT, + trans->getLastEvent()); +} + +/// @brief Tests integration of SimpleRemoveTransaction +/// This test verifies that update manager can create and manage a +/// SimpleRemoveTransaction from start to finish. It utilizes a fake server +/// which responds to all requests sent with NOERROR, simulating a +/// successful addition. The transaction processes both forward and +/// reverse changes. +TEST_F(D2UpdateMgrTest, simpleRemoveTransaction) { + // Put each transaction on the queue. + canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE); + canned_ncrs_[0]->setReverseChange(true); + canned_ncrs_[0]->setConflictResolution(false); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + // Verify the correct type of transaction was created. + SimpleRemoveTransaction* t = dynamic_cast<SimpleRemoveTransaction*>(trans.get()); + ASSERT_TRUE(t); + + // At this point the transaction should have constructed + // and sent the DNS request. + ASSERT_TRUE(trans->getCurrentServer()); + ASSERT_TRUE(trans->isModelRunning()); + ASSERT_EQ(1, trans->getUpdateAttempts()); + ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent()); + + // Create a server based on the transaction's current server, + // and start it listening. + FauxServer server(*io_service_, *(trans->getCurrentServer())); + server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR()); + + // Run sweep and IO until everything is done. + processAll(); + + // Verify that model succeeded. + EXPECT_FALSE(trans->didModelFail()); + + // Both completion flags should be true. + EXPECT_TRUE(trans->getForwardChangeCompleted()); + EXPECT_TRUE(trans->getReverseChangeCompleted()); + + // Verify that we went through success state. + EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST, + trans->getPrevState()); + EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT, + trans->getLastEvent()); +} + +} |