Parcourir la source

[master] Merge branch 'trac3222'

b10-dhcp6 fully able to do DHCP-DDNS
Thomas Markwalder il y a 11 ans
Parent
commit
2399566964

+ 4 - 3
src/bin/d2/d2_config.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
 
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 #include <string>
 
@@ -131,7 +132,7 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
 
         // If the lengths are identical and the names match we're done.
         if (req_len == dom_len) {
-            if (fqdn == domain_name) {
+            if (boost::iequals(fqdn, domain_name)) {
                 // exact match, done
                 domain = map_pair.second;
                 return (true);
@@ -143,7 +144,7 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
             // prevents "onetwo.net" from matching "two.net".
             size_t offset = req_len - dom_len;
             if ((fqdn[offset - 1] == '.')  &&
-               (fqdn.compare(offset, std::string::npos, domain_name) == 0)) {
+               (boost::iequals(fqdn.substr(offset), domain_name))) {
                 // Fqdn contains domain name, keep it if its better than
                 // any we have matched so far.
                 if (dom_len > match_len) {

+ 7 - 7
src/bin/d2/d2_messages.mes

@@ -344,10 +344,10 @@ due to invalid data contained in the NameChangeRequest. The request will be
 aborted.  This is most likely a configuration issue.
 
 % DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS successfully added the DNS mapping addition for this request: %1
-This is a debug message issued after DHCP_DDNS has submitted DNS mapping
-additions which were received and accepted by an appropriate DNS server.
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping additions which were received and accepted by an appropriate DNS server.
 
-% DHCP_DDNS_ADD_FAILED DHCP_DDNS failed attempting to make DNS mapping additions for this request: %1, event: %2
+% DHCP_DDNS_ADD_FAILED DHCP_DDNS Transaction outcome: %1
 This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
 entry additions have failed.  The precise reason for the failure should be
 documented in preceding log entries.
@@ -430,17 +430,17 @@ while DHCP_DDNS was removing a reverse address mapping.  The request will be
 aborted.  This is most likely a programmatic issue and should be reported.
 
 % DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS successfully removed the DNS mapping addition for this request: %1
-This is a debug message issued after DHCP_DDNS has submitted DNS mapping
-removals which were received and accepted by an appropriate DNS server.
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping removals which were received and accepted by an appropriate DNS server.
 
-% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS failed attempting to make DNS mapping removals for this request: %1, event: %2
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS Transaction outcome: %1
 This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
 entry removals have failed.  The precise reason for the failure should be
 documented in preceding log entries.
 
 % DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
 
-% DHCP_DDNS_UPDATE_REQUEST_SENT for transaction key: %1 to server: %2
+% DHCP_DDNS_UPDATE_REQUEST_SENT %1 for transaction key: %2 to server: %3
 This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
 server.
 

+ 7 - 7
src/bin/d2/nc_add.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -202,7 +202,7 @@ NameAddTransaction::addingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Foward Add");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -313,7 +313,7 @@ NameAddTransaction::replacingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward Replace");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -459,7 +459,7 @@ NameAddTransaction::replacingRevPtrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Reverse Replace");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -539,7 +539,7 @@ void
 NameAddTransaction::processAddOkHandler() {
     switch(getNextEvent()) {
     case UPDATE_OK_EVT:
-        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL, DHCP_DDNS_ADD_SUCCEEDED)
+        LOG_INFO(dctl_logger, DHCP_DDNS_ADD_SUCCEEDED)
                   .arg(getNcr()->toText());
         setNcrStatus(dhcp_ddns::ST_COMPLETED);
         endModel();
@@ -556,9 +556,9 @@ NameAddTransaction::processAddFailedHandler() {
     switch(getNextEvent()) {
     case UPDATE_FAILED_EVT:
     case NO_MORE_SERVERS_EVT:
-        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED).arg(getNcr()->toText())
-        .arg(getEventLabel(getNextEvent()));
         setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED)
+                  .arg(transactionOutcomeString());
         endModel();
         break;
     default:

+ 8 - 8
src/bin/d2/nc_remove.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -207,7 +207,7 @@ NameRemoveTransaction::removingFwdAddrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward A/AAAA Remove");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -311,7 +311,7 @@ NameRemoveTransaction::removingFwdRRsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Forward RR Remove");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -464,7 +464,7 @@ NameRemoveTransaction::removingRevPtrsHandler() {
 
         // Call sendUpdate() to initiate the async send. Note it also sets
         // next event to NOP_EVT.
-        sendUpdate();
+        sendUpdate("Reverse Remove");
         break;
 
     case IO_COMPLETED_EVT: {
@@ -547,8 +547,8 @@ void
 NameRemoveTransaction::processRemoveOkHandler() {
     switch(getNextEvent()) {
     case UPDATE_OK_EVT:
-        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL, DHCP_DDNS_REMOVE_SUCCEEDED)
-                  .arg(getNcr()->toText());
+        LOG_INFO(dctl_logger, DHCP_DDNS_REMOVE_SUCCEEDED)
+                .arg(getNcr()->toText());
         setNcrStatus(dhcp_ddns::ST_COMPLETED);
         endModel();
         break;
@@ -565,9 +565,9 @@ NameRemoveTransaction::processRemoveFailedHandler() {
     case UPDATE_FAILED_EVT:
     case NO_MORE_SERVERS_EVT:
     case SERVER_IO_ERROR_EVT:
-        LOG_ERROR(dctl_logger, DHCP_DDNS_REMOVE_FAILED).arg(getNcr()->toText())
-        .arg(getEventLabel(getNextEvent()));
         setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_REMOVE_FAILED)
+                  .arg(transactionOutcomeString());
         endModel();
         break;
     default:

+ 64 - 4
src/bin/d2/nc_trans.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,8 @@
 #include <d2/nc_trans.h>
 #include <dns/rdata.h>
 
+#include <sstream>
+
 namespace isc {
 namespace d2 {
 
@@ -94,18 +96,75 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
     // set to indicate IO completed.
     // runModel is exception safe so we are good to call it here.
     // It won't exit until we hit the next IO wait or the state model ends.
+    setDnsUpdateStatus(status);
     LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
               DHCP_DDNS_UPDATE_RESPONSE_RECEIVED)
               .arg(getTransactionKey().toStr())
               .arg(current_server_->toText())
-              .arg(status);
+              .arg(responseString());
 
-    setDnsUpdateStatus(status);
     runModel(IO_COMPLETED_EVT);
 }
 
+std::string
+NameChangeTransaction::responseString() const {
+    std::ostringstream stream;
+    switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS:
+            stream << "SUCCESS, rcode: ";
+            if (getDnsUpdateResponse()) {
+                 stream << getDnsUpdateResponse()->getRcode().toText();
+            } else {
+                stream << " update response is NULL";
+            }
+            break;
+        case DNSClient::TIMEOUT:
+            stream << "TIMEOUT";
+            break;
+        case DNSClient::IO_STOPPED:
+            stream << "IO_STOPPED";
+            break;
+        case DNSClient::INVALID_RESPONSE:
+            stream << "INVALID_RESPONSE";
+            break;
+        case DNSClient::OTHER:
+            stream << "OTHER";
+            break;
+        default:
+            stream << "UKNOWNN("
+                   << static_cast<int>(getDnsUpdateStatus()) << ")";
+            break;
+
+    }
+
+    return (stream.str());
+}
+
+std::string
+NameChangeTransaction::transactionOutcomeString() const {
+    std::ostringstream stream;
+    stream << "Status: " << (getNcrStatus() == dhcp_ddns::ST_COMPLETED
+                             ? "Completed, " : "Failed, ")
+           << "Event: " << getEventLabel(getNextEvent()) << ", ";
+
+    if (ncr_->isForwardChange()) {
+        stream << " Forward change:" << (getForwardChangeCompleted()
+                                         ? " completed, " : " failed, ");
+    }
+
+    if (ncr_->isReverseChange()) {
+        stream << " Reverse change:" << (getReverseChangeCompleted()
+                                          ? " completed, " : " failed, ");
+    }
+
+    stream << " request: " << ncr_->toText();
+    return (stream.str());
+}
+
+
 void
-NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
+NameChangeTransaction::sendUpdate(const std::string& comment,
+                                  bool /* use_tsig_ */) {
     try {
         ++update_attempts_;
         // @todo add logic to add/replace TSIG key info in request if
@@ -122,6 +181,7 @@ NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
         postNextEvent(NOP_EVT);
         LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
                   DHCP_DDNS_UPDATE_REQUEST_SENT)
+                  .arg(comment)
                   .arg(getTransactionKey().toStr())
                   .arg(current_server_->toText());
     } catch (const std::exception& ex) {

+ 21 - 1
src/bin/d2/nc_trans.h

@@ -207,12 +207,14 @@ protected:
     /// currently selected server.  Since the send is asynchronous, the method
     /// posts NOP_EVT as the next event and then returns.
     ///
+    /// @param comment text to include in log detail
     /// @param use_tsig True if the update should be include a TSIG key. This
     /// is not yet implemented.
     ///
     /// If an exception occurs it will be logged and and the transaction will
     /// be failed.
-    virtual void sendUpdate(bool use_tsig = false);
+    virtual void sendUpdate(const std::string& comment = "",
+                            bool use_tsig = false);
 
     /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
@@ -401,6 +403,24 @@ protected:
     /// the RData cannot be added to the given RRset.
     void addPtrRdata(dns::RRsetPtr& rrset);
 
+    /// @brief Returns a string version of the current response status and rcode
+    ///
+    /// Renders a string containing the a text label current DNS update status
+    /// and RCODE (if status is DNSClient::SUCCESS)
+    ///
+    /// @return std::string containing constructed text
+    std::string responseString() const;
+
+    /// @brief Returns a string version of transaction outcome.
+    ///
+    /// Renders a string containing summarizes the outcome of the
+    /// transaction. The information includes the overall status,
+    /// the last event, whether not forward and reverse changes were
+    /// done, as well as the NCR serviced.
+    ///
+    /// @return std::string containing constructed text
+    std::string transactionOutcomeString() const;
+
 public:
     /// @brief Fetches the NameChangeRequest for this transaction.
     ///

+ 8 - 3
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -1066,6 +1066,10 @@ TEST_F(D2CfgMgrTest, forwardMatch) {
     EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
     EXPECT_EQ("tmark.org", match->getName());
 
+    // Verify that search is case insensisitive.
+    EXPECT_TRUE(cfg_mgr_->matchForward("TMARK.ORG", match));
+    EXPECT_EQ("tmark.org", match->getName());
+
     // Verify that an exact match works.
     EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
     EXPECT_EQ("one.tmark.org", match->getName());
@@ -1207,7 +1211,8 @@ TEST_F(D2CfgMgrTest, matchReverse) {
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] }, "
-                        "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+                        // Note mixed case to test case insensitivity.
+                        "{ \"name\": \"2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.\" , "
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] },"
@@ -1247,7 +1252,7 @@ TEST_F(D2CfgMgrTest, matchReverse) {
 
     // Verify a IPv6 match.
     EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
-    EXPECT_EQ("2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.", match->getName());
+    EXPECT_EQ("2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.", match->getName());
 
     // Verify a IPv6 wild card match.
     EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));

+ 4 - 3
src/bin/d2/tests/nc_add_unittests.cc

@@ -54,10 +54,11 @@ public:
     /// It will also simulate an exception-based failure of sendUpdate, if
     /// the simulate_send_exception_ flag is true.
     ///
-    /// @param use_tsig_ Parameter is unused, but present in the base class
-    /// method.
+    /// @param comment Parameter is unused, but present in base class method.
+    /// @param use_tsig_ Parameter is unused, but present in base class method.
     ///
-    virtual void sendUpdate(bool /* use_tsig_ = false */) {
+    virtual void sendUpdate(const std::string& /*comment*/,
+                            bool /* use_tsig_ = false */) {
         if (simulate_send_exception_) {
             // Make the flag a one-shot by resetting it.
             simulate_send_exception_ = false;

+ 5 - 4
src/bin/d2/tests/nc_remove_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -55,10 +55,11 @@ public:
     /// It will also simulate an exception-based failure of sendUpdate, if
     /// the simulate_send_exception_ flag is true.
     ///
-    /// @param use_tsig_ Parameter is unused, but present in the base class
-    /// method.
+    /// @param comment Parameter is unused, but present in base class method
+    /// @param use_tsig Parameter is unused, but present in base class method.
     ///
-    virtual void sendUpdate(bool /* use_tsig_ = false */) {
+    virtual void sendUpdate(const std::string& /* comment */,
+                            bool /* use_tsig = false */) {
         if (simulate_send_exception_) {
             // Make the flag a one-shot by resetting it.
             simulate_send_exception_ = false;

+ 81 - 0
src/bin/d2/tests/nc_trans_unittests.cc

@@ -262,6 +262,8 @@ public:
     using NameChangeTransaction::addLeaseAddressRdata;
     using NameChangeTransaction::addDhcidRdata;
     using NameChangeTransaction::addPtrRdata;
+    using NameChangeTransaction::responseString;
+    using NameChangeTransaction::transactionOutcomeString;
 };
 
 // Declare them so Gtest can see them.
@@ -507,8 +509,87 @@ TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
 
     // Should be empty again.
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+}
+
+/// @brief Tests responseString method.
+TEST_F(NameChangeTransactionTest, responseString) {
+    // Create a transaction.
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Make sure it is safe to call when status says success but there
+    // is no update response.
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::SUCCESS));
+    EXPECT_EQ("SUCCESS, rcode:  update response is NULL",
+              name_change->responseString());
+
+    // Create a response. (We use an OUTBOUND message so we can set RCODE)
+    D2UpdateMessagePtr resp;
+    ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+    ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+    // Make sure we decode Rcode when status is successful.
+    ASSERT_NO_THROW(resp->setRcode(dns::Rcode::NXDOMAIN()));
+    EXPECT_EQ("SUCCESS, rcode: NXDOMAIN", name_change->responseString());
+
+    // Test all of the non-success values for status.
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+    EXPECT_EQ("TIMEOUT", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::IO_STOPPED));
+    EXPECT_EQ("IO_STOPPED", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::
+                                                    INVALID_RESPONSE));
+    EXPECT_EQ("INVALID_RESPONSE", name_change->responseString());
+
+    ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::OTHER));
+    EXPECT_EQ("OTHER", name_change->responseString());
 }
 
+/// @brief Tests transactionOutcomeString method.
+TEST_F(NameChangeTransactionTest, transactionOutcomeString) {
+    // Create a transaction.
+    NameChangeStubPtr name_change;
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    ncr = name_change->getNcr();
+
+    // Check case of failed transaction in both directions
+    std::string exp_str("Status: Failed, Event: UNDEFINED,  Forward change:"
+                        " failed,  Reverse change: failed,  request: ");
+    exp_str += ncr->toText();
+
+    std::string tstring = name_change->transactionOutcomeString();
+    std::cout << "tstring is: [" << tstring << "]" << std::endl;
+
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success all around
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    name_change->setForwardChangeCompleted(true);
+    name_change->setReverseChangeCompleted(true);
+
+    exp_str = "Status: Completed, Event: UNDEFINED,  Forward change: completed,"
+              "  Reverse change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success, with no forward change
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    ncr->setForwardChange(false);
+    exp_str = "Status: Completed, Event: UNDEFINED, "
+              " Reverse change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+    // Check case of success, with no reverse change
+    name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+    ncr->setForwardChange(true);
+    ncr->setReverseChange(false);
+    exp_str = "Status: Completed, Event: UNDEFINED, "
+              " Forward change: completed,  request: " + ncr->toText();
+    EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+}
 
 /// @brief Tests event and state dictionary construction and verification.
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {

+ 14 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -114,6 +114,16 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
         return (answer);
     }
 
+    // Server will start DDNS communications if its enabled.
+    try {
+        server_->startD2();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCP_DDNS client "
+                " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // Configuration may change active interfaces. Therefore, we have to reopen
     // sockets according to new configuration. This operation is not exception
     // safe and we really don't want to emit exceptions to the callback caller.
@@ -212,6 +222,10 @@ void ControlledDhcpv6Srv::establishSession() {
     try {
         // Pull the full configuration out from the session.
         configureDhcp6Server(*this, config_session_->getFullConfig());
+
+        // Server will start DDNS communications if its enabled.
+        server_->startD2();
+
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         openActiveSockets(getPort());

+ 5 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -91,6 +91,11 @@ New  values: hostname = %2, reverse mapping = %3, forward mapping = %4
 This debug message is logged when FQDN mapping for a particular lease has been
 changed by the recent Renew message. This mapping will be changed in DNS.
 
+% DHCP6_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error: %1,  ncr: %2
+This error message indicates that IPv6 DHCP server failed to send a DDNS
+update reqeust to the DHCP-DDNS server. This is most likely a configuration or
+networking error.
+
 % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
 This message is printed when DHCPv6 server disables an interface from being
 used to receive DHCPv6 traffic. Sockets on this interface will not be opened

+ 44 - 32
src/bin/dhcp6/dhcp6_srv.cc

@@ -494,9 +494,6 @@ bool Dhcpv6Srv::run() {
                 LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                     .arg(e.what());
             }
-
-            // Send NameChangeRequests to the b10-dhcp-ddns module.
-            sendNameChangeRequests();
         }
     }
 
@@ -1077,18 +1074,19 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
         // Get the IP address from the lease. Also, use the S flag to determine
         // if forward change should be performed. This flag will always be
         // set if server has taken responsibility for the forward update.
-        NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD,
-                              opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S),
-                              true, opt_fqdn->getDomainName(),
-                              iaaddr->getAddress().toText(),
-                              dhcid, 0, iaaddr->getValid());
-        // Add the request to the queue. This queue will be read elsewhere in
-        // the code and all requests from this queue will be sent to the
-        // D2 module.
-        name_change_reqs_.push(ncr);
+        NameChangeRequestPtr ncr;
+        ncr.reset(new NameChangeRequest(isc::dhcp_ddns::CHG_ADD,
+                                        opt_fqdn->getFlag(Option6ClientFqdn::
+                                                          FLAG_S),
+                                        true, opt_fqdn->getDomainName(),
+                                        iaaddr->getAddress().toText(),
+                                        dhcid, 0, iaaddr->getValid()));
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                  DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+                  DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr->toText());
+
+        // Post the NCR to the D2ClientMgr.
+        CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 
         /// @todo Currently we create NCR with the first IPv6 address that
         /// is carried in one of the IA_NAs. In the future, the NCR API should
@@ -1138,31 +1136,21 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
 
     }
     isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
-
     // Create a NameChangeRequest to remove the entry.
-    NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
-                          lease->fqdn_fwd_, lease->fqdn_rev_,
-                          lease->hostname_,
-                          lease->addr_.toText(),
-                          dhcid, 0, lease->valid_lft_);
-    name_change_reqs_.push(ncr);
+    NameChangeRequestPtr ncr;
+    ncr.reset(new NameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
+                                    lease->fqdn_fwd_, lease->fqdn_rev_,
+                                    lease->hostname_,
+                                    lease->addr_.toText(),
+                                    dhcid, 0, lease->valid_lft_));
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-              DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr.toText());
-
-}
+              DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr->toText());
 
-void
-Dhcpv6Srv::sendNameChangeRequests() {
-    while (!name_change_reqs_.empty()) {
-        // @todo Once next NameChangeRequest is picked from the queue
-        // we should send it to the b10-dhcp_ddns module. Currently we
-        // just drop it.
-        name_change_reqs_.pop();
-    }
+    // Post the NCR to the D2ClientMgr.
+    CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 }
 
-
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                        const Pkt6Ptr& query, const Pkt6Ptr& answer,
@@ -2455,5 +2443,29 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
     }
 }
 
+void
+Dhcpv6Srv::startD2() {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    if (d2_mgr.ddnsEnabled()) {
+        // Updates are enabled, so lets start the sender, passing in
+        // our error handler.
+        // This may throw so wherever this is called needs to ready.
+        d2_mgr.startSender(boost::bind(&Dhcpv6Srv::d2ClientErrorHandler,
+                                       this, _1, _2));
+    }
+}
+
+void
+Dhcpv6Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REQUEST_SEND_FAILED).
+              arg(result).arg((ncr ? ncr->toText() : " NULL "));
+    // We cannot communicate with b10-dhcp-ddns, suspend futher updates.
+    /// @todo We may wish to revisit this, but for now we will simpy turn
+    /// them off.
+    CfgMgr::instance().getD2ClientMgr().suspendUpdates();
+}
+
 };
 };

+ 26 - 11
src/bin/dhcp6/dhcp6_srv.h

@@ -24,6 +24,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <hooks/callout_handle.h>
 
@@ -117,6 +118,31 @@ public:
     /// @param port UDP port on which server should listen.
     static void openActiveSockets(const uint16_t port);
 
+    /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
+    ///
+    /// If updates are enabled, it Instructs the D2ClientMgr singleton to
+    /// enter send mode.  If D2ClientMgr encounters errors it may throw
+    /// D2ClientErrors. This method does not catch exceptions.
+    void startD2();
+
+    /// @brief Implements the error handler for DHCP_DDNS IO errors
+    ///
+    /// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with
+    /// a failed status.  These are communications errors, not data related
+    /// failures.
+    ///
+    /// This method logs the failure and then suspends all further updates.
+    /// Updating can only be restored by reconfiguration or restarting the
+    /// server.  There is currently no retry logic so the first IO error that
+    /// occurs will suspend updates.
+    /// @todo We may wish to make this more robust or sophisticated.
+    ///
+    /// @param result Result code of the send operation.
+    /// @param ncr NameChangeRequest which failed to send.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::
+                                      NameChangeSender::Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+
 protected:
 
     /// @brief Compare received server id with our server id
@@ -429,17 +455,6 @@ protected:
     /// records will be performed.
     void createRemovalNameChangeRequest(const Lease6Ptr& lease);
 
-    /// @brief Sends all outstanding NameChangeRequests to bind10-d2 module.
-    ///
-    /// The purpose of this function is to pick all outstanding
-    /// NameChangeRequests from the FIFO queue and send them to b10-dhcp-ddns
-    /// module.
-    ///
-    /// @todo Currently this function simply removes all requests from the
-    /// queue but doesn't send them anywhere. In the future, the
-    /// NameChangeSender will be used to deliver requests to the other module.
-    void sendNameChangeRequests();
-
     /// @brief Attempts to renew received addresses
     ///
     /// It iterates through received IA_NA options and attempts to renew

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -75,6 +75,7 @@ dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc

+ 381 - 0
src/bin/dhcp6/tests/d2_unittest.cc

@@ -0,0 +1,381 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @todo
+void
+D2Dhcpv6Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ++error_count_;
+    // call base class error handler
+    Dhcpv6Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp6SrvD2Test::SHOULD_PASS;
+const bool Dhcp6SrvD2Test::SHOULD_FAIL;
+
+Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
+}
+
+Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {
+    reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
+    // Build an NCR from json string.
+    std::ostringstream stream;
+
+    stream <<
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : false , "
+        " \"fqdn\" : \"myhost.example.com.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \""
+
+        << std::hex << std::setfill('0') << std::setw(16)
+        << dhcid_id_num << "\" , "
+
+        " \"lease_expires_on\" : \"20140121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
+}
+
+void
+Dhcp6SrvD2Test::reset() {
+    std::string config = "{ \"interfaces\": [ \"all\" ],"
+            "\"hooks-libraries\": [ ],"
+            "\"preferred-lifetime\": 3000,"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet6\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+    configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
+                            const std::string& ip_address,
+                            const uint32_t port) {
+    std::ostringstream config;
+    config <<
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
+        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-port\" : " << port << ", "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str(), exp_result);
+}
+
+void
+Dhcp6SrvD2Test::configure(const std::string& config, bool exp_result) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    if (exp_result == SHOULD_PASS) {
+        ASSERT_EQ(0, rcode);
+    } else {
+        ASSERT_EQ(1, rcode);
+    }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp6 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, enableDisable) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify a valid config with ddns enabled configures ddns properly,
+    // but does not start the sender.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start does not throw and starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify a valid config with ddns disabled configures ddns properly.
+    // Sender should not have been started.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false));
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that the sender does NOT get started when ddns is disabled.
+    srv_.startD2();
+    ASSERT_FALSE(mgr.amSending());
+}
+
+// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns configuration.
+// It does so by first enabling updates by submitting a valid configuration and then
+// ensuring they remain on after submitting a flawed configuration.
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, badConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now attempt to give it an invalid configuration.
+    // Result should indicate failure.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
+
+    // Configure was not altered, so ddns should be enabled and still sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly.  Not effect should be no change in
+// status for ddns updating.  Updates should still enabled and
+// in send mode.  This indicates that the sender was not stopped.
+TEST_F(Dhcp6SrvD2Test, sameConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now submit an identical configuration.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+
+    // Configuration was not altered, so ddns should still enabled and sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly.  Updates should be enabled, however they should
+// not yet be running.  This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp6SrvD2Test, differentConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now enable it on a different port.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
+
+    // Configuration was altered, so ddns should still enabled but not sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket.  Note this test does not employ any sort of receiving
+// client to verify actual transmission.  These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp6SrvD2Test, simpleUDPSend) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that we can queue up a message.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    EXPECT_EQ(1, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the queue is now empty.
+    EXPECT_EQ(0, mgr.getQueueSize());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates
+// being suspended.  This indicates that Dhcp6Srv's error handler has been
+// invoked as expected.  Note that this unit test relies on an attempt to send
+// to a server address of 0.0.0.0 port 0 fails under all OSs.
+TEST_F(Dhcp6SrvD2Test, forceUDPSendFailure) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    // Using server address of 0.0.0.0/0 should induce failure on send.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Queue up 3 messages.
+    for (int i = 0; i < 3; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send, which should
+    // fail.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Verify that we can't just restart it.
+    /// @todo This may change if we add ability to resume.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // This will finish sending the 1st message in queue
+    // and initiate send of 2nd message.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // First message is off the queue.
+    EXPECT_EQ(2, mgr.getQueueSize());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp6SrvD2Test, queueMaxError) {
+    // Configure it enabled and start it.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Attempt to queue more then the maximum allowed.
+    int max_msgs = mgr.getQueueMaxSize();
+    for (int i = 0; i < max_msgs + 1; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+
+    // Stopping sender will complete the first message so there
+    // should be max less one.
+    EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+

+ 117 - 0
src/bin/dhcp6/tests/d2_unittest.h

@@ -0,0 +1,117 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file d2_unittest.h Defines classes for testing Dhcpv6srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp6/dhcp6_srv.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv6Srv class used in D2 testing.
+/// Use of this class allows the intervention at strategic points in testing
+/// by permitting overridden methods and access to scope protected members.
+class D2Dhcpv6Srv : public  Dhcpv6Srv {
+public:
+    /// @brief Counts the number of times the client error handler is called.
+    int error_count_;
+
+    /// @brief Constructor
+    D2Dhcpv6Srv()
+        : Dhcpv6Srv(0), error_count_(0) {
+    }
+
+    /// @brief virtual Destructor.
+    virtual ~D2Dhcpv6Srv() {
+    }
+
+    /// @brief Override the error handler.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                      Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+};
+
+/// @brief Test fixture which permits testing the interaction between the
+/// D2ClientMgr and Dhcpv6Srv.
+class Dhcp6SrvD2Test : public ::testing::Test {
+public:
+    /// @brief Mnemonic constants for calls to configuration methods.
+    static const bool SHOULD_PASS = true;
+    static const bool SHOULD_FAIL = false;
+
+    /// @brief Constructor
+    Dhcp6SrvD2Test();
+
+    /// @brief virtual Destructor
+    virtual ~Dhcp6SrvD2Test();
+
+    /// @brief Resets the CfgMgr singleton to defaults.
+    /// Primarily used in the test destructor as gtest doesn't exit between
+    /// tests.
+    /// @todo CfgMgr should provide a method to reset everything or maybe
+    /// reconstruct the singleton.
+    void reset();
+
+    /// @brief Configures the server with D2 enabled or disabled
+    ///
+    /// Constructs a configuration string including dhcp-ddns with the
+    /// parameters given and passes it into the server's configuration handler.
+    ///
+    /// @param enable_updates value to assign to the enable-updates parameter
+    /// @param exp_result indicates if configuration should pass or fail
+    /// @param ip_address IP address for the D2 server
+    /// @param port  port for the D2 server
+    void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
+                     const std::string& ip_address = "127.0.0.1",
+                     const uint32_t port = 53001);
+
+    /// @brief Configures the server with the given configuration
+    ///
+    /// Passes the given configuration string into the server's configuration
+    /// handler.  It accepts a flag indicating whether or not the configuration
+    /// is expected to succeed or fail.  This permits testing the server's
+    /// response to both valid and invalid configurations.
+    ///
+    /// @param config JSON string containing the configuration
+    /// @param exp_result indicates if configuration should pass or fail
+    void configure(const std::string& config, bool exp_result = SHOULD_PASS);
+
+    /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+    ///
+    /// @param dhcid_id_num Integer value to use as the DHCID.
+    dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
+                                                 dhcid_id_num = 0xdeadbeef);
+
+    /// @brief Stores the return code of the last configuration attempt.
+    int rcode_;
+
+    /// @brief Stores the message component of the last configuration tattempt.
+    isc::data::ConstElementPtr comment_;
+
+    /// @brief Server object under test.
+    D2Dhcpv6Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H

+ 100 - 140
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -43,6 +43,9 @@ namespace {
 /// @brief A test fixture class for testing DHCPv6 Client FQDN Option handling.
 class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
 public:
+    /// Pointer to Dhcpv6Srv that is used in tests
+    boost::scoped_ptr<NakedDhcpv6Srv> srv_;
+
     // Reference to D2ClientMgr singleton
     D2ClientMgr& d2_mgr_;
 
@@ -55,7 +58,8 @@ public:
 
     /// @brief Constructor
     FqdnDhcpv6SrvTest()
-        : Dhcpv6SrvTest(), d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
+        : Dhcpv6SrvTest(), srv_(new NakedDhcpv6Srv(0)),
+          d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
         // generateClientId assigns DUID to duid_.
         generateClientId();
         lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
@@ -100,6 +104,7 @@ public:
                                   (mask & REPLACE_CLIENT_NAME),
                                   "myhost", "example.com")));
         ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+        ASSERT_NO_THROW(srv_->startD2());
     }
 
     /// @brief Construct the DHCPv6 Client FQDN option using flags and
@@ -174,12 +179,9 @@ public:
     /// server id.
     ///
     /// @param msg_type A type of the message to be created.
-    /// @param srv An object representing the DHCPv6 server, which
-    /// is used to generate the client identifier.
     ///
     /// @return An object representing the created message.
-    Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
-                                   NakedDhcpv6Srv& srv) {
+    Pkt6Ptr generateMessageWithIds(const uint8_t msg_type) {
         Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
         // Generate client-id.
         OptionPtr opt_clientid = generateClientId();
@@ -187,7 +189,7 @@ public:
 
         if (msg_type != DHCPV6_SOLICIT) {
             // Generate server-id.
-            pkt->addOption(srv.getServerID());
+            pkt->addOption(srv_->getServerID());
         }
 
         return (pkt);
@@ -286,7 +288,7 @@ public:
                   const Option6ClientFqdn::DomainNameType in_domain_type,
                   const uint8_t exp_flags,
                   const std::string& exp_domain_name) {
-        NakedDhcpv6Srv srv(0);
+
         Pkt6Ptr question = generateMessage(msg_type,
                                            in_flags,
                                            in_domain_name,
@@ -296,7 +298,7 @@ public:
 
         Pkt6Ptr answer(new Pkt6(msg_type == DHCPV6_SOLICIT ? DHCPV6_ADVERTISE :
                                 DHCPV6_REPLY, question->getTransid()));
-        ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
+        ASSERT_NO_THROW(srv_->processClientFqdn(question, answer));
         Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
             Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
         ASSERT_TRUE(answ_fqdn);
@@ -330,7 +332,6 @@ public:
     ///
     /// @param msg_type A type of the client's message.
     /// @param hostname A domain name in the client's FQDN.
-    /// @param srv A server object, used to process the message.
     /// @param include_oro A boolean value which indicates whether the ORO
     /// option should be included in the client's message (if true) or not
     /// (if false). In the former case, the function will expect that server
@@ -339,11 +340,10 @@ public:
     void testProcessMessage(const uint8_t msg_type,
                             const std::string& hostname,
                             const std::string& exp_hostname,
-                            NakedDhcpv6Srv& srv,
                             const bool include_oro = true) {
         // Create a message of a specified type, add server id and
         // FQDN option.
-        OptionPtr srvid = srv.getServerID();
+        OptionPtr srvid = srv_->getServerID();
         // Set the appropriate FQDN type. It must be partial if hostname is
         // empty.
         Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ?
@@ -356,18 +356,18 @@ public:
         // functions to generate response.
         Pkt6Ptr reply;
         if (msg_type == DHCPV6_SOLICIT) {
-            ASSERT_NO_THROW(reply = srv.processSolicit(req));
+            ASSERT_NO_THROW(reply = srv_->processSolicit(req));
 
         } else if (msg_type == DHCPV6_REQUEST) {
-            ASSERT_NO_THROW(reply = srv.processRequest(req));
+            ASSERT_NO_THROW(reply = srv_->processRequest(req));
 
         } else if (msg_type == DHCPV6_RENEW) {
-            ASSERT_NO_THROW(reply = srv.processRequest(req));
+            ASSERT_NO_THROW(reply = srv_->processRequest(req));
 
         } else if (msg_type == DHCPV6_RELEASE) {
             // For Release no lease will be acquired so we have to leave
             // function here.
-            ASSERT_NO_THROW(reply = srv.processRelease(req));
+            ASSERT_NO_THROW(reply = srv_->processRelease(req));
             return;
         } else {
             // We are not interested in testing other message types.
@@ -413,7 +413,6 @@ public:
     /// queue and checks that it holds valid parameters. The NameChangeRequest
     /// is removed from the queue.
     ///
-    /// @param srv A server object holding a queue of NameChangeRequests.
     /// @param type An expected type of the NameChangeRequest (Add or Remove).
     /// @param reverse An expected setting of the reverse update flag.
     /// @param forward An expected setting of the forward udpate flag.
@@ -431,23 +430,27 @@ public:
     /// NameChangeRequest expires.
     /// @param len A valid lifetime of the lease associated with the
     /// NameChangeRequest.
-    void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
-                                 const isc::dhcp_ddns::NameChangeType type,
+    void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
                                  const bool reverse, const bool forward,
                                  const std::string& addr,
                                  const std::string& dhcid,
                                  const uint16_t expires,
                                  const uint16_t len) {
-        NameChangeRequest ncr = srv.name_change_reqs_.front();
-        EXPECT_EQ(type, ncr.getChangeType());
-        EXPECT_EQ(forward, ncr.isForwardChange());
-        EXPECT_EQ(reverse, ncr.isReverseChange());
-        EXPECT_EQ(addr, ncr.getIpAddress());
-        EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
-        EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
-        EXPECT_EQ(len, ncr.getLeaseLength());
-        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
-        srv.name_change_reqs_.pop();
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+        ASSERT_TRUE(ncr);
+
+        EXPECT_EQ(type, ncr->getChangeType());
+        EXPECT_EQ(forward, ncr->isForwardChange());
+        EXPECT_EQ(reverse, ncr->isReverseChange());
+        EXPECT_EQ(addr, ncr->getIpAddress());
+        EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
+        EXPECT_EQ(expires, ncr->getLeaseExpiresOn());
+        EXPECT_EQ(len, ncr->getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+
+        // Process the message off the queue
+        ASSERT_NO_THROW(d2_mgr_.runReadyIO());
     }
 
     // Holds a lease used by a test.
@@ -504,11 +507,9 @@ TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
 // Test that exception is thrown if supplied NULL answer packet when
 // creating NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
-    NakedDhcpv6Srv srv(0);
-
     Pkt6Ptr answer;
 
-    EXPECT_THROW(srv.createNameChangeRequests(answer),
+    EXPECT_THROW(srv_->createNameChangeRequests(answer),
                  isc::Unexpected);
 
 }
@@ -516,39 +517,33 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
 // Test that exception is thrown if supplied answer from the server
 // contains no DUID when creating NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
-    NakedDhcpv6Srv srv(0);
-
     Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
                                                  "myhost.example.com",
                                                  Option6ClientFqdn::FULL);
     answer->addOption(fqdn);
 
-    EXPECT_THROW(srv.createNameChangeRequests(answer), isc::Unexpected);
+    EXPECT_THROW(srv_->createNameChangeRequests(answer), isc::Unexpected);
 
 }
 
 // Test no NameChangeRequests if Client FQDN is not added to the server's
 // response.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
 
     // There should be no new NameChangeRequests.
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that NameChangeRequests are not generated if an answer message
 // contains no addresses.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
     // Add Client FQDN option.
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
@@ -556,20 +551,18 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
                                                  Option6ClientFqdn::FULL);
     answer->addOption(fqdn);
 
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
 
     // We didn't add any IAs, so there should be no NameChangeRequests in th
     // queue.
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that exactly one NameChangeRequest is created as a result of processing
 // the answer message which holds 3 IAs and when FQDN is specified.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
-    NakedDhcpv6Srv srv(0);
-
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
     // Create three IAs, each having different address.
     addIA(1234, IOAddress("2001:db8:1::1"), answer);
@@ -585,11 +578,11 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
     answer->addOption(fqdn);
 
     // Create NameChangeRequest for the first allocated address.
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     // Verify that NameChangeRequest is correct.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -600,13 +593,11 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
 // Checks that NameChangeRequests to add entries are not
 // created when ddns updates are disabled.
 TEST_F(FqdnDhcpv6SrvTest, noAddRequestsWhenDisabled) {
-    NakedDhcpv6Srv srv(0);
-
     // Disable DDNS udpates.
     disableD2();
 
     // Create Reply message with Client Id and Server id.
-    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
 
     // Create three IAs, each having different address.
     addIA(1234, IOAddress("2001:db8:1::1"), answer);
@@ -619,17 +610,14 @@ TEST_F(FqdnDhcpv6SrvTest, noAddRequestsWhenDisabled) {
                                                  Option6ClientFqdn::FULL);
     answer->addOption(fqdn);
 
-    // Create NameChangeRequest for the first allocated address.
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+    // An attempt to send a NCR would throw.
+    ASSERT_NO_THROW(srv_->createNameChangeRequests(answer));
 }
 
 
 // Test creation of the NameChangeRequest to remove both forward and reverse
 // mapping for the given lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     // Part of the domain name is in upper case, to test that it gets converted
@@ -637,11 +625,10 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
     // as if we typed domain-name in lower case.
     lease_->hostname_ = "MYHOST.example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
-
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -652,8 +639,6 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
 // Checks that NameChangeRequests to remove entries are not created
 // when ddns updates are disabled.
 TEST_F(FqdnDhcpv6SrvTest, noRemovalsWhenDisabled) {
-    NakedDhcpv6Srv srv(0);
-
     // Disable DDNS updates.
     disableD2();
 
@@ -664,26 +649,23 @@ TEST_F(FqdnDhcpv6SrvTest, noRemovalsWhenDisabled) {
     // as if we typed domain-name in lower case.
     lease_->hostname_ = "MYHOST.example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
-
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+    // When DDNS is disabled an attempt to send a request will throw.
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 }
 
 
 // Test creation of the NameChangeRequest to remove reverse mapping for the
 // given lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = false;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "myhost.example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -694,29 +676,25 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
 // Test that NameChangeRequest to remove DNS records is not generated when
 // neither forward nor reverse DNS update has been performed for a lease.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = false;
     lease_->fqdn_rev_ = false;
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
 // Test that NameChangeRequest is not generated if the hostname hasn't been
 // specified for a lease for which forward and reverse mapping has been set.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
@@ -724,28 +702,24 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
 // been specified for a lease for which forward and reverse mapping has been
 // set.
 TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) {
-    NakedDhcpv6Srv srv(0);
-
     lease_->fqdn_fwd_ = true;
     lease_->fqdn_rev_ = true;
     lease_->hostname_ = "myhost..example.com.";
 
-    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+    ASSERT_NO_THROW(srv_->createRemovalNameChangeRequest(lease_));
 
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
 // Test that Advertise message generated in a response to the Solicit will
 // not result in generation if the NameChangeRequests.
 TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Solicit message with FQDN option and generate server's
     // response using processSolicit function.
     testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    EXPECT_TRUE(srv.name_change_reqs_.empty());
+                       "myhost.example.com.");
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that client may send two requests, each carrying FQDN option with
@@ -753,16 +727,14 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -776,14 +748,14 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -797,16 +769,14 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
 // DNS if the Request was sent instead of Soicit. The code should differentiate
 // behavior depending whether Solicit or Request is sent.
 TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -817,8 +787,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
     // The NameChangeRequest should only be generated when a client sends
     // Request or Renew.
     testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_TRUE(srv.name_change_reqs_.empty());
+                       "otherhost.example.com.");
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 
 }
 
@@ -829,16 +799,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
 // DNS entry added previously when Request was processed, another one to
 // add a new entry for the FQDN held in the Renew.
 TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -852,14 +820,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -868,16 +836,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
 }
 
 TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
-    NakedDhcpv6Srv srv(0);
-
     // Create a Request message with FQDN option and generate server's
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -888,9 +854,9 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // also removed. Therefore, we expect that single NameChangeRequest to
     // remove DNS entries is generated.
     testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
-                       "otherhost.example.com.", srv);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                       "otherhost.example.com.");
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -901,15 +867,13 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
 // Checks that the server include DHCPv6 Client FQDN option in its
 // response even when client doesn't request this option using ORO.
 TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
-    NakedDhcpv6Srv srv(0);
-
     // The last parameter disables use of the ORO to request FQDN option
     // In this case, we expect that the FQDN option will not be included
     // in the server's response. The testProcessMessage will check that.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv, false);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost.example.com.", false);
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -919,13 +883,10 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
 // Checks that FQDN is generated from an ip address, when client sends an empty
 // FQDN.
 TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
-    NakedDhcpv6Srv srv(0);
-
     testProcessMessage(DHCPV6_REQUEST, "",
-                       "myhost-2001-db8-1-1--dead-beef.example.com.",
-                       srv, false);
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                       "myhost-2001-db8-1-1--dead-beef.example.com.", false);
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201C905E54BE12DE6AF92ADE72752B9F362"
                             "13B5A8BC9D217548CD739B4CF31AFB1B",
@@ -951,12 +912,11 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     CfgMgr::instance().addSubnet6(subnet_);
 
     // Allocate a lease.
-    NakedDhcpv6Srv srv(0);
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
-                       "myhost.example.com.", srv);
+                       "myhost.example.com.");
     // Test that the appropriate NameChangeRequest has been generated.
-    ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -986,18 +946,18 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     // lease database, it is guaranteed that the allocation engine will
     // reuse this lease.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
-                       "myhost.example.com.", srv);
-    ASSERT_EQ(2, srv.name_change_reqs_.size());
+                       "myhost.example.com.");
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     // The first name change request generated, should remove a DNS
     // mapping for an expired lease.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD2"
                             "65FCEA97F93623019B2E0D14E5323D5A",
                             0, 5);
     // The second name change request should add a DNS mapping for
     // a new lease.
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",