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
#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>
// 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.
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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)
" \"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.
" \"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
" \"ip-address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
"}",
// Valid Remove.
"{"
" \"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
"{"
" \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
"}"
};
#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>
" \"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"};
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.
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());
}
}
+/// @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());
+}
+
}
" \"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;
" \"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;
" \"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.
" \"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.
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
-// 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
/// @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.
///
/// @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
" \"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;
--- /dev/null
+// 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);
+}
+
+}
--- /dev/null
+// 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);
+}
+
+}
<< dhcid_id_num << "\" , "
" \"lease-expires-on\" : \"20140121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
"}";
return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
<< dhcid_id_num << "\" , "
" \"lease-expires-on\" : \"20140121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
"}";
return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
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,
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);
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.
<< "\"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());
}
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;
<< "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());
}
(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
-// 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
/// 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.
/// "ip-address" : "<address>",
/// "dhcid" : "<hex_string>",
/// "lease-expires-on" : "<yyyymmddHHMMSS>",
- /// "lease-length" : <secs>
+ /// "lease-length" : <secs>,
+ /// "use-conflict-resolution": <boolean>
/// }
/// @endcode
///
/// - 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:
///
/// "ip-address" : "192.168.2.1" ,
/// "dhcid" : "010203040A7F8E3D" ,
/// "lease-expires-on" : "20130121132405",
- /// "lease-length" : 1300
+ /// "lease-length" : 1300,
+ /// "use-conflict-resolution": true
/// }
/// @endcode
///
/// "ip-address" : "2001::db8:1::2",
/// "dhcid" : "010203040A7F8E3D" , "
/// "lease-expires-on" : "20130121132405",
- /// "lease-length" : 27400
+ /// "lease-length" : 27400,
+ /// "use-conflict-resolution": true
/// }
/// @endcode
///
/// 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
/// @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_;
};
" \"ip-address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
"}",
// Valid Remove.
"{"
" \"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
"{"
" \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true"
"}"
};
-// 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
#include <dhcp/hwaddr.h>
#include <util/time_utilities.h>
+#include <testutils/gtest_utils.h>
#include <gtest/gtest.h>
#include <algorithm>
" \"ip-address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
"}",
// Valid Remove.
"{"
" \"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
"{"
" \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
"}"
};
" \"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.
"{"
" \"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.
"{"
" \"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.
"{"
" \"ip-address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
"}",
// Blank FQDN
"{"
" \"ip-address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
- " \"lease-length\" : 1300 "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
"}",
// Malformed FQDN
"{"
" \"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
"{"
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease-expires-on\" : \"20130121132405\" , "
" \"lease-length\" : 1300 "
+ " \"use-conflict-resolution\": true"
"}",
// Blank DHCID
"{"
" \"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
"{"
" \"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
"{"
" \"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
"{"
" \"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.
"{"
" \"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.
"\"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
"\"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.
" \"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));