Parcourir la source

[3087] Initial implementation of d2::NameAddTransaction

This interrim checkin includes the initial implementation of
NameAddTransaction to b10-dhcp-ddns.  It provides the preliminary
state model needed to add forward and/or reverse DNS entries for
and FQDN.  It does not yet construct the actual DNS update requests.
Thomas Markwalder il y a 11 ans
Parent
commit
4c16336d81

+ 1 - 0
src/bin/d2/Makefile.am

@@ -64,6 +64,7 @@ b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
+b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 

+ 60 - 0
src/bin/d2/d2_messages.mes

@@ -258,3 +258,63 @@ This is error message issued when the application fails to process a
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be
 reported.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Server, %1, rejected a DNS update request to add the address mapping for FQDN, %1, with an RCODE: %3
+This is an error message issued when an udpate was rejected by the DNS server itwas sent to for the reason given by the RCODE. The rcode values are defined in
+RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR while attempting a request to add a forward address mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an IO error
+This is an error message issued when a communication error occurs while DHCP_DDNS is carrying out a forward address update.  The application will retry against
+the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT while attempting a request to add a forward address mapping to DNS server, %1, for FQDN, %2, DHCP_DDNS received a corrupt response
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping,  is mangled or mal-formed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_UNKNOWN_FAILURE while attempting a request to add a forward address mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an unexepected error.
+This is an error message issued when a unexpected error condition error occurs
+while DHCP_DDNS is carrying out a forward address update.  The request will be
+be aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the address mapping for FQDN, %1, with an RCODE: %3
+This is an error message issued when an udpate was rejected by the DNS server itwas sent to for the reason given by the RCODE. The rcode values are defined in
+RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR while attempting a request to replace a forward address mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an IO error
+This is an error message issued when a communication error occurs while DHCP_DDNS is carrying out a forward address update.  The application will retry against
+the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT while attempting a request to replace a forward address mapping to DNS server, %1, for FQDN, %2, DHCP_DDNS received a corrupt response
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping,  is mangled or mal-formed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_UNKNOWN_FAILURE while attempting a request to replace a forward address mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an unexepected error.
+This is an error message issued when a unexpected error condition error occurs
+while DHCP_DDNS is carrying out a forward address update.  The request will be
+be aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the reverse mapping for FQDN, %1, with an RCODE: %3
+This is an error message issued when an udpate was rejected by the DNS server itwas sent to for the reason given by the RCODE. The rcode values are defined in
+RFC 2136.
+
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR while attempting a request to replace a reverse mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an IO error
+This is an error message issued when a communication error occurs while DHCP_DDNS is carrying out a reverse address update.  The application will retry against
+the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT while attempting a request to replace a reverse mapping to DNS server, %1, for FQDN, %2, DHCP_DDNS received a corrupt response
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a reverse address,  is mangled or mal-formed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_UNKNOWN_FAILURE while attempting a request to replace a reverse mapping to DNS server %1 for FQDN %2, DHCP_DDNS encountered an unexepected error.
+This is an error message issued when a unexpected error condition error occurs
+while DHCP_DDNS is carrying out a reverse address update.  The request will be
+be aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_TRANS_SEND_EROR application encountered an unexpected error while attempting to send an DNS update: %1
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered a unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.

+ 531 - 0
src/bin/d2/nc_add.cc

@@ -0,0 +1,531 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/nc_add.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace d2 {
+
+// NameAddTransaction states
+const int NameAddTransaction::ADDING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_REV_PTRS_ST;
+
+// NameAddTransaction events
+const int NameAddTransaction::FQDN_IN_USE_EVT;
+const int NameAddTransaction::FQDN_NOT_IN_USE_EVT;
+
+NameAddTransaction::
+NameAddTransaction(IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain)
+    : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain) {
+    if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
+        isc_throw (NameAddTransactionError,
+                   "NameAddTransaction, request type must be CHG_ADD");
+    }
+}
+
+NameAddTransaction::~NameAddTransaction(){
+}
+
+void
+NameAddTransaction::defineEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineEvents();
+
+    // Define NCT events.
+    defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
+    defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
+}
+
+void
+NameAddTransaction::verifyEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::verifyEvents();
+
+    // Verify NCT events.
+    getEvent(FQDN_IN_USE_EVT);
+    getEvent(FQDN_NOT_IN_USE_EVT);
+}
+
+void
+NameAddTransaction::defineStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineStates();
+
+    // Define the states.
+    defineState(READY_ST, "READY_ST",
+             boost::bind(&NameAddTransaction::readyHandler, this));
+
+    defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+             boost::bind(&NameAddTransaction::selectingFwdServerHandler, this));
+
+    defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+             boost::bind(&NameAddTransaction::selectingRevServerHandler, this));
+
+    defineState(ADDING_FWD_ADDRS_ST, "ADDING_FWD_ADDRS_ST",
+             boost::bind(&NameAddTransaction::addingFwdAddrsHandler, this));
+
+    defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST",
+             boost::bind(&NameAddTransaction::replacingFwdAddrsHandler, this));
+
+    defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST",
+             boost::bind(&NameAddTransaction::replacingRevPtrsHandler, this));
+
+    defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+             boost::bind(&NameAddTransaction::processAddOkHandler, this));
+
+    defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+             boost::bind(&NameAddTransaction::processAddFailedHandler, this));
+
+}
+void
+NameAddTransaction::verifyStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::verifyStates();
+
+    // Verify NCT states. This ensures that derivations provide the handlers.
+    getState(ADDING_FWD_ADDRS_ST);
+    getState(REPLACING_FWD_ADDRS_ST);
+    getState(REPLACING_REV_PTRS_ST);
+}
+
+void
+NameAddTransaction::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(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::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(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(ADDING_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
+NameAddTransaction::addingFwdAddrsHandler() {
+    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.
+            buildAddFwdAddressRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        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 if (rcode == dns::Rcode::YXDOMAIN()) {
+                // FQDN is in use so we need to attempt to replace
+                // forward address.
+                transition(REPLACING_FWD_ADDRS_ST, FQDN_IN_USE_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should we try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_REJECTED)
+                          .arg(getNcr()->getFqdn())
+                          .arg(getCurrentServer()->getIpAddress())
+                          .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(dctl_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            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(dctl_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_ADD_UNKNOWN_FAILURE)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            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(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::replacingFwdAddrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case FQDN_IN_USE_EVT:
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            buildReplaceFwdAddressRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        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 replace the forward mapping. 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 if (rcode == dns::Rcode::NXDOMAIN()) {
+                // FQDN is NOT in use so go back and do the forward add address.
+                // Covers the case that it was there when we tried to add it,
+                // but has since been removed per RFC 4703.
+                transition(ADDING_FWD_ADDRS_ST, SERVER_SELECTED_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(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_REJECTED)
+                          .arg(getNcr()->getFqdn())
+                          .arg(getCurrentServer()->getIpAddress())
+                          .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(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_IO_ERROR)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .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_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REPLACE_UNKNOWN_FAILURE)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            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(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::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(NameAddTransactionError,
+                  "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
+NameAddTransaction::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.
+            buildReplaceRevPtrsRequest();
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        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(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED)
+                          .arg(getNcr()->getFqdn())
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            // @todo clearing request seems clunky here
+            // safest thing to do is to always rebuild it
+            clearDnsUpdateRequest();
+            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(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .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;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // 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(dctl_logger, DHCP_DDNS_REVERSE_REPLACE_UNKNOWN_FAILURE)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            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(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::processAddOkHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+        // @todo do we need a log statement here?
+        setNcrStatus(dhcp_ddns::ST_COMPLETED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::processAddFailedHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_FAILED_EVT:
+        // @todo do we need a log statement here?
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameAddTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameAddTransaction::buildAddFwdAddressRequest() {
+    isc_throw(NotImplemented, "buildAddFwdAddressRequest");
+}
+
+void
+NameAddTransaction::buildReplaceFwdAddressRequest() {
+    isc_throw(NotImplemented, "buildReplaceFwdAddressRequest");
+}
+
+void
+NameAddTransaction::buildReplaceRevPtrsRequest() {
+    isc_throw(NotImplemented, "buildReplaceRevPtrsRequest");
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 384 - 0
src/bin/d2/nc_add.h

@@ -0,0 +1,384 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NC_ADD_H
+#define NC_ADD_H
+
+/// @file nc_add.h This file defines the class NameAddTransaction.
+
+#include <d2/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the NameAddTransaction encounters a general error.
+class NameAddTransactionError : public isc::Exception {
+public:
+    NameAddTransactionError(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.
+///
+/// NameAddTransaction implements a state machine for adding (or replacing) a
+/// forward 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 NameAddTransaction : public NameChangeTransaction {
+public:
+
+    //@{  Additional states needed for NameAdd state model.
+    /// @brief State that attempts to add forward address records.
+    static const int ADDING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+    /// @brief State that attempts to replace forward address records.
+    static const int REPLACING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+    /// @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 NameAdd 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
+    ///
+    /// @throw NameAddTransaction error if given request is not a CHG_ADD,
+    /// NameChangeTransaction error for base class construction errors.
+    NameAddTransaction(IOServicePtr& io_service,
+                       dhcp_ddns::NameChangeRequestPtr& ncr,
+                       DdnsDomainPtr& forward_domain,
+                       DdnsDomainPtr& reverse_domain);
+
+    /// @brief Destructor
+    virtual ~NameAddTransaction();
+
+protected:
+    /// @brief Adds events defined by NameAddTransaction 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 events.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyEvents();
+
+    /// @brief Adds states defined by NameAddTransaction 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.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
+
+    /// @brief State handler for R
+    ///
+    /// Entered from:
+    /// - INIT_ST with next event of START_EVT
+    ///
+    /// Servers as the starting state handler.
+    ///
+    /// 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 NameAddTransactionError if upon entry next event is not
+    /// START_EVT.EADY_ST.
+    void readyHandler();
+
+    /// @brief State handler for SELECTING_FWD_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - ADDING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_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:
+    /// - ADDING_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 NameAddTransactionError 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
+    /// - ADDING_FWD_ADDRS_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:
+    /// - ADDING_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 NameAddTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingRevServerHandler();
+
+    /// @brief State handler for ADD_FWD_ADDRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+    /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to add 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.
+    ///
+    /// If the handler is invoked with a next event of IO_COMPELTED_EVT, then
+    /// 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.
+    ///
+    /// - REPLACING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT if the DNS
+    /// server response indicates that an entry for the given FQDN already
+    /// exists.
+    ///
+    /// - 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 NameAddTransactionError if upon entry next event is not
+    /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+    void addingFwdAddrsHandler();
+
+    /// @brief State handler for .
+    ///
+    /// Entered from:
+    /// - ADDING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT
+    ///
+    /// Attempts to delete and then add 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 FDQN_IN_USE_EVT or SERVER_SELECTED_EVT, the handler
+    /// builds the forward replacement request, schedules an asynchronous send
+    /// via sendUpdate(), and returns.  Note that sendUpdate will post NOP_EVT
+    /// as the next event.
+    ///
+    /// If the handler is invoked with a next event of IO_COMPELTED_EVT, then
+    /// 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 replacement and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// replacement and the request does not include a reverse DNS update.
+    ///
+    /// - ADDING_FWD_ADDR_STR with a next event of SERVER_SELECTED_EVT  if the
+    /// DNS server response indicates that the FQDN is not in use.  This could
+    /// occur if a previous add attempt indicated the FQDN was in use, but
+    /// that entry has since been removed by another entity prior to this
+    /// replacement attempt.
+    ///
+    /// - 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.
+    ///
+    /// - 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 NameAddTransactionError if upon entry next event is not:
+    /// FQDN_IN_USE_EVT, 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.
+    ///
+    /// If the handler is invoked with a next event of IO_COMPELTED_EVT, then
+    /// 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_OK_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 NameAddTransactionError 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:
+    /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+    /// - 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 NameAddTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT
+    void processAddOkHandler();
+
+    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+    ///
+    /// Entered from:
+    /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - 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 NameAddTransactionError if upon entry next event is not:
+    /// UPDATE_FAILED_EVT
+    void processAddFailedHandler();
+
+    /// @brief Builds a DNS request to add an forward DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildAddFwdAddressRequest();
+
+    /// @brief Builds a DNS request to replace forward DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildReplaceFwdAddressRequest();
+
+    /// @brief Builds a DNS request to replace a reverse DNS entry for an FQDN
+    ///
+    /// @todo - Method not implemented yet
+    ///
+    /// @throw isc::NotImplemented
+    void buildReplaceRevPtrsRequest();
+
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameAddTransaction> NameAddTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif

+ 85 - 3
src/bin/d2/nc_trans.cc

@@ -38,16 +38,20 @@ const int NameChangeTransaction::UPDATE_FAILED_EVT;
 
 const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
 
+const unsigned int NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT;
+const unsigned int NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+
 NameChangeTransaction::
 NameChangeTransaction(IOServicePtr& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_domain)
     : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
-     reverse_domain_(reverse_domain), dns_client_(),
+     reverse_domain_(reverse_domain), dns_client_(), dns_update_request_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      forward_change_completed_(false), reverse_change_completed_(false),
-     current_server_list_(), current_server_(), next_server_pos_(0) {
+     current_server_list_(), current_server_(), next_server_pos_(0),
+     update_attempts_(0) {
     // @todo if io_service is NULL we are multi-threading and should
     // instantiate our own
     if (!io_service_) {
@@ -89,6 +93,38 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
 }
 
 void
+NameChangeTransaction::sendUpdate(bool /* use_tsig_ */) {
+    try {
+        ++update_attempts_;
+        // @todo add logic to add/replace TSIG key info in request if
+        // use_tsig_ is true. We should be able to navigate to the TSIG key
+        // for the current server.  If not we would need to add that.
+
+        // @todo time out should ultimately be configurable, down to
+        // server level?
+        dns_client_->doUpdate(io_service_, current_server_->getIpAddress(),
+                              current_server_->getPort(), *dns_update_request_,
+                              DNS_UPDATE_DEFAULT_TIMEOUT);
+
+        // Message is on its way, so the next event should be NOP_EVT.
+        postNextEvent(NOP_EVT);
+    } catch (const std::exception& ex) {
+        // We were unable to initiate the send.
+        // It is presumed that any throw from doUpdate is due to a programmatic
+        // error, such as an unforeseen permutation of data, rather than an IO
+        // failure. IO errors should be caught by the underlying asiolink
+        // mechansisms and manifested as an unsuccessful IO statu in the
+        // DNSClient callback.  Any problem here most likely means the request
+        // is corrupt in some way and cannot be completed, therefore we will
+        // log it, mark it as failed, and set next event to NOP_EVT.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_TRANS_SEND_EROR).arg(ex.what());
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        // @todo is this right?
+        postNextEvent(NOP_EVT);
+    }
+}
+
+void
 NameChangeTransaction::defineEvents() {
     // Call superclass impl first.
     StateModel::defineEvents();
@@ -147,11 +183,43 @@ NameChangeTransaction::onModelFailure(const std::string& explanation) {
 }
 
 void
+NameChangeTransaction::retryTransition(int server_sel_state) {
+    if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) {
+        // Re-enter the current state with same server selected.
+        transition(getCurrState(), SERVER_SELECTED_EVT);
+    } else  {
+        // Transition to given server selection state if we are out
+        // of retries.
+        transition(server_sel_state, SERVER_IO_ERROR_EVT);
+    }
+}
+
+void
+NameChangeTransaction::setDnsUpdateRequest(D2UpdateMessagePtr& request) {
+    dns_update_request_ = request;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateRequest() {
+    dns_update_request_.reset();
+}
+
+void
 NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
     dns_update_status_ = status;
 }
 
 void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+    dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateResponse() {
+    dns_update_response_.reset();
+}
+
+void
 NameChangeTransaction::setForwardChangeCompleted(const bool value) {
     forward_change_completed_ = value;
 }
@@ -161,6 +229,11 @@ NameChangeTransaction::setReverseChangeCompleted(const bool value) {
     reverse_change_completed_ = value;
 }
 
+void
+NameChangeTransaction::setUpdateAttempts(size_t value) {
+    update_attempts_ = value;
+}
+
 const dhcp_ddns::NameChangeRequestPtr&
 NameChangeTransaction::getNcr() const {
     return (ncr_);
@@ -227,12 +300,16 @@ NameChangeTransaction::getCurrentServer() const {
     return (current_server_);
 }
 
-
 void
 NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(status));
 }
 
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateRequest() const {
+    return (dns_update_request_);
+}
+
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
@@ -253,5 +330,10 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
 }
 
+size_t
+NameChangeTransaction::getUpdateAttempts() const {
+    return (update_attempts_);
+}
+
 } // namespace isc::d2
 } // namespace isc

+ 46 - 0
src/bin/d2/nc_trans.h

@@ -151,6 +151,12 @@ public:
     static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
     //@}
 
+    /// @brief Defualt time to assign to a single DNS udpate.
+    static const unsigned int DNS_UPDATE_DEFAULT_TIMEOUT = 5 * 1000;
+
+    /// @brief Maximum times to attempt a single update on a given server.
+    static const unsigned int MAX_UPDATE_TRIES_PER_SERVER = 3;
+
     /// @brief Constructor
     ///
     /// Instantiates a transaction that is ready to be started.
@@ -191,6 +197,9 @@ public:
     virtual void operator()(DNSClient::Status status);
 
 protected:
+    /// @todo
+    void sendUpdate(bool use_tsig_ = false);
+
     /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
     /// This method adds the events common to NCR transaction processing to
@@ -248,6 +257,17 @@ protected:
     /// @param explanation is text detailing the error
     virtual void onModelFailure(const std::string& explanation);
 
+    /// @todo
+    void retryTransition(int server_sel_state);
+
+    /// @brief Sets the update request packet to the given packet.
+    ///
+    /// @param request is the new request packet to assign.
+    void setDnsUpdateRequest(D2UpdateMessagePtr& request);
+
+    /// @brief Destroys the current update request packet.
+    void clearDnsUpdateRequest();
+
     /// @brief Sets the update status to the given status value.
     ///
     /// @param status is the new value for the update status.
@@ -258,6 +278,9 @@ protected:
     /// @param response is the new response packet to assign.
     void setDnsUpdateResponse(D2UpdateMessagePtr& response);
 
+    /// @brief Destroys the current update respons packet.
+    void clearDnsUpdateResponse();
+
     /// @brief Sets the forward change completion flag to the given value.
     ///
     /// @param value is the new value to assign to the flag.
@@ -307,6 +330,11 @@ protected:
     /// @return A const pointer reference to the DNSClient
     const DNSClientPtr& getDNSClient() const;
 
+    /// @brief Sets the update attempt count to the given value.
+    ///
+    /// @param value is the new value to assign.
+    void setUpdateAttempts(size_t value);
+
 public:
     /// @brief Fetches the NameChangeRequest for this transaction.
     ///
@@ -344,6 +372,12 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     DdnsDomainPtr& getReverseDomain();
 
+    /// @brief Fetches the current DNS update request packet.
+    ///
+    /// @return A const pointer reference to the current D2UpdateMessage
+    /// request.
+    const D2UpdateMessagePtr& getDnsUpdateRequest() const;
+
     /// @brief Fetches the most recent DNS update status.
     ///
     /// @return A DNSClient::Status indicating the result of the most recent
@@ -374,6 +408,12 @@ public:
     /// @return True if the reverse change has been completed, false otherwise.
     bool getReverseChangeCompleted() const;
 
+    /// @brief Fetches the update attempt count for the current update.
+    ///
+    /// @return size_t which is the number of times the current request has
+    /// been attempted against the current server.
+    size_t getUpdateAttempts() const;
+
 private:
     /// @brief The IOService which should be used to for IO processing.
     IOServicePtr io_service_;
@@ -398,6 +438,9 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     DNSClientPtr dns_client_;
 
+    /// @brief The DNS current update request packet.
+    D2UpdateMessagePtr dns_update_request_;
+
     /// @brief The outcome of the most recently completed DNS packet exchange.
     DNSClient::Status dns_update_status_;
 
@@ -421,6 +464,9 @@ private:
     /// This value is always the position of the next selection in the server
     /// list, which may be beyond the end of the list.
     size_t next_server_pos_;
+
+    // @todo
+    size_t update_attempts_;
 };
 
 /// @brief Defines a pointer to a NameChangeTransaction.

+ 2 - 0
src/bin/d2/tests/Makefile.am

@@ -66,6 +66,7 @@ d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
+d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
@@ -81,6 +82,7 @@ d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
+d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc

+ 165 - 0
src/bin/d2/tests/nc_add_unittests.cc

@@ -0,0 +1,165 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/nc_add.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Test class derived from NameAddTransaction to provide visiblity
+// to protected methods.
+class NameAddStub : public NameAddTransaction {
+public:
+    NameAddStub(IOServicePtr& io_service,
+                dhcp_ddns::NameChangeRequestPtr& ncr,
+                DdnsDomainPtr& forward_domain,
+                DdnsDomainPtr& reverse_domain)
+        : NameAddTransaction(io_service, ncr, forward_domain, reverse_domain){
+    }
+
+    virtual ~NameAddStub() {
+    }
+
+    using NameAddTransaction::defineEvents;
+    using NameAddTransaction::verifyEvents;
+    using NameAddTransaction::defineStates;
+    using NameAddTransaction::verifyStates;
+};
+
+typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
+
+/// @brief Test fixture for testing NameAddTransaction
+///
+/// Note this class uses NameAddStub class to exercise non-public
+/// aspects of NameAddTransaction.
+class NameAddTransactionTest : public ::testing::Test {
+public:
+    IOServicePtr io_service_;
+    DdnsDomainPtr forward_domain_;
+    DdnsDomainPtr reverse_domain_;
+
+    NameAddTransactionTest() : io_service_(new isc::asiolink::IOService()) {
+    }
+
+    virtual ~NameAddTransactionTest() {
+    }
+
+    /// @brief Instantiates a NameAddTransaction built around a canned
+    /// NameChangeRequest.
+    NameAddStubPtr makeCannedTransaction() {
+        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 "
+            "}";
+
+        dhcp_ddns::NameChangeRequestPtr ncr;
+
+        DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+        DnsServerInfoPtr server;
+
+        ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
+
+        // make forward server list
+        server.reset(new DnsServerInfo("forward.example.com",
+                                       isc::asiolink::IOAddress("1.1.1.1")));
+        servers->push_back(server);
+        forward_domain_.reset(new DdnsDomain("*", "", servers));
+
+        // make reverse server list
+        servers->clear();
+        server.reset(new DnsServerInfo("reverse.example.com",
+                                       isc::asiolink::IOAddress("2.2.2.2")));
+        servers->push_back(server);
+        reverse_domain_.reset(new DdnsDomain("*", "", servers));
+        return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+                                      forward_domain_, reverse_domain_)));
+
+    }
+
+};
+
+/// @brief Tests NameAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameAddTransaction, construction) {
+    IOServicePtr io_service(new isc::asiolink::IOService());
+
+    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 "
+        "}";
+
+    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(NameAddTransaction(io_service, ncr,
+                                    forward_domain, reverse_domain),
+                                    NameAddTransactionError);
+
+    // Verify that a valid construction attempt works.
+    ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+    EXPECT_NO_THROW(NameAddTransaction(io_service, ncr,
+                                       forward_domain, reverse_domain));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameAddTransactionTest, dictionaryCheck) {
+    NameAddStubPtr name_add;
+    ASSERT_NO_THROW(name_add = makeCannedTransaction());
+
+    // 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());
+}
+
+}

+ 62 - 5
src/bin/d2/tests/nc_trans_unittests.cc

@@ -193,8 +193,12 @@ public:
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getDNSClient;
     using NameChangeTransaction::setNcrStatus;
+    using NameChangeTransaction::setDnsUpdateRequest;
+    using NameChangeTransaction::clearDnsUpdateRequest;
     using NameChangeTransaction::setDnsUpdateStatus;
     using NameChangeTransaction::getDnsUpdateResponse;
+    using NameChangeTransaction::setDnsUpdateResponse;
+    using NameChangeTransaction::clearDnsUpdateResponse;
     using NameChangeTransaction::getForwardChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
@@ -213,9 +217,9 @@ public:
     IOServicePtr io_service_;
     DdnsDomainPtr forward_domain_;
     DdnsDomainPtr reverse_domain_;
-
+    
     NameChangeTransactionTest() : io_service_(new isc::asiolink::IOService()) {
-    }
+    } 
 
     virtual ~NameChangeTransactionTest() {
     }
@@ -296,6 +300,13 @@ TEST(NameChangeTransaction, construction) {
     ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
     ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
 
+    // Verify that construction with a null IOServicePtr fails.
+    // @todo Subject to change if multi-threading is implemenated.
+    IOServicePtr empty;
+    EXPECT_THROW(NameChangeTransaction(empty, ncr,
+                                       forward_domain, reverse_domain),
+                                       NameChangeTransactionError);
+
     // Verify that construction with an empty NameChangeRequest throws.
     EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
                                        forward_domain, reverse_domain),
@@ -366,9 +377,6 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
 
-    // Verify that the DNS update response can be retrieved.
-    EXPECT_FALSE(name_change->getDnsUpdateResponse());
-
     // Verify that the forward change complete flag can be set and fetched.
     EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
     EXPECT_TRUE(name_change->getForwardChangeCompleted());
@@ -378,6 +386,55 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
 }
 
+TEST_F(NameChangeTransactionTest, dnsUpdateRequestAccessors) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+    // Verify that the DNS update request accessors.
+    D2UpdateMessagePtr req;
+    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+
+    // Post construction it is empty.
+    EXPECT_FALSE(name_change->getDnsUpdateRequest());
+
+    /// @param request is the new request packet to assign.
+    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+    // Post set, we should be able to fetch it.
+    EXPECT_TRUE(name_change->getDnsUpdateRequest());
+
+    // Should be able to clear it.
+    ASSERT_NO_THROW(name_change->clearDnsUpdateRequest());
+
+    // Should be empty again.
+    EXPECT_FALSE(name_change->getDnsUpdateRequest());
+}
+
+TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Verify that the DNS update response accessors.
+    D2UpdateMessagePtr resp;
+    ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)));
+
+    // Post construction it is empty.
+    EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+    /// @param request is the new request packet to assign.
+    ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+    // Post set, we should be able to fetch it.
+    EXPECT_TRUE(name_change->getDnsUpdateResponse());
+
+    // Should be able to clear it.
+    ASSERT_NO_THROW(name_change->clearDnsUpdateResponse());
+
+    // Should be empty again.
+    EXPECT_FALSE(name_change->getDnsUpdateResponse());
+}
+
+
+
 /// @brief Tests event and state dictionary construction and verification.
 TEST_F(NameChangeTransactionTest, dictionaryCheck) {
     NameChangeStubPtr name_change;