]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1386] D2 supports add and remove without conflict resolution
authorThomas Markwalder <tmark@isc.org>
Thu, 15 Oct 2020 19:03:59 +0000 (15:03 -0400)
committerThomas Markwalder <tmark@isc.org>
Wed, 21 Oct 2020 18:00:22 +0000 (14:00 -0400)
src/lib/dhcp_ddns/ncr_msg.*
    Added NameChangeRequest::conflict_resolution_

src/bin/d2/d2_update_mgr.cc
    D2UpdateMgr::makeTransaction() - use's the NCR's value for
    use-conflict-resolution() in determining which type of transacation
    to create.

src/bin/d2/simple_add.* - new files
    SimpleAddTransaction() - new class which executes a DNS add
    without conflict resolution.

src/bin/d2/simple_remove.* - new files
    SimpleRemoveTransaction() - new class which executes a DNS remove
    without conflict resolution.

src/bin/d2/tests/simple_add_unittests.cc - new file
    Tests SimpleAddTransaction

src/bin/d2/tests/simple_remove_unittests.cc - new file
    Tests SimpleRemoveTransaction

src/bin/dhcp4/tests/d2_unittest.cc
src/bin/dhcp6/tests/d2_unittest.cc
src/bin/d2/tests/d2_process_unittests.cc
src/bin/d2/tests/d2_queue_mgr_unittests.cc
src/bin/d2/tests/d2_update_mgr_unittests.cc
src/bin/d2/tests/nc_add_unittests.cc
src/bin/d2/tests/nc_remove_unittests.cc
src/bin/d2/tests/nc_trans_unittests.cc
src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
src/lib/dhcp_ddns/tests/ncr_unittests.cc
src/lib/dhcpsrv/tests/d2_udp_unittest.cc
    updated tests

24 files changed:
src/bin/d2/Makefile.am
src/bin/d2/d2_update_mgr.cc
src/bin/d2/simple_add.cc [new file with mode: 0644]
src/bin/d2/simple_add.h [new file with mode: 0644]
src/bin/d2/simple_remove.cc [new file with mode: 0644]
src/bin/d2/simple_remove.h [new file with mode: 0644]
src/bin/d2/tests/Makefile.am
src/bin/d2/tests/d2_process_unittests.cc
src/bin/d2/tests/d2_queue_mgr_unittests.cc
src/bin/d2/tests/d2_update_mgr_unittests.cc
src/bin/d2/tests/nc_add_unittests.cc
src/bin/d2/tests/nc_remove_unittests.cc
src/bin/d2/tests/nc_test_utils.cc
src/bin/d2/tests/nc_test_utils.h
src/bin/d2/tests/nc_trans_unittests.cc
src/bin/d2/tests/simple_add_unittests.cc [new file with mode: 0644]
src/bin/d2/tests/simple_remove_unittests.cc [new file with mode: 0644]
src/bin/dhcp4/tests/d2_unittest.cc
src/bin/dhcp6/tests/d2_unittest.cc
src/lib/dhcp_ddns/ncr_msg.cc
src/lib/dhcp_ddns/ncr_msg.h
src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
src/lib/dhcp_ddns/tests/ncr_unittests.cc
src/lib/dhcpsrv/tests/d2_udp_unittest.cc

index e1c27c0f8cca9e7a3da56d5b19d6d3a9107d36d3..24ff5a557adb214a2109378c0eeba29ce714c3a0 100644 (file)
@@ -47,6 +47,8 @@ libd2_la_SOURCES += nc_trans.cc nc_trans.h
 libd2_la_SOURCES += d2_controller.cc d2_controller.h
 libd2_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 libd2_la_SOURCES += d2_messages.h d2_messages.cc
+libd2_la_SOURCES += simple_add.cc simple_add.h
+libd2_la_SOURCES += simple_remove.cc simple_remove.h
 EXTRA_DIST += d2_messages.mes
 
 sbin_PROGRAMS = kea-dhcp-ddns
index 322dd1f31e8d9fd0a9e752f88fc1201664777315..28bc25df39092943723a9278ba172bd477729e78 100644 (file)
@@ -9,6 +9,8 @@
 #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 <sstream>
 #include <iostream>
@@ -197,13 +199,25 @@ D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
     // the transaction's IO.
     NameChangeTransactionPtr trans;
     if (next_ncr->getChangeType() == dhcp_ddns::CHG_ADD) {
-        trans.reset(new NameAddTransaction(io_service_, next_ncr,
-                                           forward_domain, reverse_domain,
-                                           cfg_mgr_));
+        if (next_ncr->useConflictResolution()) {
+            trans.reset(new NameAddTransaction(io_service_, next_ncr,
+                                               forward_domain, reverse_domain,
+                                               cfg_mgr_));
+        } else {
+            trans.reset(new SimpleAddTransaction(io_service_, next_ncr,
+                                                 forward_domain, reverse_domain,
+                                                 cfg_mgr_));
+        }
     } else {
-        trans.reset(new NameRemoveTransaction(io_service_, next_ncr,
-                                              forward_domain, reverse_domain,
-                                              cfg_mgr_));
+        if (next_ncr->useConflictResolution()) {
+            trans.reset(new NameRemoveTransaction(io_service_, next_ncr,
+                                                  forward_domain, reverse_domain,
+                                                  cfg_mgr_));
+        } else {
+            trans.reset(new SimpleRemoveTransaction(io_service_, next_ncr,
+                                                    forward_domain, reverse_domain,
+                                                    cfg_mgr_));
+        }
     }
 
     // Add the new transaction to the list.
diff --git a/src/bin/d2/simple_add.cc b/src/bin/d2/simple_add.cc
new file mode 100644 (file)
index 0000000..652966f
--- /dev/null
@@ -0,0 +1,544 @@
+// Copyright (C) 2020 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/simple_add.h>
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+// SimpleAddTransaction states
+const int SimpleAddTransaction::REPLACING_FWD_ADDRS_ST;
+const int SimpleAddTransaction::REPLACING_REV_PTRS_ST;
+
+// SimpleAddTransaction events
+const int SimpleAddTransaction::FQDN_IN_USE_EVT;
+const int SimpleAddTransaction::FQDN_NOT_IN_USE_EVT;
+
+SimpleAddTransaction::
+SimpleAddTransaction(asiolink::IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain,
+                   D2CfgMgrPtr& cfg_mgr)
+    : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+                            cfg_mgr) {
+    if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
+        isc_throw (SimpleAddTransactionError,
+                   "SimpleAddTransaction, request type must be CHG_ADD");
+    }
+}
+
+SimpleAddTransaction::~SimpleAddTransaction(){
+}
+
+void
+SimpleAddTransaction::defineEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineEvents();
+
+    // Define SimpleAddTransaction events.
+    defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
+    defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
+}
+
+void
+SimpleAddTransaction::verifyEvents() {
+    // Call superclass implementation first to verify its events. These are
+    // events common to all transactions, and they must be defined.
+    // SELECT_SERVER_EVT
+    // SERVER_SELECTED_EVT
+    // SERVER_IO_ERROR_EVT
+    // NO_MORE_SERVERS_EVT
+    // IO_COMPLETED_EVT
+    // UPDATE_OK_EVT
+    // UPDATE_FAILED_EVT
+    NameChangeTransaction::verifyEvents();
+
+    // Verify SimpleAddTransaction events by attempting to fetch them.
+    getEvent(FQDN_IN_USE_EVT);
+    getEvent(FQDN_NOT_IN_USE_EVT);
+}
+
+void
+SimpleAddTransaction::defineStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineStates();
+
+    // Define SimpleAddTransaction states.
+    defineState(READY_ST, "READY_ST",
+             std::bind(&SimpleAddTransaction::readyHandler, this));
+
+    defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+             std::bind(&SimpleAddTransaction::selectingFwdServerHandler, this));
+
+    defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+             std::bind(&SimpleAddTransaction::selectingRevServerHandler, this));
+
+    defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST",
+             std::bind(&SimpleAddTransaction::replacingFwdAddrsHandler, this));
+
+    defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST",
+             std::bind(&SimpleAddTransaction::replacingRevPtrsHandler, this));
+
+    defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+             std::bind(&SimpleAddTransaction::processAddOkHandler, this));
+
+    defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+             std::bind(&SimpleAddTransaction::processAddFailedHandler, this));
+}
+
+void
+SimpleAddTransaction::verifyStates() {
+    // Call superclass implementation first to verify its states. These are
+    // states common to all transactions, and they must be defined.
+    // READY_ST
+    // SELECTING_FWD_SERVER_ST
+    // SELECTING_REV_SERVER_ST
+    // PROCESS_TRANS_OK_ST
+    // PROCESS_TRANS_FAILED_ST
+    NameChangeTransaction::verifyStates();
+
+    // Verify SimpleAddTransaction states by attempting to fetch them.
+    getStateInternal(REPLACING_FWD_ADDRS_ST);
+    getStateInternal(REPLACING_REV_PTRS_ST);
+}
+
+void
+SimpleAddTransaction::readyHandler() {
+    switch(getNextEvent()) {
+    case START_EVT:
+        if (getForwardDomain()) {
+            // Request includes a forward change, do that first.
+            transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+        } else {
+            // Reverse change only, transition accordingly.
+            transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+        }
+
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleAddTransaction::selectingFwdServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getForwardDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REPLACING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+void
+SimpleAddTransaction::replacingFwdAddrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildReplaceFwdAddressRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE)
+                          .arg(getRequestId())
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate("Forward Add");
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if (rcode == dns::Rcode::NOERROR()) {
+                // We were able to add it. Mark it as done.
+                setForwardChangeCompleted(true);
+
+                // If request calls for reverse update then do that next,
+                // otherwise we can process ok.
+                if (getReverseDomain()) {
+                    transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+                } else {
+                    transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+                }
+            } else {
+                // Any other value means cease. Really shouldn't happen.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_REJECTED)
+                          .arg(getRequestId())
+                          .arg(getCurrentServer()->toText())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR)
+                      .arg(getRequestId())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT)
+                      .arg(getRequestId())
+                      .arg(getCurrentServer()->toText())
+                      .arg(getNcr()->getFqdn());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS)
+                      .arg(getRequestId())
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleAddTransaction::selectingRevServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getReverseDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REPLACING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+
+void
+SimpleAddTransaction::replacingRevPtrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildReplaceRevPtrsRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE)
+                          .arg(getRequestId())
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate("Reverse Replace");
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if (rcode == dns::Rcode::NOERROR()) {
+                // We were able to update the reverse mapping. Mark it as done.
+                setReverseChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED)
+                          .arg(getRequestId())
+                          .arg(getCurrentServer()->toText())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR)
+                      .arg(getRequestId())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT)
+                      .arg(getRequestId())
+                      .arg(getCurrentServer()->toText())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(d2_to_dns_logger,
+                      DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS)
+                      .arg(getRequestId())
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleAddTransaction::processAddOkHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+        LOG_INFO(d2_to_dns_logger, DHCP_DDNS_ADD_SUCCEEDED)
+                 .arg(getRequestId())
+                 .arg(getNcr()->toText());
+        setNcrStatus(dhcp_ddns::ST_COMPLETED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleAddTransaction::processAddFailedHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_FAILED_EVT:
+    case NO_MORE_SERVERS_EVT:
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_ADD_FAILED)
+                  .arg(getRequestId())
+                  .arg(transactionOutcomeString());
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleAddTransaction::buildReplaceFwdAddressRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+    // Construct dns::Name from NCR fqdn.
+    dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+    // There are no prerequisites.
+
+    // Build the Update Section.  First we delete any pre-existing
+    // FQDN/IP and DHCID RRs.  Then we add new ones.
+
+    // Create the FQDN/IP 'delete' RR and add it to update section.
+    dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+                         getAddressRRType(), dns::RRTTL(0)));
+
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Create the DHCID 'delete' RR and add it to the update section.
+    update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(),
+                                dns::RRType::DHCID(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Now make the new RRs.
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+    // Create the FQDN/IP 'add' RR and add it to the to update section.
+    // Based on RFC 2136, section 2.5.1
+    update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+                                getAddressRRType(), lease_ttl));
+
+    addLeaseAddressRdata(update);
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Now create the FQDN/DHCID 'add' RR and add it to update section.
+    // Based on RFC 2136, section 2.5.1
+    update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+                                dns::RRType::DHCID(), lease_ttl));
+
+    // We add the DHCID for auditing purposes and in the event
+    // conflict resoloution is later enabled.
+    addDhcidRdata(update);
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+void
+SimpleAddTransaction::buildReplaceRevPtrsRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+    // Create the reverse IP address "FQDN".
+    std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+    dns::Name rev_ip(rev_addr);
+
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+    // There are no prerequisites.
+
+    // Create the FQDN/IP PTR 'delete' RR for this IP and add it to 
+    // the update section.
+    dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+                         dns::RRType::PTR(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Create the DHCID 'delete' RR and add it to the update section.
+    update.reset(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+                                dns::RRType::DHCID(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata
+    // then add it to update section.
+    update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+                                dns::RRType::PTR(), lease_ttl));
+    addPtrRdata(update);
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata
+    // then add it to update section.
+    update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+                                dns::RRType::DHCID(), lease_ttl));
+    addDhcidRdata(update);
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/simple_add.h b/src/bin/d2/simple_add.h
new file mode 100644 (file)
index 0000000..25948ad
--- /dev/null
@@ -0,0 +1,358 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_ADD_H
+#define SIMPLE_ADD_H
+
+/// @file nc_add.h This file defines the class SimpleAddTransaction.
+
+#include <d2/nc_trans.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the SimpleAddTransaction encounters a general error.
+class SimpleAddTransactionError : public isc::Exception {
+public:
+    SimpleAddTransactionError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Add update.
+///
+/// SimpleAddTransaction implements a state machine for adding (or replacing) a
+/// forward and/or reverse DNS mapping. This state machine is based upon the
+/// processing logic described in RFC 4703, Sections 5.3 and 5.4.  That logic
+/// may be paraphrased as follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+///     Select a forward server
+///     Send the server a request to add the forward entry
+///     If the server responds with already in use:
+///         Send a server a request to delete and then add forward entry
+///
+///     If the forward update is unsuccessful:
+///         abandon the update
+///
+/// If the request includes a reverse change:
+///     Select a reverse server
+///     Send a server a request to delete and then add reverse entry
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class SimpleAddTransaction : public NameChangeTransaction {
+public:
+
+    //@{  Additional states needed for SimpleAdd state model.
+    /// @brief State that attempts to add forward address records.
+    static const int REPLACING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+    /// @brief State that attempts to replace reverse PTR records
+    static const int REPLACING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+    //@}
+
+    //@{ Additional events needed for SimpleAdd state model.
+    /// @brief Event sent when an add attempt fails with address in use.
+    static const int FQDN_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 1;
+
+    /// @brief Event sent when replace attempt to fails with address not in use.
+    static const int FQDN_NOT_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+    //@}
+
+    /// @brief Constructor
+    ///
+    /// Instantiates an Add transaction that is ready to be started.
+    ///
+    /// @param io_service IO service to be used for IO processing
+    /// @param ncr is the NameChangeRequest to fulfill
+    /// @param forward_domain is the domain to use for forward DNS updates
+    /// @param reverse_domain is the domain to use for reverse DNS updates
+    /// @param cfg_mgr pointer to the configuration manager
+    ///
+    /// @throw SimpleAddTransaction error if given request is not a CHG_ADD,
+    /// NameChangeTransaction error for base class construction errors.
+    SimpleAddTransaction(asiolink::IOServicePtr& io_service,
+                       dhcp_ddns::NameChangeRequestPtr& ncr,
+                       DdnsDomainPtr& forward_domain,
+                       DdnsDomainPtr& reverse_domain,
+                       D2CfgMgrPtr& cfg_mgr);
+
+    /// @brief Destructor
+    virtual ~SimpleAddTransaction();
+
+protected:
+    /// @brief Adds events defined by SimpleAddTransaction to the event set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// events unique to NCR Add transaction processing.
+    ///
+    /// @throw StateModelError if an event definition is invalid or a duplicate.
+    virtual void defineEvents();
+
+    /// @brief Validates the contents of the set of events.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Add transaction's.  This tests that the needed events are in the event
+    /// dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyEvents();
+
+    /// @brief Adds states defined by SimpleAddTransaction to the state set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// states unique to NCR Add transaction processing.
+    ///
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
+    virtual void defineStates();
+
+    /// @brief Validates the contents of the set of states.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Add transaction's states. This tests that the needed states are in the
+    /// state dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
+
+    /// @brief State handler for READY_ST.
+    ///
+    /// Entered from:
+    /// - INIT_ST with next event of START_EVT
+    ///
+    /// The READY_ST is the state the model transitions into when the inherited
+    /// method, startTransaction() is invoked.  This handler, therefore, is the
+    /// entry point into the state model execution.h  Its primary task is to
+    /// determine whether to start with a forward DNS change or a reverse DNS
+    /// change.
+    ///
+    /// Transitions to:
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes a forward change.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes only a reverse change.
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not
+    /// START_EVT.
+    void readyHandler();
+
+    /// @brief State handler for SELECTING_FWD_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the forward domain for the forward
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the forward domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon successful
+    /// server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingFwdServerHandler();
+
+    /// @brief State handler for SELECTING_REV_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+    /// - REPLACING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the reverse domain for the reverse
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the reverse domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REPLACING_REV_PTRS_ST with next event of SERVER_SELECTED upon successful
+    /// server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingRevServerHandler();
+
+    /// @brief State handler for REPLACING_FWD_ADDRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to replace a forward DNS entry for a given FQDN.  If this is
+    /// first invocation of the handler after transitioning into this state,
+    /// any previous update request context is deleted.   If next event
+    /// is SERVER_SELECTED_EVT, the handler builds the forward add request,
+    /// schedules an asynchronous send via sendUpdate(), and returns.  Note
+    /// that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - SELECTING_REV_SERVER_ST with next event of SELECT_SERVER_EVT upon
+    /// successful addition and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// addition and no reverse DNS update is required.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any other reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this states with next event of SERVER_SELECTED_EVT_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not
+    /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+    void replacingFwdAddrsHandler();
+
+    /// @brief State handler for REPLACING_REV_PTRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to delete and then add a reverse DNS entry for a given FQDN.
+    /// If this is first invocation of the handler after transitioning into
+    /// this state, any previous update request context is deleted.  If next
+    /// event is SERVER_SELECTED_EVT, the handler builds the reverse replacement
+    /// add request, schedules an asynchronous send via sendUpdate(), and
+    /// returns.  Note that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+    /// successful replacement.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+    /// DNS server rejected the update for any reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not:
+    /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+    void replacingRevPtrsHandler();
+
+    /// @brief State handler for PROCESS_TRANS_OK_ST.
+    ///
+    /// Entered from:
+    /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+    /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+    ///
+    /// Sets the transaction action status to indicate success and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of END_EVT.
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT
+    void processAddOkHandler();
+
+    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+    ///
+    /// Sets the transaction status to indicate failure and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of FAIL_EVT.
+    ///
+    /// @throw SimpleAddTransactionError if upon entry next event is not:
+    /// UPDATE_FAILED_EVT
+    void processAddFailedHandler();
+
+    /// @brief Builds a DNS request to add/replace a forward DNS entry for an FQDN
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for adding a
+    /// forward DNS mapping.  Once constructed, the request is stored as
+    /// the transaction's DNS update request.
+    ///
+    /// Prerequisite RRsets:
+    /// - There are no prerequisites.
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of all RRs for the given FQDN
+    /// 1. An FQDN/IP RR addition    (type A for IPv4, AAAA for IPv6)
+    /// 2. An FQDN/DHCID RR addition (type DHCID)
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildReplaceFwdAddressRequest();
+
+    /// @brief Builds a DNS request to replace a reverse DNS entry for an FQDN
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for replacing a
+    /// reverse DNS mapping.  Once constructed, the request is stored as
+    /// the transaction's DNS update request.
+    ///
+    /// Prerequisite RRsets:
+    /// - There are no prerequisites.
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of any existing PTR RRs for the lease address
+    /// 2. A delete of any existing DHCID RRs for the lease address
+    /// 3. A PTR RR addition for the lease address and FQDN
+    /// 4. A DHCID RR addition for the lease address and lease client DHCID
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildReplaceRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a SimpleAddTransaction.
+typedef boost::shared_ptr<SimpleAddTransaction> SimpleAddTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/simple_remove.cc b/src/bin/d2/simple_remove.cc
new file mode 100644 (file)
index 0000000..d914637
--- /dev/null
@@ -0,0 +1,530 @@
+// Copyright (C) 2013-2020 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/simple_remove.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+
+// SimpleRemoveTransaction states
+const int SimpleRemoveTransaction::REMOVING_FWD_RRS_ST;
+const int SimpleRemoveTransaction::REMOVING_REV_PTRS_ST;
+
+// SimpleRemoveTransaction events
+// Currently SimpleRemoveTransaction does not define any events.
+
+SimpleRemoveTransaction::
+SimpleRemoveTransaction(asiolink::IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain,
+                   D2CfgMgrPtr& cfg_mgr)
+    : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+                            cfg_mgr) {
+    if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
+        isc_throw (SimpleRemoveTransactionError,
+                   "SimpleRemoveTransaction, request type must be CHG_REMOVE");
+    }
+}
+
+SimpleRemoveTransaction::~SimpleRemoveTransaction(){
+}
+
+void
+SimpleRemoveTransaction::defineEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineEvents();
+
+    // Define SimpleRemoveTransaction events.
+    // Currently SimpleRemoveTransaction does not define any events.
+    // defineEvent(TBD_EVENT, "TBD_EVT");
+}
+
+void
+SimpleRemoveTransaction::verifyEvents() {
+    // Call superclass implementation first to verify its events. These are
+    // events common to all transactions, and they must be defined.
+    // SELECT_SERVER_EVT
+    // SERVER_SELECTED_EVT
+    // SERVER_IO_ERROR_EVT
+    // NO_MORE_SERVERS_EVT
+    // IO_COMPLETED_EVT
+    // UPDATE_OK_EVT
+    // UPDATE_FAILED_EVT
+    NameChangeTransaction::verifyEvents();
+
+    // Verify SimpleRemoveTransaction events by attempting to fetch them.
+    // Currently SimpleRemoveTransaction does not define any events.
+    // getEvent(TBD_EVENT);
+}
+
+void
+SimpleRemoveTransaction::defineStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineStates();
+
+    // Define SimpleRemoveTransaction states.
+    defineState(READY_ST, "READY_ST",
+                std::bind(&SimpleRemoveTransaction::readyHandler, this));
+
+    defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+                std::bind(&SimpleRemoveTransaction::selectingFwdServerHandler,
+                          this));
+
+    defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+                std::bind(&SimpleRemoveTransaction::selectingRevServerHandler,
+                          this));
+
+    defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST",
+                std::bind(&SimpleRemoveTransaction::removingFwdRRsHandler,
+                          this));
+
+    defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST",
+                std::bind(&SimpleRemoveTransaction::removingRevPtrsHandler,
+                          this));
+
+    defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+                std::bind(&SimpleRemoveTransaction::processRemoveOkHandler,
+                          this));
+
+    defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+                std::bind(&SimpleRemoveTransaction::processRemoveFailedHandler,
+                          this));
+}
+
+void
+SimpleRemoveTransaction::verifyStates() {
+    // Call superclass implementation first to verify its states. These are
+    // states common to all transactions, and they must be defined.
+    // READY_ST
+    // SELECTING_FWD_SERVER_ST
+    // SELECTING_REV_SERVER_ST
+    // PROCESS_TRANS_OK_ST
+    // PROCESS_TRANS_FAILED_ST
+    NameChangeTransaction::verifyStates();
+
+    // Verify SimpleRemoveTransaction states by attempting to fetch them.
+    getStateInternal(REMOVING_FWD_RRS_ST);
+    getStateInternal(REMOVING_REV_PTRS_ST);
+}
+
+void
+SimpleRemoveTransaction::readyHandler() {
+    switch(getNextEvent()) {
+    case START_EVT:
+        if (getForwardDomain()) {
+            // Request includes a forward change, do that first.
+            transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+        } else {
+            // Reverse change only, transition accordingly.
+            transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+        }
+
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleRemoveTransaction::selectingFwdServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getForwardDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REMOVING_FWD_RRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+void
+SimpleRemoveTransaction::removingFwdRRsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildRemoveFwdRRsRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(d2_to_dns_logger,
+                          DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE)
+                          .arg(getRequestId())
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate("Forward RR Remove");
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            // The RCODE will be based on a value-dependent RRset search,
+            // see RFC 2136 section 3.2.3/3.2.4.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if ((rcode == dns::Rcode::NOERROR()) ||
+                (rcode == dns::Rcode::NXRRSET())) {
+                // We were able to remove them or they were not there (
+                // Rcode of NXRRSET means there are no matching RRsets).
+                // In either case, we consider it success and mark it as done.
+                setForwardChangeCompleted(true);
+
+                // If request calls for reverse update then do that next,
+                // otherwise we can process ok.
+                if (getReverseDomain()) {
+                    transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+                } else {
+                    transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+                }
+            } else {
+                // Any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED)
+                          .arg(getRequestId())
+                          .arg(getCurrentServer()->toText())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR)
+                      .arg(getRequestId())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT)
+                      .arg(getRequestId())
+                      .arg(getCurrentServer()->toText())
+                      .arg(getNcr()->getFqdn());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(d2_to_dns_logger,
+                      DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS)
+                      .arg(getRequestId())
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+
+void
+SimpleRemoveTransaction::selectingRevServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getReverseDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+
+void
+SimpleRemoveTransaction::removingRevPtrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildRemoveRevPtrsRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE)
+                          .arg(getRequestId())
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate("Reverse Remove");
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            // The RCODE will be based on a value-dependent RRset search,
+            // see RFC 2136 section 3.2.3/3.2.4.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if ((rcode == dns::Rcode::NOERROR()) ||
+                (rcode == dns::Rcode::NXRRSET())) {
+                // We were able to remove the reverse mapping or they were
+                // not there (Rcode of NXRRSET means there are no matching
+                // RRsets). In either case, mark it as done.
+                setReverseChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED)
+                          .arg(getRequestId())
+                          .arg(getCurrentServer()->toText())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR)
+                      .arg(getRequestId())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT)
+                      .arg(getRequestId())
+                      .arg(getCurrentServer()->toText())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(d2_to_dns_logger,
+                      DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS)
+                      .arg(getRequestId())
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->toText());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+
+void
+SimpleRemoveTransaction::processRemoveOkHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+        LOG_INFO(d2_to_dns_logger, DHCP_DDNS_REMOVE_SUCCEEDED)
+                .arg(getRequestId())
+                .arg(getNcr()->toText());
+        setNcrStatus(dhcp_ddns::ST_COMPLETED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleRemoveTransaction::processRemoveFailedHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_FAILED_EVT:
+    case NO_MORE_SERVERS_EVT:
+    case SERVER_IO_ERROR_EVT:
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REMOVE_FAILED)
+                  .arg(getRequestId())
+                  .arg(transactionOutcomeString());
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(SimpleRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+SimpleRemoveTransaction::buildRemoveFwdRRsRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+    // There are no pre-requisites.
+
+    // Build the Update Section
+    // Construct dns::Name from NCR fqdn.
+    dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+    // Build the Update Section. 
+
+    // Create the FQDN/IP 'delete' RR and add it to update section.
+    dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+                         getAddressRRType(), dns::RRTTL(0)));
+
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Create the DHCID 'delete' RR and add it to the update section.
+    update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(),
+                                dns::RRType::DHCID(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+void
+SimpleRemoveTransaction::buildRemoveRevPtrsRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+    // Create the reverse IP address "FQDN".
+    std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+    dns::Name rev_ip(rev_addr);
+
+    /// TKM @todo - not sure we need this pre-req
+    // Create an assertion that the PTRDNAME in the PTR record matches the
+    // client's FQDN for the address that was released.
+    // Based on RFC 2136, section 3.2.3
+    dns::RRsetPtr prereq(new dns::RRset(rev_ip, dns::RRClass::IN(),
+                                        dns::RRType::PTR(), dns::RRTTL(0)));
+    addPtrRdata(prereq);
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Now, build the Update section.
+
+    // Create a delete of any RRs for the FQDN and add it to update section.
+    // Based on RFC 2136, section 3.4.2.3
+    dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+                         dns::RRType::ANY(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/simple_remove.h b/src/bin/d2/simple_remove.h
new file mode 100644 (file)
index 0000000..ff43254
--- /dev/null
@@ -0,0 +1,357 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_REMOVE_H
+#define SIMPLE_REMOVE_H
+
+/// @file nc_remove.h This file defines the class SimpleRemoveTransaction.
+
+#include <d2/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the SimpleRemoveTransaction encounters a general error.
+class SimpleRemoveTransactionError : public isc::Exception {
+public:
+    SimpleRemoveTransactionError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Remove update.
+///
+/// SimpleRemoveTransaction implements a state machine for removing a forward
+/// and/or reverse DNS mappings. This state machine is based upon the processing
+/// logic described in RFC 4703, Section 5.5. That logic may be paraphrased as
+/// follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+///     Select a forward server
+///     Send the server a request to remove client's specific forward address RR
+///     If it succeeds or the server responds with name no longer in use
+///         Send a server a request to delete any other RRs for that FQDN, such
+///         as the DHCID RR.
+///     otherwise
+///         abandon the update
+///
+/// If the request includes a reverse change:
+///     Select a reverse server
+///     Send a server a request to delete reverse entry (PTR RR)
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class SimpleRemoveTransaction : public NameChangeTransaction {
+public:
+
+    //@{  Additional states needed for SimpleRemove state model.
+    /// @brief State that attempts to remove FQDN/IP and DHCID RRs for an FQDN
+    static const int REMOVING_FWD_RRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+    /// @brief State that attempts to remove reverse PTR records
+    static const int REMOVING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+    //@}
+
+    //@{ Additional events needed for SimpleRemove state model.
+    /// @brief Event sent when replace attempt to fails with address not in use.
+    /// @todo Currently none have been identified.
+    //@}
+
+    /// @brief Constructor
+    ///
+    /// Instantiates an Remove transaction that is ready to be started.
+    ///
+    /// @param io_service IO service to be used for IO processing
+    /// @param ncr is the NameChangeRequest to fulfill
+    /// @param forward_domain is the domain to use for forward DNS updates
+    /// @param reverse_domain is the domain to use for reverse DNS updates
+    /// @param cfg_mgr pointer to the configuration manager
+    ///
+    /// @throw SimpleRemoveTransaction error if given request is not a CHG_REMOVE,
+    /// NameChangeTransaction error for base class construction errors.
+    SimpleRemoveTransaction(asiolink::IOServicePtr& io_service,
+                          dhcp_ddns::NameChangeRequestPtr& ncr,
+                          DdnsDomainPtr& forward_domain,
+                          DdnsDomainPtr& reverse_domain,
+                          D2CfgMgrPtr& cfg_mgr);
+
+    /// @brief Destructor
+    virtual ~SimpleRemoveTransaction();
+
+protected:
+    /// @brief Adds events defined by SimpleRemoveTransaction to the event set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// events unique to NCR Remove transaction processing.
+    ///
+    /// @throw StateModelError if an event definition is invalid or a duplicate.
+    virtual void defineEvents();
+
+    /// @brief Validates the contents of the set of events.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Remove transaction's events. This tests that the needed events are in
+    /// the event dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyEvents();
+
+    /// @brief Adds states defined by SimpleRemoveTransaction to the state set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// states unique to NCR Remove transaction processing.
+    ///
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
+    virtual void defineStates();
+
+    /// @brief Validates the contents of the set of states.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Remove transaction's states.  This tests that the needed states are in
+    /// the state dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
+
+    /// @brief State handler for READY_ST.
+    ///
+    /// Entered from:
+    /// - INIT_ST with next event of START_EVT
+    ///
+    /// The READY_ST is the state the model transitions into when the inherited
+    /// method, startTransaction() is invoked.  This handler, therefore, is the
+    /// entry point into the state model execution.  Its primary task is to
+    /// determine whether to start with a forward DNS change or a reverse DNS
+    /// change.
+    ///
+    /// Transitions to:
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes a forward change.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes only a reverse change.
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not
+    /// START_EVT.
+    void readyHandler();
+
+    /// @brief State handler for SELECTING_FWD_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_FWD_RRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the forward domain for the forward
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the forward domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REMOVING_FWD_RRS_ST with next event of SERVER_SELECTED upon
+    /// successful server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingFwdServerHandler();
+
+    /// @brief State handler for SELECTING_REV_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_FWD_RRS_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the reverse domain for the reverse
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the reverse domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REMOVING_REV_PTRS_ST with next event of SERVER_SELECTED upon
+    /// successful server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingRevServerHandler();
+
+    /// @brief State handler for REMOVING_FWD_RRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER_ST with next event SEVER_SELECTED_EVT
+    ///
+    /// Attempts to delete any remaining RRs associated with the given FQDN
+    /// such as the DHCID RR.  If this is first invocation of the handler after
+    /// transitioning into this state, any previous update request context is
+    /// deleted and the handler builds the forward remove request. It then
+    /// schedules an asynchronous send via sendUpdate(),
+    /// and returns.  Note that sendUpdate will post NOP_EVT as the next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+    /// successful completion and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// completion and the request does not include a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any other reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of SERVER_IO_ERROR_EVT if
+    /// there we have reached maximum number of retries without success on the
+    /// current server.
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT or IO_COMPLETE_EVT
+    void removingFwdRRsHandler();
+
+    /// @brief State handler for REMOVING_REV_PTRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to delete a reverse DNS entry for a given FQDN. If this is
+    /// first invocation of the handler after transitioning into this state,
+    /// any previous update request context is deleted.  If next event is
+    /// SERVER_SELECTED_EVT, the handler builds the reverse remove request,
+    /// schedules an asynchronous send via sendUpdate(), and then returns.
+    /// Note that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+    /// successful completion.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+    /// DNS server rejected the update for any reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+    /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+    void removingRevPtrsHandler();
+
+    /// @brief State handler for PROCESS_TRANS_OK_ST.
+    ///
+    /// Entered from:
+    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_OK_EVT
+    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+    ///
+    /// Sets the transaction action status to indicate success and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of END_EVT.
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT
+    void processRemoveOkHandler();
+
+    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REMOVING_FWD_RRS_ST with a next event of SERVER_IO_ERROR_EVT
+    /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+    ///
+    /// Sets the transaction status to indicate failure and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of FAIL_EVT.
+    ///
+    /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_FAILED_EVT
+    void processRemoveFailedHandler();
+
+    /// @brief Builds a DNS request to remove all forward DNS RRs for a FQDN.
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for removing any
+    /// remaining forward DNS RRs, once all A or AAAA entries for the FQDN
+    /// have been removed. Once constructed, the request is stored as the
+    /// transaction's DNS update request.
+    ///
+    /// The request content is adherent to RFC 4703 section 5.5, paragraph 5.
+    ///
+    /// Prerequisite RRsets:
+    /// - None
+    ///
+    /// Updates RRsets:
+    /// -# A delete of FQDN/IP RR for the FQDN
+    /// -# A delete of DHCID RR for the FQDN
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildRemoveFwdRRsRequest();
+
+    /// @brief Builds a DNS request to remove a reverse DNS entry for a FQDN
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for removing a
+    /// reverse DNS mapping.  Once constructed, the request is stored as
+    /// the transaction's DNS update request.
+    ///
+    /// The request content is adherent to RFC 4703 section 5.5, paragraph 2:
+    ///
+    /// Prerequisite RRsets:
+    /// 1. An assertion that a PTR record matching the client's FQDN exists.
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of all RRs for the FQDN
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildRemoveRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a SimpleRemoveTransaction.
+typedef boost::shared_ptr<SimpleRemoveTransaction> SimpleRemoveTransactionPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
index 422d6401c126425fabe194fdf159879a8b97ccea..38b764399f7c9a6973fe05b9ccc17452bd2f965e 100644 (file)
@@ -60,6 +60,8 @@ d2_unittests_SOURCES += d2_simple_parser_unittest.cc
 d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h
 d2_unittests_SOURCES += get_config_unittest.cc
 d2_unittests_SOURCES += d2_command_unittest.cc
+d2_unittests_SOURCES += simple_add_unittests.cc
+d2_unittests_SOURCES += simple_remove_unittests.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
index ff7d067d8b74a8b53cf8187abde138c861927613..73936e5bb05d8e8c8caa112ce0578b33df7e9334 100644 (file)
@@ -284,7 +284,8 @@ TEST_F(D2ProcessTest, queueFullRecovery) {
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"010203040A7F8E3D\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     // Start queue manager with known good config.
@@ -511,7 +512,8 @@ TEST_F(D2ProcessTest, canShutdown) {
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"010203040A7F8E3D\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     // Manually enqueue a request.  This lets us test logic with queue
index 5d1cd4ba0c943f5568d849ce3382ac21c3ce1f60..68e9916424b236621092b7b37922194e2f4b10ef 100644 (file)
@@ -37,7 +37,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\" : true "
      "}",
     // Valid Remove.
      "{"
@@ -48,7 +49,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\" : true "
      "}",
      // Valid Add with IPv6 address
      "{"
@@ -59,7 +61,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\" : true "
      "}"
 };
 
index 0fd39bd1755db7dbdf78aa513c1bb199291cec2b..9ddee7534e8b7b6561e5b653f673494ebc8e4ddb 100644 (file)
@@ -9,6 +9,11 @@
 #include <asiolink/io_service.h>
 #include <d2/d2_update_mgr.h>
 #include <nc_test_utils.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.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>
 
@@ -95,7 +100,8 @@ public:
         " \"ip-address\" : \"192.168.1.2\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
         const char* dhcids[] = { "111111", "222222", "333333", "444444"};
@@ -674,6 +680,9 @@ TEST_F(D2UpdateMgrTest, addTransaction) {
     ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
     NameChangeTransactionPtr trans = (*pos).second;
     ASSERT_TRUE(trans);
+   
+    // Verify the correct type of transcation was created. 
+    ASSERT_NO_THROW(dynamic_cast<NameAddTransaction&>(*trans)); 
 
     // At this point the transaction should have constructed
     // and sent the DNS request.
@@ -728,6 +737,9 @@ TEST_F(D2UpdateMgrTest, removeTransaction) {
     NameChangeTransactionPtr trans = (*pos).second;
     ASSERT_TRUE(trans);
 
+    // Verify the correct type of transcation was created. 
+    ASSERT_NO_THROW(dynamic_cast<NameRemoveTransaction&>(*trans)); 
+
     // At this point the transaction should have constructed
     // and sent the DNS request.
     ASSERT_TRUE(trans->getCurrentServer());
@@ -858,4 +870,118 @@ TEST_F(D2UpdateMgrTest, multiTransactionTimeout) {
     }
 }
 
+/// @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 transcation was created. 
+    ASSERT_NO_THROW(dynamic_cast<SimpleAddTransaction&>(*trans)); 
+
+    // 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 transcation was created. 
+    ASSERT_NO_THROW(dynamic_cast<SimpleRemoveTransaction&>(*trans)); 
+
+    // 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());
+}
+
 }
index 9f54a29c070bf58d4d85b153cc85651a73e88ca5..67d11d1903b627698c5590c11f9d417cf079113a 100644 (file)
@@ -272,7 +272,8 @@ TEST(NameAddTransaction, construction) {
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     dhcp_ddns::NameChangeRequestPtr ncr;
index 123a7e119017c58d986e0a82487856cbf6d312f2..f5ced0a61b280f29887e7b09f3504d1d081c018f 100644 (file)
@@ -275,7 +275,8 @@ TEST(NameRemoveTransaction, construction) {
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     dhcp_ddns::NameChangeRequestPtr ncr;
index ccc2482359062a2a5f591761f445eaaddf019b5b..01014c5655742f8ab3cf8215b217eb4f18eff2fa 100644 (file)
@@ -266,7 +266,8 @@ TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     // Create NameChangeRequest from JSON string.
@@ -324,7 +325,8 @@ TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
         " \"ip-address\" : \"2001:1::100\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     // Create NameChangeRequest from JSON string.
@@ -813,5 +815,117 @@ std::string toHexText(const uint8_t* data, size_t len) {
     return (stream.str());
 }
 
+// Verifies that the contents of the given transaction's  DNS update request
+// is correct for replacing a forward DNS entry when not using conflict
+// resolution.
+void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran) {
+    const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+    ASSERT_TRUE(request);
+
+    // Safety check.
+    dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+    ASSERT_TRUE(ncr);
+
+    std::string exp_zone_name = tran.getForwardDomain()->getName();
+    std::string exp_fqdn = ncr->getFqdn();
+    const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+    // Verify the zone section.
+    checkZone(request, exp_zone_name);
+
+    // Verify the PREREQUISITE SECTION
+    // There should be no prerequisites. 
+    dns::RRsetPtr rrset;
+    checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+    // Verify the UPDATE SECTION
+    // Should be 4
+    // 1. delete of the FQDN/IP RR
+    // 2. delete of the DHCID RR 
+    // 3. add of the FQDN/IP RR
+    // 4. add of the DHCID RR 
+    checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4);
+
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
+    // Verify the FQDN delete RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+    // Verify the DHCID delete RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 1));
+    checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+            0, ncr);
+
+    // Verify the FQDN/IP add RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 2));
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+    // Now, verify the DHCID add RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 3));
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+            ttl, ncr);
+
+    // Verify there are no RRs in the ADDITIONAL Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+    // Verify that it will render toWire without throwing.
+    dns::MessageRenderer renderer;
+    ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+    const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+    ASSERT_TRUE(request);
+
+    // Safety check.
+    dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+    ASSERT_TRUE(ncr);
+
+    std::string exp_zone_name = tran.getForwardDomain()->getName();
+    std::string exp_fqdn = ncr->getFqdn();
+    const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+    // Verify the zone section.
+    checkZone(request, exp_zone_name);
+
+    // Verify there no prerequisites.
+    checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+    // Verify there are 2 RRs in the UPDATE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+    // Verify the FQDN delete RR.
+    dns::RRsetPtr rrset;
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+    // Verify the DHCID delete RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 1));
+    checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+            0, ncr);
+
+    // Verify that it will render toWire without throwing.
+    dns::MessageRenderer renderer;
+    ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void checkContext(NameChangeTransactionPtr trans, const int exp_state,
+                  const int exp_evt, const std::string& file, int line) {
+    ASSERT_TRUE(trans);
+    ASSERT_TRUE(exp_state == trans->getCurrState() && exp_evt == trans->getNextEvent())
+            << "expected state: " << trans->getStateLabel(exp_state)
+            << " event: " << trans->getEventLabel(exp_evt)
+            << ", actual context: " << trans->getContextStr()
+            << " at " << file << ":" << line;
+}
+
 }; // namespace isc::d2
 }; // namespace isc
index 9bdb0184b805eb3ebded96331ecda524ea83fc87..fa37bde2edefed19391e36a89f71f1475632167f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -372,6 +372,21 @@ extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran);
 /// @param tran Transaction containing the request to be verified.
 extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran);
 
+/// @brief Verifies a simple forward mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a forward DNS mapping when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran);
 
 /// @brief Creates a NameChangeRequest from JSON string.
 ///
@@ -444,6 +459,19 @@ extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name,
 /// @param len size (in bytes) of data
 extern std::string toHexText(const uint8_t* data, size_t len);
 
+/// @brief Verifies the current state and next event in a transaction
+/// @param trans NameChangeTransaction to check
+/// @param exp_state expected current state of the transaction
+/// @param exp_event  expected next event of the transaction
+/// @param file source file name
+/// @param line souce line number
+extern void checkContext(NameChangeTransactionPtr trans, const int exp_state,
+                         const int exp_evt, const std::string& file, int line);
+
+/// @brief Macro for calling checkContext() that supplies invocation location
+#define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__)
+
+
 }; // namespace isc::d2
 }; // namespace isc
 
index 03a89e7bc38f848a2b33d0bba942e8391b88864f..47444772305e6380c0b95e5be2610895eb1ecd87 100644 (file)
@@ -369,7 +369,8 @@ TEST(NameChangeTransaction, construction) {
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"0102030405060708\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     dhcp_ddns::NameChangeRequestPtr ncr;
diff --git a/src/bin/d2/tests/simple_add_unittests.cc b/src/bin/d2/tests/simple_add_unittests.cc
new file mode 100644 (file)
index 0000000..a9cda70
--- /dev/null
@@ -0,0 +1,1216 @@
+// Copyright (C) 2020 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 <d2/d2_cfg_mgr.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/simple_add.h>
+#include <dns/messagerenderer.h>
+#include <nc_test_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test class derived from SimpleAddTransaction to provide visibility
+// to protected methods.
+class SimpleAddStub : public SimpleAddTransaction {
+public:
+    SimpleAddStub(asiolink::IOServicePtr& io_service,
+                  dhcp_ddns::NameChangeRequestPtr& ncr,
+                  DdnsDomainPtr& forward_domain,
+                  DdnsDomainPtr& reverse_domain,
+                  D2CfgMgrPtr& cfg_mgr)
+        : SimpleAddTransaction(io_service, ncr, forward_domain, reverse_domain,
+                               cfg_mgr),
+          simulate_send_exception_(false),
+          simulate_build_request_exception_(false) {
+    }
+
+    virtual ~SimpleAddStub() {
+    }
+
+    /// @brief Simulates sending update requests to the DNS server
+    ///
+    /// This method simulates the initiation of an asynchronous send of
+    /// a DNS update request. It overrides the actual sendUpdate method in
+    /// the base class, thus avoiding an actual send, yet still increments
+    /// the update attempt count and posts a next event of NOP_EVT.
+    ///
+    /// It will also simulate an exception-based failure of sendUpdate, if
+    /// the simulate_send_exception_ flag is true.
+    ///
+    /// @param comment Parameter is unused, but present in base class method.
+    ///
+    virtual void sendUpdate(const std::string& /*comment*/) {
+        if (simulate_send_exception_) {
+            // Make the flag a one-shot by resetting it.
+            simulate_send_exception_ = false;
+            // Transition to failed.
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            return;
+        }
+
+        // Update send attempt count and post a NOP_EVT.
+        setUpdateAttempts(getUpdateAttempts() + 1);
+        postNextEvent(StateModel::NOP_EVT);
+    }
+
+    /// @brief Prepares the initial D2UpdateMessage
+    ///
+    /// This method overrides the NameChangeTransaction implementation to
+    /// provide the ability to simulate an exception throw in the build
+    /// request logic.
+    /// If the one-shot flag, simulate_build_request_exception_ is true,
+    /// this method will throw an exception, otherwise it will invoke the
+    /// base class method, providing normal functionality.
+    ///
+    /// For parameter description see the NameChangeTransaction implementation.
+    virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+        if (simulate_build_request_exception_) {
+            simulate_build_request_exception_ = false;
+            isc_throw (SimpleAddTransactionError,
+                       "Simulated build requests exception");
+        }
+
+        return (NameChangeTransaction::prepNewRequest(domain));
+    }
+
+    /// @brief Simulates receiving a response
+    ///
+    /// This method simulates the completion of a DNSClient send.  This allows
+    /// the state handler logic devoted to dealing with IO completion to be
+    /// fully exercised without requiring any actual IO.  The two primary
+    /// pieces of information gleaned from IO completion are the DNSClient
+    /// status which indicates whether or not the IO exchange was successful
+    /// and the rcode, which indicates the server's reaction to the request.
+    ///
+    /// This method updates the transaction's DNS status value to that of the
+    /// given parameter, and then constructs and DNS update response message
+    /// with the given rcode value.  To complete the simulation it then posts
+    /// a next event of IO_COMPLETED_EVT.
+    ///
+    /// @param status simulated DNSClient status
+    /// @param rcode  simulated server response code
+    void fakeResponse(const DNSClient::Status& status,
+                      const dns::Rcode& rcode) {
+        // Set the DNS update status.  This is normally set in
+        // DNSClient IO completion handler.
+        setDnsUpdateStatus(status);
+
+        // Construct an empty message with the given Rcode.
+        D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+        msg->setRcode(rcode);
+
+        // Set the update response to the message.
+        setDnsUpdateResponse(msg);
+
+        // Post the IO completion event.
+        postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+    }
+
+    /// @brief Selects the first forward server.
+    /// Some state handlers require a server to have been selected.
+    /// This selects a server without going through the state
+    /// transition(s) to do so.
+    bool selectFwdServer() {
+        if (getForwardDomain()) {
+            initServerSelection(getForwardDomain());
+            selectNextServer();
+            return (getCurrentServer().get() != 0);
+        }
+
+        return (false);
+    }
+
+    /// @brief Selects the first reverse server.
+    /// Some state handlers require a server to have been selected.
+    /// This selects a server without going through the state
+    /// transition(s) to do so.
+    bool selectRevServer() {
+        if (getReverseDomain()) {
+            initServerSelection(getReverseDomain());
+            selectNextServer();
+            return (getCurrentServer().get() != 0);
+        }
+
+        return (false);
+    }
+
+    /// @brief One-shot flag which will simulate sendUpdate failure if true.
+    bool simulate_send_exception_;
+
+    /// @brief One-shot flag which will simulate an exception when sendUpdate
+    /// failure if true.
+    bool simulate_build_request_exception_;
+
+    using StateModel::postNextEvent;
+    using StateModel::setState;
+    using StateModel::initDictionaries;
+    using SimpleAddTransaction::defineEvents;
+    using SimpleAddTransaction::verifyEvents;
+    using SimpleAddTransaction::defineStates;
+    using SimpleAddTransaction::verifyStates;
+    using SimpleAddTransaction::readyHandler;
+    using SimpleAddTransaction::selectingFwdServerHandler;
+    using SimpleAddTransaction::getCurrentServer;
+    using SimpleAddTransaction::setDnsUpdateStatus;
+    using SimpleAddTransaction::replacingFwdAddrsHandler;
+    using SimpleAddTransaction::selectingRevServerHandler;
+    using SimpleAddTransaction::replacingRevPtrsHandler;
+    using SimpleAddTransaction::processAddOkHandler;
+    using SimpleAddTransaction::processAddFailedHandler;
+    using SimpleAddTransaction::buildReplaceFwdAddressRequest;
+    using SimpleAddTransaction::buildReplaceRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleAddStub> SimpleAddStubPtr;
+
+/// @brief Test fixture for testing SimpleAddTransaction
+///
+/// Note this class uses SimpleAddStub class to exercise non-public
+/// aspects of SimpleAddTransaction.
+class SimpleAddTransactionTest : public TransactionTest {
+public:
+
+    SimpleAddTransactionTest() {
+    }
+
+    virtual ~SimpleAddTransactionTest() {
+    }
+
+    /// @brief Creates a transaction which requests an IPv4 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_mask determines which change directions are requested
+    SimpleAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+        // Creates IPv4 remove request, forward, and reverse domains.
+        setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+        // Now create the test transaction as would occur in update manager.
+        return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+                                               forward_domain_,
+                                               reverse_domain_, cfg_mgr_)));
+    }
+
+    /// @brief Creates a transaction which requests an IPv6 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_mask determines which change directions are requested
+    SimpleAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+        // Creates IPv6 remove request, forward, and reverse domains.
+        setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+        // Now create the test transaction as would occur in update manager.
+        return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+                                               forward_domain_,
+                                               reverse_domain_,
+                                               cfg_mgr_)));
+    }
+
+    /// @brief Create a test transaction at a known point in the state model.
+    ///
+    /// Method prepares a new test transaction and sets its state and next
+    /// event values to those given.  This makes the transaction appear to
+    /// be at that point in the state model without having to transition it
+    /// through prerequisite states.   It also provides the ability to set
+    /// which change directions are requested: forward change only, reverse
+    /// change only, or both.
+    ///
+    /// @param state value to set as the current state
+    /// @param event value to post as the next event
+    /// @param change_mask determines which change directions are requested
+    /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+    /// transaction.
+    SimpleAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+                                   unsigned int change_mask = FWD_AND_REV_CHG,
+                                   short family = AF_INET) {
+        SimpleAddStubPtr name_add =  (family == AF_INET ?
+                                    makeTransaction4(change_mask) :
+                                    makeTransaction6(change_mask));
+        name_add->initDictionaries();
+        name_add->postNextEvent(event);
+        name_add->setState(state);
+        return (name_add);
+    }
+};
+
+/// @brief Tests SimpleAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleAddTransaction, construction) {
+    asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+    D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+    const char* msg_str =
+        "{"
+        " \"change-type\" : 1 , "
+        " \"forward-change\" : true , "
+        " \"reverse-change\" : true , "
+        " \"fqdn\" : \"example.com.\" , "
+        " \"ip-address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \"0102030405060708\" , "
+        " \"lease-expires-on\" : \"20130121132405\" , "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
+        "}";
+
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    DnsServerInfoStoragePtr servers;
+    DdnsDomainPtr forward_domain;
+    DdnsDomainPtr reverse_domain;
+    DdnsDomainPtr empty_domain;
+
+    ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+    ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+    ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+    // Verify that construction with wrong change type fails.
+    EXPECT_THROW(SimpleAddTransaction(io_service, ncr,
+                                    forward_domain, reverse_domain, cfg_mgr),
+                                    SimpleAddTransactionError);
+
+    // Verify that a valid construction attempt works.
+    ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+    EXPECT_NO_THROW(SimpleAddTransaction(io_service, ncr,
+                                       forward_domain, reverse_domain,
+                                       cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleAddTransactionTest, dictionaryCheck) {
+    SimpleAddStubPtr name_add;
+    ASSERT_NO_THROW(name_add = makeTransaction4());
+    // Verify that the event and state dictionary validation fails prior
+    // dictionary construction.
+    ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+    ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+    // Construct both dictionaries.
+    ASSERT_NO_THROW(name_add->defineEvents());
+    ASSERT_NO_THROW(name_add->defineStates());
+
+    // Verify both event and state dictionaries now pass validation.
+    ASSERT_NO_THROW(name_add->verifyEvents());
+    ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for replacing a forward
+/// dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceFwdAddressRequest) {
+    // Create a IPv4 forward replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    SimpleAddStubPtr name_add;
+    ASSERT_NO_THROW(name_add = makeTransaction4());
+    ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+    checkSimpleReplaceFwdAddressRequest(*name_add);
+
+    // Create a IPv6 forward replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    ASSERT_NO_THROW(name_add = makeTransaction6());
+    ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+    checkSimpleReplaceFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests the construction of a DNS update request for replacing a
+/// reverse dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceRevPtrsRequest) {
+    // Create a IPv4 reverse replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    SimpleAddStubPtr name_add;
+    ASSERT_NO_THROW(name_add = makeTransaction4());
+    ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+    checkReplaceRevPtrsRequest(*name_add);
+
+    // Create a IPv6 reverse replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    ASSERT_NO_THROW(name_add = makeTransaction6());
+    ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+    checkReplaceRevPtrsRequest(*name_add);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, readyHandler) {
+    SimpleAddStubPtr name_add;
+
+    // Create a transaction which includes only a forward change.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                   StateModel::START_EVT, FORWARD_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring only a forward change, transitions to
+    // selecting a forward server.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create a transaction which includes both a forward and a reverse change.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                   StateModel::START_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring both forward and reverse, starts with
+    // the forward change by transitioning to selecting a forward server.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create and prep a reverse only transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                   StateModel::START_EVT, REVERSE_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_add->readyHandler());
+
+    // Verify that a request requiring only a reverse change, transitions to
+    // selecting a reverse server.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::READY_ST, StateModel::NOP_EVT)
+    );
+
+    // Running the readyHandler should throw.
+    EXPECT_THROW(name_add->readyHandler(), SimpleAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingFwdServerHandler) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                                    NameChangeTransaction::SELECT_SERVER_EVT)
+    );
+
+    // Call selectingFwdServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_add->getForwardDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+                     << " selections: " << i);
+        // Run selectingFwdServerHandler.
+        ASSERT_NO_THROW(name_add->selectingFwdServerHandler());
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_add->getCurrentServer());
+
+        // Verify that we transitioned correctly.
+        CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                      NameChangeTransaction::SERVER_SELECTED_EVT);
+
+        // Post a server IO error event.  This simulates an IO error occurring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+                                                SERVER_IO_ERROR_EVT));
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                                   StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->selectingFwdServerHandler(),
+                 SimpleAddTransactionError);
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   NameChangeTransaction::StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->replacingFwdAddrsHandler(), SimpleAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Verify that an update message was constructed properly.
+    checkSimpleReplaceFwdAddressRequest(*name_add);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                  NameChangeTransaction::NOP_EVT);
+
+    // Simulate receiving a successful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Forward completion should be true, reverse should be false.
+    EXPECT_TRUE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since it is a forward only change, we should be done.
+    // Verify that we transitioned correctly.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                  NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+    SimpleAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error or FQDN in use is failure.  Arbitrarily choosing refused.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run replacingFwdAddrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verify that we transitioned
+    // correctly.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+    SimpleAddStubPtr name_add;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingFwdAddrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+    SimpleAddStubPtr name_add;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingFwdAddrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a corrupt server response.
+        name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingFwdAddrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingRevServerHandler) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                                   NameChangeTransaction::SELECT_SERVER_EVT)
+    );
+
+    // Call selectingRevServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_add->getReverseDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+                     << " selections: " << i);
+        // Run selectingRevServerHandler.
+        ASSERT_NO_THROW(name_add->selectingRevServerHandler());
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_add->getCurrentServer());
+
+        // Verify that we transitioned correctly.
+        CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                      NameChangeTransaction::SERVER_SELECTED_EVT);
+
+        // Post a server IO error event.  This simulates an IO error occurring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+                                                SERVER_IO_ERROR_EVT));
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                                   StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->selectingRevServerHandler(),
+                 SimpleAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   NameChangeTransaction::StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+                 SimpleAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Verify that an update message was constructed properly.
+    checkReplaceRevPtrsRequest(*name_add);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                  NameChangeTransaction::NOP_EVT);
+
+    // Simulate receiving a successful update response.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run replacingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Forward completion should be false, reverse should be true.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+    // Since it is a reverse change, we should be done.
+    // Verify that we transitioned correctly.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                  NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+    SimpleAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error is failure.  Arbitrarily choosing refused.
+    name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run replacingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verify that we transitioned
+    // correctly.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+    SimpleAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+    SimpleAddStubPtr name_add;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_add->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest();
+
+        // Run replacingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time out we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server corrupt response.
+        name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run replacingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_add->getForwardChangeCompleted());
+        EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddOkHandler) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                   NameChangeTransaction::UPDATE_OK_EVT)
+    );
+
+    // Run processAddOkHandler.
+    EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+    // Verify that the model has ended.
+    CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                   StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->processAddOkHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                   NameChangeTransaction::UPDATE_FAILED_EVT)
+    );
+
+    // Run processAddFailedHandler.
+    EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                   StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_add->processAddFailedHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                   NameChangeTransaction::NO_MORE_SERVERS_EVT)
+    );
+
+    // Run processAddFailedHandler.
+    EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    name_add->simulate_send_exception_ = true;
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    name_add->simulate_send_exception_ = true;
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+                                   NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    // Set the one-shot exception simulation flag.
+    name_add->simulate_build_request_exception_ = true;
+
+    // Run replacingFwdAddrsHandler to construct and send the request.
+    // This should fail with a build request throw which should be caught
+    // in the state handler.
+    ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+    // Verify we did not attempt to send anything.
+    EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) {
+    SimpleAddStubPtr name_add;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+                                   NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Set the one-shot exception simulation flag.
+    name_add->simulate_build_request_exception_ = true;
+
+    // Run replacingRevPtrsHandler to construct and send the request.
+    // This should fail with a build request throw which should be caught
+    // in the state handler.
+    ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+    // Verify we did not attempt to send anything.
+    EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_add->getForwardChangeCompleted());
+    EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
diff --git a/src/bin/d2/tests/simple_remove_unittests.cc b/src/bin/d2/tests/simple_remove_unittests.cc
new file mode 100644 (file)
index 0000000..98a70eb
--- /dev/null
@@ -0,0 +1,1299 @@
+// Copyright (C) 2020 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 <d2/d2_cfg_mgr.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/simple_remove.h>
+#include <dns/messagerenderer.h>
+#include <nc_test_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+
+namespace {
+
+/// @brief Test class derived from SimpleRemoveTransaction to provide visibility
+// to protected methods.
+class SimpleRemoveStub : public SimpleRemoveTransaction {
+public:
+    SimpleRemoveStub(asiolink::IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain,
+                   D2CfgMgrPtr& cfg_mgr)
+        : SimpleRemoveTransaction(io_service, ncr, forward_domain,
+                                reverse_domain, cfg_mgr),
+          simulate_send_exception_(false),
+          simulate_build_request_exception_(false) {
+    }
+
+    virtual ~SimpleRemoveStub() {
+    }
+
+    /// @brief Simulates sending update requests to the DNS server
+    ///
+    /// This method simulates the initiation of an asynchronous send of
+    /// a DNS update request. It overrides the actual sendUpdate method in
+    /// the base class, thus avoiding an actual send, yet still increments
+    /// the update attempt count and posts a next event of NOP_EVT.
+    ///
+    /// It will also simulate an exception-based failure of sendUpdate, if
+    /// the simulate_send_exception_ flag is true.
+    ///
+    /// @param comment Parameter is unused, but present in base class method
+    ///
+    virtual void sendUpdate(const std::string& /* comment */) {
+        if (simulate_send_exception_) {
+            // Make the flag a one-shot by resetting it.
+            simulate_send_exception_ = false;
+            // Transition to failed.
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            return;
+        }
+
+        // Update send attempt count and post a NOP_EVT.
+        setUpdateAttempts(getUpdateAttempts() + 1);
+        postNextEvent(StateModel::NOP_EVT);
+    }
+
+    /// @brief Prepares the initial D2UpdateMessage
+    ///
+    /// This method overrides the NameChangeTransaction implementation to
+    /// provide the ability to simulate an exception throw in the build
+    /// request logic.
+    /// If the one-shot flag, simulate_build_request_exception_ is true,
+    /// this method will throw an exception, otherwise it will invoke the
+    /// base class method, providing normal functionality.
+    ///
+    /// For parameter description see the NameChangeTransaction implementation.
+    virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+        if (simulate_build_request_exception_) {
+            simulate_build_request_exception_ = false;
+            isc_throw (SimpleRemoveTransactionError,
+                       "Simulated build requests exception");
+        }
+
+        return (NameChangeTransaction::prepNewRequest(domain));
+    }
+
+    /// @brief Simulates receiving a response
+    ///
+    /// This method simulates the completion of a DNSClient send.  This allows
+    /// the state handler logic devoted to dealing with IO completion to be
+    /// fully exercised without requiring any actual IO.  The two primary
+    /// pieces of information gleaned from IO completion are the DNSClient
+    /// status which indicates whether or not the IO exchange was successful
+    /// and the rcode, which indicates the server's reaction to the request.
+    ///
+    /// This method updates the transaction's DNS status value to that of the
+    /// given parameter, and then constructs and DNS update response message
+    /// with the given rcode value.  To complete the simulation it then posts
+    /// a next event of IO_COMPLETED_EVT.
+    ///
+    /// @param status simulated DNSClient status
+    /// @param rcode  simulated server response code
+    void fakeResponse(const DNSClient::Status& status,
+                      const dns::Rcode& rcode) {
+        // Set the DNS update status.  This is normally set in
+        // DNSClient IO completion handler.
+        setDnsUpdateStatus(status);
+
+        // Construct an empty message with the given Rcode.
+        D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+        msg->setRcode(rcode);
+
+        // Set the update response to the message.
+        setDnsUpdateResponse(msg);
+
+        // Post the IO completion event.
+        postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+    }
+
+    /// @brief Selects the first forward server.
+    /// Some state handlers require a server to have been selected.
+    /// This selects a server without going through the state
+    /// transition(s) to do so.
+    bool selectFwdServer() {
+        if (getForwardDomain()) {
+            initServerSelection(getForwardDomain());
+            selectNextServer();
+            return (getCurrentServer().get() != 0);
+        }
+
+        return (false);
+    }
+
+    /// @brief Selects the first reverse server.
+    /// Some state handlers require a server to have been selected.
+    /// This selects a server without going through the state
+    /// transition(s) to do so.
+    bool selectRevServer() {
+        if (getReverseDomain()) {
+            initServerSelection(getReverseDomain());
+            selectNextServer();
+            return (getCurrentServer().get() != 0);
+        }
+
+        return (false);
+    }
+
+    /// @brief One-shot flag which will simulate sendUpdate failure if true.
+    bool simulate_send_exception_;
+
+    /// @brief One-shot flag which will simulate an exception when sendUpdate
+    /// failure if true.
+    bool simulate_build_request_exception_;
+
+    using StateModel::postNextEvent;
+    using StateModel::setState;
+    using StateModel::initDictionaries;
+    using SimpleRemoveTransaction::defineEvents;
+    using SimpleRemoveTransaction::verifyEvents;
+    using SimpleRemoveTransaction::defineStates;
+    using SimpleRemoveTransaction::verifyStates;
+    using SimpleRemoveTransaction::readyHandler;
+    using SimpleRemoveTransaction::selectingFwdServerHandler;
+    using SimpleRemoveTransaction::getCurrentServer;
+    using SimpleRemoveTransaction::setDnsUpdateStatus;
+    using SimpleRemoveTransaction::removingFwdRRsHandler;
+    using SimpleRemoveTransaction::selectingRevServerHandler;
+    using SimpleRemoveTransaction::removingRevPtrsHandler;
+    using SimpleRemoveTransaction::processRemoveOkHandler;
+    using SimpleRemoveTransaction::processRemoveFailedHandler;
+    using SimpleRemoveTransaction::buildRemoveFwdRRsRequest;
+    using SimpleRemoveTransaction::buildRemoveRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleRemoveStub> SimpleRemoveStubPtr;
+
+/// @brief Test fixture for testing SimpleRemoveTransaction
+///
+/// Note this class uses SimpleRemoveStub class to exercise non-public
+/// aspects of SimpleRemoveTransaction.
+class SimpleRemoveTransactionTest : public TransactionTest {
+public:
+    SimpleRemoveTransactionTest() {
+    }
+
+    virtual ~SimpleRemoveTransactionTest() {
+    }
+
+    /// @brief Creates a transaction which requests an IPv4 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_mask determines which change directions are requested
+    SimpleRemoveStubPtr makeTransaction4(int change_mask) {
+        // Creates IPv4 remove request, forward, and reverse domains.
+        setupForIPv4Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+        // Now create the test transaction as would occur in update manager.
+        return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+                                                     forward_domain_,
+                                                     reverse_domain_,
+                                                     cfg_mgr_)));
+    }
+
+    /// @brief Creates a transaction which requests an IPv6 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_mask determines which change directions are requested
+    SimpleRemoveStubPtr makeTransaction6(int change_mask) {
+        // Creates IPv6 remove request, forward, and reverse domains.
+        setupForIPv6Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+        // Now create the test transaction as would occur in update manager.
+        return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+                                                     forward_domain_,
+                                                     reverse_domain_,
+                                                     cfg_mgr_)));
+    }
+
+    /// @brief Create a test transaction at a known point in the state model.
+    ///
+    /// Method prepares a new test transaction and sets its state and next
+    /// event values to those given.  This makes the transaction appear to
+    /// be at that point in the state model without having to transition it
+    /// through prerequisite states.   It also provides the ability to set
+    /// which change directions are requested: forward change only, reverse
+    /// change only, or both.
+    ///
+    /// @param state value to set as the current state
+    /// @param event value to post as the next event
+    /// @param change_mask determines which change directions are requested
+    /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+    /// transaction.
+    SimpleRemoveStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+                                      unsigned int change_mask = FWD_AND_REV_CHG,
+                                      short family = AF_INET) {
+        SimpleRemoveStubPtr name_remove = (family == AF_INET ?
+                                         makeTransaction4(change_mask) :
+                                         makeTransaction6(change_mask));
+        name_remove->initDictionaries();
+        name_remove->postNextEvent(event);
+        name_remove->setState(state);
+        return (name_remove);
+    }
+
+};
+
+/// @brief Tests SimpleRemoveTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleRemoveTransaction, construction) {
+    asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+    D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+    const char* msg_str =
+        "{"
+        " \"change-type\" : 0 , "
+        " \"forward-change\" : true , "
+        " \"reverse-change\" : true , "
+        " \"fqdn\" : \"example.com.\" , "
+        " \"ip-address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \"0102030405060708\" , "
+        " \"lease-expires-on\" : \"20130121132405\" , "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
+        "}";
+
+    dhcp_ddns::NameChangeRequestPtr ncr;
+    DnsServerInfoStoragePtr servers;
+    DdnsDomainPtr forward_domain;
+    DdnsDomainPtr reverse_domain;
+    DdnsDomainPtr empty_domain;
+
+    ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+    ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+    ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+    // Verify that construction with wrong change type fails.
+    EXPECT_THROW(SimpleRemoveTransaction(io_service, ncr,
+                                       forward_domain, reverse_domain, cfg_mgr),
+                                       SimpleRemoveTransactionError);
+
+    // Verify that a valid construction attempt works.
+    ncr->setChangeType(isc::dhcp_ddns::CHG_REMOVE);
+    EXPECT_NO_THROW(SimpleRemoveTransaction(io_service, ncr,
+                                          forward_domain, reverse_domain,
+                                          cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleRemoveTransactionTest, dictionaryCheck) {
+    SimpleRemoveStubPtr name_remove;
+    ASSERT_NO_THROW(name_remove = makeTransaction4(FWD_AND_REV_CHG));
+    // Verify that the event and state dictionary validation fails prior
+    // dictionary construction.
+    ASSERT_THROW(name_remove->verifyEvents(), StateModelError);
+    ASSERT_THROW(name_remove->verifyStates(), StateModelError);
+
+    // Construct both dictionaries.
+    ASSERT_NO_THROW(name_remove->defineEvents());
+    ASSERT_NO_THROW(name_remove->defineStates());
+
+    // Verify both event and state dictionaries now pass validation.
+    ASSERT_NO_THROW(name_remove->verifyEvents());
+    ASSERT_NO_THROW(name_remove->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// dns RR entries.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveFwdRRsRequest) {
+    // Create a IPv4 forward replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    SimpleRemoveStubPtr name_remove;
+    ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+    ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+    checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+    // Create a IPv6 forward replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+    ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+    checkSimpleRemoveFwdRRsRequest(*name_remove);
+}
+
+/// @brief Tests the construction of a DNS update request for removing a
+/// reverse dns entry.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveRevPtrsRequest) {
+    // Create a IPv4 reverse replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    SimpleRemoveStubPtr name_remove;
+    ASSERT_NO_THROW(name_remove = makeTransaction4(REVERSE_CHG));
+    ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+    checkRemoveRevPtrsRequest(*name_remove);
+
+    // Create a IPv6 reverse replace transaction.
+    // Verify the request builds without error.
+    // and then verify the request contents.
+    ASSERT_NO_THROW(name_remove = makeTransaction6(REVERSE_CHG));
+    ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+    checkRemoveRevPtrsRequest(*name_remove);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, readyHandler) {
+    SimpleRemoveStubPtr name_remove;
+
+    // Create a transaction which includes only a forward change.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                      StateModel::START_EVT, FORWARD_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_remove->readyHandler());
+
+    // Verify that a request requiring only a forward change, transitions to
+    // selecting a forward server.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create a transaction which includes both a forward and a reverse change.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                      StateModel::START_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_remove->readyHandler());
+
+    // Verify that a request requiring both forward and reverse, starts with
+    // the forward change by transitioning to selecting a forward server.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create and prep a reverse only transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                     StateModel::START_EVT, REVERSE_CHG)
+    );
+
+    // Run readyHandler.
+    EXPECT_NO_THROW(name_remove->readyHandler());
+
+    // Verify that a request requiring only a reverse change, transitions to
+    // selecting a reverse server.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                  NameChangeTransaction::SELECT_SERVER_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+                                     StateModel::NOP_EVT)
+    );
+
+    // Running the readyHandler should throw.
+    EXPECT_THROW(name_remove->readyHandler(), SimpleRemoveTransactionError);
+}
+
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingFwdServerHandler) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                                    NameChangeTransaction::SELECT_SERVER_EVT)
+    );
+
+    // Call selectingFwdServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_remove->getForwardDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+                     << " selections:" << i);
+        // Run selectingFwdServerHandler.
+        ASSERT_NO_THROW(name_remove->selectingFwdServerHandler());
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_remove->getCurrentServer());
+
+        // Verify that we transitioned correctly.
+        CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                      NameChangeTransaction::SERVER_SELECTED_EVT);
+
+        // Post a server IO error event.  This simulates an IO error occurring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+                                                   SERVER_IO_ERROR_EVT));
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_remove->selectingFwdServerHandler());
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+                                      StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->selectingFwdServerHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+// ************************ removingFwdRRsHandler Tests *****************
+
+// Tests that removingFwdRRsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidEvent) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                      StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->removingFwdRRsHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                      NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Run removingFwdRRsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Verify that an update message was constructed properly.
+    checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                  NameChangeTransaction::NOP_EVT);
+
+    // Simulate receiving a successful update response.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run removingFwdRRsHandler again to process the response.
+    EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Forward completion should be true, reverse should be false.
+    EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Since it is a forward only change, we should be done.
+    // Verify that we transitioned correctly.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                 NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_OtherRcode) {
+    SimpleRemoveStubPtr name_remove;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                      NameChangeTransaction::SERVER_SELECTED_EVT,
+                                      FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectFwdServer());
+
+    // Run removingFwdRRsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error is failure (we are also treating FQDN not in use is
+    // success). Arbitrarily choosing refused.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run removingFwdRRsHandler again to process the response.
+    EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verify that we transitioned
+    // correctly.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_Timeout) {
+    SimpleRemoveStubPtr name_remove;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                      NameChangeTransaction::SERVER_SELECTED_EVT,
+                                      FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_remove->getDnsUpdateRequest();
+
+        // Run removingFwdRRsHandler to send the request.
+        EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_remove->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time around we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run removingFwdRRsHandler again to process the response.
+        EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+        EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes a forward and reverse change.
+//  Initial posted event is UPDATE_OK_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidResponse) {
+    SimpleRemoveStubPtr name_remove;
+
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                    NameChangeTransaction::UPDATE_OK_EVT, FWD_AND_REV_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectFwdServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_remove->getDnsUpdateRequest();
+
+        // Run removingFwdRRsHandler to send the request.
+        EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_remove->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time around we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a corrupt server response.
+        name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run removingFwdRRsHandler again to process the response.
+        EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+        EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingRevServerHandler) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                                    NameChangeTransaction::SELECT_SERVER_EVT)
+    );
+
+    // Call selectingRevServerHandler enough times to select all of the
+    // servers in it's current domain.  The first time, it will be with
+    // next event of SELECT_SERVER_EVT.  Thereafter it will be with a next
+    // event of SERVER_IO_ERROR_EVT.
+    int num_servers = name_remove->getReverseDomain()->getServers()->size();
+    for (int i = 0; i < num_servers; ++i) {
+        SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+                     << " selections:" << i);
+        // Run selectingRevServerHandler.
+        ASSERT_NO_THROW(name_remove->selectingRevServerHandler());
+
+        // Verify that a server was selected.
+        ASSERT_TRUE(name_remove->getCurrentServer());
+
+        // Verify that we transitioned correctly.
+        CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                      NameChangeTransaction::SERVER_SELECTED_EVT);
+
+        // Post a server IO error event.  This simulates an IO error occurring
+        // and a need to select the new server.
+        ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+                                                SERVER_IO_ERROR_EVT));
+    }
+
+    // We should have exhausted the list of servers. Processing another
+    // SERVER_IO_ERROR_EVT should transition us to failure.
+    EXPECT_NO_THROW(name_remove->selectingRevServerHandler());
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                                      StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->selectingRevServerHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+//************************** removingRevPtrsHandler tests *****************
+
+// Tests that removingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_InvalidEvent) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler but with
+    // an invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->removingRevPtrsHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_RevOnlyOK) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                      NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Run removingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Verify that an update message was constructed properly.
+    checkRemoveRevPtrsRequest(*name_remove);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                  StateModel::NOP_EVT);
+
+    // Simulate receiving a successful update response.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+    // Run removingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Forward completion should be false, reverse should be true.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+    // Since it is a reverse change, we should be done.
+    // Verify that we transitioned correctly.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                  NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates FQDN is NOT in use.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Should not be an update message yet.
+    D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+    ASSERT_FALSE(update_msg);
+
+    // At this point completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Run removingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Verify that an update message was constructed properly.
+    checkRemoveRevPtrsRequest(*name_remove);
+
+    // Verify that we are still in this state and next event is NOP_EVT.
+    // This indicates we "sent" the message and are waiting for IO completion.
+    CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                  StateModel::NOP_EVT);
+
+    // Simulate receiving a RRSET does not exist.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+    // Run removingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Forward completion should be false, reverse should be true.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+    // Since it is a reverse change, we should be done.
+    // Verify that we transitioned correctly.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                  NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent without error.
+//  A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_OtherRcode) {
+    SimpleRemoveStubPtr name_remove;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectRevServer());
+
+    // Run removingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Simulate receiving server rejection response. Per RFC, anything other
+    // than no error is failure.  Arbitrarily choosing refused.
+    name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+    // Run removingRevPtrsHandler again to process the response.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Completion flags should still be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // We should have failed the transaction. Verify that we transitioned
+    // correctly.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_Timeout) {
+    SimpleRemoveStubPtr name_remove;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                      NameChangeTransaction::SERVER_SELECTED_EVT,
+                                      REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_remove->getDnsUpdateRequest();
+
+        // Run removingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_remove->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time around we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server IO timeout.
+        name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+        name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run removingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+        EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The update request is sent but a corrupt response is received, this occurs
+//  MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_CorruptResponse) {
+    SimpleRemoveStubPtr name_remove;
+    // Create the transaction.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    // Select a server to satisfy log statements.
+    ASSERT_TRUE(name_remove->selectRevServer());
+
+    // Verify that we can make maximum number of update attempts permitted
+    // and then transition to selecting a new server.
+    int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+    for (int i = 1; i <= max_tries; ++i) {
+        const D2UpdateMessagePtr prev_msg = name_remove->getDnsUpdateRequest();
+
+        // Run removingRevPtrsHandler to send the request.
+        EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+        const D2UpdateMessagePtr curr_msg = name_remove->getDnsUpdateRequest();
+        if (i == 1) {
+            // First time around we should build the message.
+            EXPECT_FALSE(prev_msg);
+            EXPECT_TRUE(curr_msg);
+        } else {
+            // Subsequent passes should reuse the request. We are only
+            // looking to check that we have not replaced the pointer value
+            // with a new pointer.  This tests the on_entry() logic which
+            // clears the request ONLY upon initial entry into the state.
+            EXPECT_TRUE(prev_msg == curr_msg);
+        }
+
+        // Simulate a server corrupt response.
+        name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+        name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+        // Run removingRevPtrsHandler again to process the response.
+        EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+        // Completion flags should be false.
+        EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+        EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+        if (i < max_tries) {
+            // We should be ready to try again.
+            CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                          NameChangeTransaction::SERVER_SELECTED_EVT);
+        } else {
+            // Server retries should be exhausted, time for a new server.
+            CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+                          NameChangeTransaction::SERVER_IO_ERROR_EVT);
+        }
+    }
+}
+
+// Tests the processRemoveOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveOkHandler) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                      NameChangeTransaction::UPDATE_OK_EVT)
+    );
+
+    // Run processRemoveOkHandler.
+    EXPECT_NO_THROW(name_remove->processRemoveOkHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_remove->getNcrStatus());
+
+    // Verify that the model has ended.
+    CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+                                      StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->processRemoveOkHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                      NameChangeTransaction::UPDATE_FAILED_EVT)
+    );
+
+    // Run processRemoveFailedHandler.
+    EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+    // Create and prep transaction, poised to run the handler but with an
+    // invalid event.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                      StateModel::NOP_EVT)
+    );
+
+    // Running the handler should throw.
+    EXPECT_THROW(name_remove->processRemoveFailedHandler(),
+                 SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_NoMoreServers) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                    NameChangeTransaction::NO_MORE_SERVERS_EVT)
+    );
+
+    // Run processRemoveFailedHandler.
+    EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of SERVER_IO_ERROR_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_ServerIOError) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                                    NameChangeTransaction::SERVER_IO_ERROR_EVT)
+    );
+
+    // Run processRemoveFailedHandler.
+    EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_SendUpdateException) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    name_remove->simulate_send_exception_ = true;
+
+    // Run removingFwdRRsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrHandler with the following scenario:
+//
+//  The request includes only a reverse change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_SendUpdateException) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+    );
+
+    name_remove->simulate_send_exception_ = true;
+
+    // Run removingRevPtrsHandler to construct and send the request.
+    EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_BuildRequestException) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    // Set the one-shot exception simulation flag.
+    name_remove->simulate_build_request_exception_ = true;
+
+    // Run removingFwdRRsHandler to construct and send the request.
+    // This should fail with a build request throw which should be caught
+    // in the state handler.
+    ASSERT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+    // Verify we did not attempt to send anything.
+    EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPTRsHandler with the following scenario:
+//
+//  The request includes only a forward change.
+//  Initial posted event is SERVER_SELECTED_EVT.
+//  The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPTRsHandler_BuildRequestException) {
+    SimpleRemoveStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(
+        name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+                                    NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+    );
+
+    // Set the one-shot exception simulation flag.
+    name_remove->simulate_build_request_exception_ = true;
+
+    // Run removingRevPtrsHandler to construct and send the request.
+    // This should fail with a build request throw which should be caught
+    // in the state handler.
+    ASSERT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+    // Verify we did not attempt to send anything.
+    EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+    // Completion flags should be false.
+    EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+    EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+    // Since IO exceptions should be gracefully handled, any that occur
+    // are unanticipated, and deemed unrecoverable, so the transaction should
+    // be transitioned to failure.
+    CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+                  NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
index d0e25a64396a435637333c85a8590ae5ab5e5129..0f2d49503344aa6d2e5d100f62d9d76038ef2d76 100644 (file)
@@ -60,7 +60,8 @@ Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
         << dhcid_id_num << "\" , "
 
         " \"lease-expires-on\" : \"20140121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
index 25a70e413f4611fde81ad92a994fbc455913dc24..f54f3fc332c4aa7f3bee8da1bac61f37dc1b6ff4 100644 (file)
@@ -62,7 +62,8 @@ Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
         << dhcid_id_num << "\" , "
 
         " \"lease-expires-on\" : \"20140121132405\" , "
-        " \"lease-length\" : 1300 "
+        " \"lease-length\" : 1300, "
+        " \"use-conflict-resolution\" : true "
         "}";
 
     return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
index aabf8d24ae855725f58197746ed7b982997bee41..f38548434948600324ee58e2ae7a286bba9b7cc8 100644 (file)
@@ -200,7 +200,8 @@ operator<<(std::ostream& os, const D2Dhcid& dhcid) {
 NameChangeRequest::NameChangeRequest()
     : change_type_(CHG_ADD), forward_change_(false),
     reverse_change_(false), fqdn_(""), ip_io_address_("0.0.0.0"),
-    dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+    dhcid_(), lease_expires_on_(), lease_length_(0), conflict_resolution_(true),
+    status_(ST_NEW) {
 }
 
 NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
@@ -208,11 +209,13 @@ NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
             const std::string& fqdn, const std::string& ip_address,
             const D2Dhcid& dhcid,
             const uint64_t lease_expires_on,
-            const uint32_t lease_length)
+            const uint32_t lease_length,
+            const bool conflict_resolution)
     : change_type_(change_type), forward_change_(forward_change),
     reverse_change_(reverse_change), fqdn_(fqdn), ip_io_address_("0.0.0.0"),
     dhcid_(dhcid), lease_expires_on_(lease_expires_on),
-    lease_length_(lease_length), status_(ST_NEW) {
+    lease_length_(lease_length), conflict_resolution_(conflict_resolution),
+    status_(ST_NEW) {
 
     // User setter to validate fqdn.
     setFqdn(fqdn);
@@ -341,6 +344,10 @@ NameChangeRequest::fromJSON(const std::string& json) {
     element = ncr->getElement("lease-length", element_map);
     ncr->setLeaseLength(element);
 
+    /// @todo Should this be optional (i.e. backward compatible)?
+    element = ncr->getElement("use-conflict-resolution", element_map);
+    ncr->setConflictResolution(element);
+
     // All members were in the Element set and were correct lexically. Now
     // validate the overall content semantically.  This will throw an
     // NcrMessageError if anything is amiss.
@@ -366,7 +373,9 @@ NameChangeRequest::toJSON() const  {
         << "\"ip-address\":\"" << getIpAddress() << "\","
         << "\"dhcid\":\"" << getDhcid().toStr() << "\","
         << "\"lease-expires-on\":\""  << getLeaseExpiresOnStr() << "\","
-        << "\"lease-length\":" << getLeaseLength() << "}";
+        << "\"lease-length\":" << getLeaseLength() << ","
+        << "\"use-conflict-resolution\":"
+        << (useConflictResolution() ? "true" : "false") << "}";
 
     return (stream.str());
 }
@@ -576,6 +585,27 @@ NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
     setLeaseLength(static_cast<uint32_t>(value));
 }
 
+void
+NameChangeRequest::setConflictResolution(const bool value) {
+    conflict_resolution_ = value;
+}
+
+void
+NameChangeRequest::setConflictResolution(isc::data::ConstElementPtr element) {
+    bool value;
+    try {
+        // Get the element's boolean value.
+        value = element->boolValue();
+    } catch (const isc::data::TypeError& ex) {
+        // We expect a boolean Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for use-conflict-resolution: " << ex.what());
+    }
+
+    // Good to go, make the assignment.
+    setConflictResolution(value);
+}
+
 void
 NameChangeRequest::setStatus(const NameChangeStatus value) {
     status_ = value;
@@ -606,7 +636,9 @@ NameChangeRequest::toText() const {
            << "IP Address: [" << ip_io_address_ << "]" << std::endl
            << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
            << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
-           << "Lease Length: " << lease_length_ << std::endl;
+           << "Lease Length: " << lease_length_ << std::endl
+           << "Conflict Resolution: " << (conflict_resolution_ ? "yes" : "no")
+           << std::endl;
 
     return (stream.str());
 }
@@ -620,7 +652,8 @@ NameChangeRequest::operator == (const NameChangeRequest& other) {
             (ip_io_address_ == other.ip_io_address_) &&
             (dhcid_ == other.dhcid_) &&
             (lease_expires_on_ == other.lease_expires_on_) &&
-            (lease_length_ == other.lease_length_));
+            (lease_length_ == other.lease_length_) &&
+            (conflict_resolution_ == other.conflict_resolution_));
 }
 
 bool
index 958f57509d19175b67b5a0c43e4ac77d58c9874f..1a718a1f65d5a219b82117e3c75d8894c74e564a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -253,12 +253,15 @@ public:
     /// expires.
     /// @param lease_length the amount of time in seconds for which the
     /// lease is valid (TTL).
+    /// @param conflict_resolution indicates whether or not conflict resolution
+    /// (per RFC 4703) is enabled.
     NameChangeRequest(const NameChangeType change_type,
                       const bool forward_change, const bool reverse_change,
                       const std::string& fqdn, const std::string& ip_address,
                       const D2Dhcid& dhcid,
                       const uint64_t lease_expires_on,
-                      const uint32_t lease_length);
+                      const uint32_t lease_length,
+                      const bool conflict_resolution = true);
 
     /// @brief Static method for creating a NameChangeRequest from a
     /// buffer containing a marshalled request in a given format.
@@ -321,7 +324,8 @@ public:
     ///      "ip-address" : "<address>",
     ///      "dhcid" : "<hex_string>",
     ///      "lease-expires-on" : "<yyyymmddHHMMSS>",
-    ///      "lease-length" : <secs>
+    ///      "lease-length" : <secs>,
+    ///      "use-conflict-resolution": <boolean>
     ///     }
     /// @endcode
     ///
@@ -357,6 +361,8 @@ public:
     ///     - SS - seconds of the minute (0-59)
     /// - lease-length - the length of the lease in seconds.  This is an
     ///   integer and may range between 1 and 4294967295 (2^32 - 1) inclusive.
+    /// - use-conflict-resolution - when true, follow RFC 4703 which uses
+    ///   DHCID records to prohibit multiple clients from updating an FQDN
     ///
     /// Examples:
     ///
@@ -371,7 +377,8 @@ public:
     ///     "ip-address" : "192.168.2.1" ,
     ///     "dhcid" : "010203040A7F8E3D" ,
     ///     "lease-expires-on" : "20130121132405",
-    ///     "lease-length" : 1300
+    ///     "lease-length" : 1300,
+    ///     "use-conflict-resolution": true
     ///  }
     /// @endcode
     ///
@@ -386,7 +393,8 @@ public:
     ///     "ip-address" : "2001::db8:1::2",
     ///     "dhcid" : "010203040A7F8E3D" , "
     ///     "lease-expires-on" : "20130121132405",
-    ///     "lease-length" : 27400
+    ///     "lease-length" : 27400,
+    ///     "use-conflict-resolution": true
     ///   }
     /// @endcode
     ///
@@ -649,6 +657,28 @@ public:
     /// Element
     void setLeaseLength(isc::data::ConstElementPtr element);
 
+    /// @brief Checks if conflict resolution is enabled
+    ///
+    /// @return a true if the conflict resolution is enabled.
+    bool useConflictResolution() const {
+        return (conflict_resolution_);
+    }
+
+    /// @brief Sets the conflict resolution flag to the given value.
+    ///
+    /// @param value contains the new value to assign to the conflict
+    /// resolution flag
+    void setConflictResolution(const bool value);
+
+    /// @brief Sets the conflict resolution flag to the value of the given Element.
+    ///
+    /// @param element is a boolean Element containing the conflict resolution flag
+    /// value.
+    ///
+    /// @throw NcrMessageError if the element is not a boolean
+    /// Element
+    void setConflictResolution(isc::data::ConstElementPtr element);
+
     /// @brief Fetches the request status.
     ///
     /// @return the request status as a NameChangeStatus
@@ -717,6 +747,9 @@ private:
     /// @brief The amount of time in seconds for which the lease is valid (TTL).
     uint32_t lease_length_;
 
+    /// @brief Indicates if conflict resoltuion is enabled.
+    bool conflict_resolution_;
+
     /// @brief The processing status of the request.  Used internally.
     NameChangeStatus status_;
 };
index f910d5eada1d20b62010f6433059049e3c928d83..c12b646afb1592dbfe992ff6b9c436c326ffb202 100644 (file)
@@ -41,7 +41,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Valid Remove.
      "{"
@@ -52,7 +53,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": false"
      "}",
      // Valid Add with IPv6 address
      "{"
@@ -63,7 +65,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\" : true"
      "}"
 };
 
index fe76829793834069a317de147e841b37d76b432e..059c6bd76bb9cc053afbb430b82d79213e43d823 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -11,6 +11,7 @@
 #include <dhcp/hwaddr.h>
 #include <util/time_utilities.h>
 
+#include <testutils/gtest_utils.h>
 #include <gtest/gtest.h>
 #include <algorithm>
 
@@ -34,7 +35,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Valid Remove.
      "{"
@@ -45,7 +47,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
      // Valid Add with IPv6 address
      "{"
@@ -56,7 +59,8 @@ const char *valid_msgs[] =
      " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}"
 };
 
@@ -73,7 +77,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Invalid forward change.
      "{"
@@ -84,7 +89,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Invalid reverse change.
      "{"
@@ -95,7 +101,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Forward and reverse change both false.
      "{"
@@ -106,7 +113,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Blank FQDN
      "{"
@@ -117,7 +125,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Malformed FQDN
      "{"
@@ -128,7 +137,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Bad IP address
      "{"
@@ -140,6 +150,7 @@ const char *invalid_msgs[] =
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
      " \"lease-length\" : 1300 "
+     " \"use-conflict-resolution\": true"
      "}",
     // Blank DHCID
      "{"
@@ -150,7 +161,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Odd number of digits in DHCID
      "{"
@@ -161,7 +173,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Text in DHCID
      "{"
@@ -172,7 +185,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Invalid lease expiration string
      "{"
@@ -183,7 +197,8 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
-     " \"lease-length\" : 1300 "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": true"
      "}",
     // Non-integer for lease length.
      "{"
@@ -194,9 +209,32 @@ const char *invalid_msgs[] =
      " \"ip-address\" : \"192.168.2.1\" , "
      " \"dhcid\" : \"010203040A7F8E3D\" , "
      " \"lease-expires-on\" : \"20130121132405\" , "
-     " \"lease-length\" : \"BOGUS\" "
+     " \"lease-length\" : \"BOGUS\", "
+     " \"use-conflict-resolution\": true"
+     "}",
+    // Missing use-conflict-resolution
+     "{"
+     " \"change-type\" : 0 , "
+     " \"forward-change\" : true , "
+     " \"reverse-change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip-address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease-expires-on\" : \"20130121132405\" , "
+     " \"lease-length\" : 1300 "
+     "}",
+    // Invalid use-conflict-resolution
+     "{"
+     " \"change-type\" : 0 , "
+     " \"forward-change\" : true , "
+     " \"reverse-change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip-address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease-expires-on\" : \"20130121132405\" , "
+     " \"lease-length\" : 1300, "
+     " \"use-conflict-resolution\": 777"
      "}"
-
 };
 
 /// @brief Tests the NameChangeRequest constructors.
@@ -471,13 +509,14 @@ TEST(NameChangeRequestTest, basicJsonTest) {
                             "\"ip-address\":\"192.168.2.1\","
                             "\"dhcid\":\"010203040A7F8E3D\","
                             "\"lease-expires-on\":\"20130121132405\","
-                            "\"lease-length\":1300"
+                            "\"lease-length\":1300,"
+                            "\"use-conflict-resolution\":true"
                           "}";
 
     // Verify that a NameChangeRequests can be instantiated from the
     // a valid JSON rendition.
     NameChangeRequestPtr ncr;
-    ASSERT_NO_THROW(ncr  = NameChangeRequest::fromJSON(msg_str));
+    ASSERT_NO_THROW_LOG(ncr  = NameChangeRequest::fromJSON(msg_str));
     ASSERT_TRUE(ncr);
 
     // Verify that the JSON string created by the new request equals the
@@ -556,7 +595,8 @@ TEST(NameChangeRequestTest, toFromBufferTest) {
                             "\"ip-address\":\"192.168.2.1\","
                             "\"dhcid\":\"010203040A7F8E3D\","
                             "\"lease-expires-on\":\"20130121132405\","
-                            "\"lease-length\":1300"
+                            "\"lease-length\":1300,"
+                            "\"use-conflict-resolution\":true"
                           "}";
 
     // Create a request from JSON directly.
index fef107b2cad8d849dfe5ff5cdb7d3511cf377aba..1781ff95c4218568467be0ce13fbc7902c8e10b4 100644 (file)
@@ -180,7 +180,8 @@ public:
             " \"ip-address\" : \"192.168.2.1\" , "
             " \"dhcid\" : \"010203040A7F8E3D\" , "
             " \"lease-expires-on\" : \"20140121132405\" , "
-            " \"lease-length\" : 1300 "
+            " \"lease-length\" : 1300, "
+            " \"use-conflict-resolution\" : true "
             "}";
 
         return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));