From: Thomas Markwalder Date: Thu, 15 Oct 2020 19:03:59 +0000 (-0400) Subject: [#1386] D2 supports add and remove without conflict resolution X-Git-Tag: Kea-1.9.1~60 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cef56d36706ccd81251e64abf98d1dc7f3edd697;p=thirdparty%2Fkea.git [#1386] D2 supports add and remove without conflict resolution src/lib/dhcp_ddns/ncr_msg.* Added NameChangeRequest::conflict_resolution_ src/bin/d2/d2_update_mgr.cc D2UpdateMgr::makeTransaction() - use's the NCR's value for use-conflict-resolution() in determining which type of transacation to create. src/bin/d2/simple_add.* - new files SimpleAddTransaction() - new class which executes a DNS add without conflict resolution. src/bin/d2/simple_remove.* - new files SimpleRemoveTransaction() - new class which executes a DNS remove without conflict resolution. src/bin/d2/tests/simple_add_unittests.cc - new file Tests SimpleAddTransaction src/bin/d2/tests/simple_remove_unittests.cc - new file Tests SimpleRemoveTransaction src/bin/dhcp4/tests/d2_unittest.cc src/bin/dhcp6/tests/d2_unittest.cc src/bin/d2/tests/d2_process_unittests.cc src/bin/d2/tests/d2_queue_mgr_unittests.cc src/bin/d2/tests/d2_update_mgr_unittests.cc src/bin/d2/tests/nc_add_unittests.cc src/bin/d2/tests/nc_remove_unittests.cc src/bin/d2/tests/nc_trans_unittests.cc src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc src/lib/dhcp_ddns/tests/ncr_unittests.cc src/lib/dhcpsrv/tests/d2_udp_unittest.cc updated tests --- diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am index e1c27c0f8c..24ff5a557a 100644 --- a/src/bin/d2/Makefile.am +++ b/src/bin/d2/Makefile.am @@ -47,6 +47,8 @@ libd2_la_SOURCES += nc_trans.cc nc_trans.h libd2_la_SOURCES += d2_controller.cc d2_controller.h libd2_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h libd2_la_SOURCES += d2_messages.h d2_messages.cc +libd2_la_SOURCES += simple_add.cc simple_add.h +libd2_la_SOURCES += simple_remove.cc simple_remove.h EXTRA_DIST += d2_messages.mes sbin_PROGRAMS = kea-dhcp-ddns diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc index 322dd1f31e..28bc25df39 100644 --- a/src/bin/d2/d2_update_mgr.cc +++ b/src/bin/d2/d2_update_mgr.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -197,13 +199,25 @@ D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) { // the transaction's IO. NameChangeTransactionPtr trans; if (next_ncr->getChangeType() == dhcp_ddns::CHG_ADD) { - trans.reset(new NameAddTransaction(io_service_, next_ncr, - forward_domain, reverse_domain, - cfg_mgr_)); + if (next_ncr->useConflictResolution()) { + trans.reset(new NameAddTransaction(io_service_, next_ncr, + forward_domain, reverse_domain, + cfg_mgr_)); + } else { + trans.reset(new SimpleAddTransaction(io_service_, next_ncr, + forward_domain, reverse_domain, + cfg_mgr_)); + } } else { - trans.reset(new NameRemoveTransaction(io_service_, next_ncr, - forward_domain, reverse_domain, - cfg_mgr_)); + if (next_ncr->useConflictResolution()) { + trans.reset(new NameRemoveTransaction(io_service_, next_ncr, + forward_domain, reverse_domain, + cfg_mgr_)); + } else { + trans.reset(new SimpleRemoveTransaction(io_service_, next_ncr, + forward_domain, reverse_domain, + cfg_mgr_)); + } } // Add the new transaction to the list. diff --git a/src/bin/d2/simple_add.cc b/src/bin/d2/simple_add.cc new file mode 100644 index 0000000000..652966f374 --- /dev/null +++ b/src/bin/d2/simple_add.cc @@ -0,0 +1,544 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include + +#include +#include + +#include + +namespace isc { +namespace d2 { + +// SimpleAddTransaction states +const int SimpleAddTransaction::REPLACING_FWD_ADDRS_ST; +const int SimpleAddTransaction::REPLACING_REV_PTRS_ST; + +// SimpleAddTransaction events +const int SimpleAddTransaction::FQDN_IN_USE_EVT; +const int SimpleAddTransaction::FQDN_NOT_IN_USE_EVT; + +SimpleAddTransaction:: +SimpleAddTransaction(asiolink::IOServicePtr& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr& forward_domain, + DdnsDomainPtr& reverse_domain, + D2CfgMgrPtr& cfg_mgr) + : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain, + cfg_mgr) { + if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) { + isc_throw (SimpleAddTransactionError, + "SimpleAddTransaction, request type must be CHG_ADD"); + } +} + +SimpleAddTransaction::~SimpleAddTransaction(){ +} + +void +SimpleAddTransaction::defineEvents() { + // Call superclass impl first. + NameChangeTransaction::defineEvents(); + + // Define SimpleAddTransaction events. + defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT"); + defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT"); +} + +void +SimpleAddTransaction::verifyEvents() { + // Call superclass implementation first to verify its events. These are + // events common to all transactions, and they must be defined. + // SELECT_SERVER_EVT + // SERVER_SELECTED_EVT + // SERVER_IO_ERROR_EVT + // NO_MORE_SERVERS_EVT + // IO_COMPLETED_EVT + // UPDATE_OK_EVT + // UPDATE_FAILED_EVT + NameChangeTransaction::verifyEvents(); + + // Verify SimpleAddTransaction events by attempting to fetch them. + getEvent(FQDN_IN_USE_EVT); + getEvent(FQDN_NOT_IN_USE_EVT); +} + +void +SimpleAddTransaction::defineStates() { + // Call superclass impl first. + NameChangeTransaction::defineStates(); + + // Define SimpleAddTransaction states. + defineState(READY_ST, "READY_ST", + std::bind(&SimpleAddTransaction::readyHandler, this)); + + defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST", + std::bind(&SimpleAddTransaction::selectingFwdServerHandler, this)); + + defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST", + std::bind(&SimpleAddTransaction::selectingRevServerHandler, this)); + + defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST", + std::bind(&SimpleAddTransaction::replacingFwdAddrsHandler, this)); + + defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST", + std::bind(&SimpleAddTransaction::replacingRevPtrsHandler, this)); + + defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST", + std::bind(&SimpleAddTransaction::processAddOkHandler, this)); + + defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST", + std::bind(&SimpleAddTransaction::processAddFailedHandler, this)); +} + +void +SimpleAddTransaction::verifyStates() { + // Call superclass implementation first to verify its states. These are + // states common to all transactions, and they must be defined. + // READY_ST + // SELECTING_FWD_SERVER_ST + // SELECTING_REV_SERVER_ST + // PROCESS_TRANS_OK_ST + // PROCESS_TRANS_FAILED_ST + NameChangeTransaction::verifyStates(); + + // Verify SimpleAddTransaction states by attempting to fetch them. + getStateInternal(REPLACING_FWD_ADDRS_ST); + getStateInternal(REPLACING_REV_PTRS_ST); +} + +void +SimpleAddTransaction::readyHandler() { + switch(getNextEvent()) { + case START_EVT: + if (getForwardDomain()) { + // Request includes a forward change, do that first. + transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT); + } else { + // Reverse change only, transition accordingly. + transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); + } + + break; + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleAddTransaction::selectingFwdServerHandler() { + switch(getNextEvent()) { + case SELECT_SERVER_EVT: + // First time through for this transaction, so initialize server + // selection. + initServerSelection(getForwardDomain()); + break; + case SERVER_IO_ERROR_EVT: + // We failed to communicate with current server. Attempt to select + // another one below. + break; + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } + + // Select the next server from the list of forward servers. + if (selectNextServer()) { + // We have a server to try. + transition(REPLACING_FWD_ADDRS_ST, SERVER_SELECTED_EVT); + } + else { + // Server list is exhausted, so fail the transaction. + transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); + } +} + +void +SimpleAddTransaction::replacingFwdAddrsHandler() { + if (doOnEntry()) { + // Clear the request on initial transition. This allows us to reuse + // the request on retries if necessary. + clearDnsUpdateRequest(); + } + + switch(getNextEvent()) { + case SERVER_SELECTED_EVT: + if (!getDnsUpdateRequest()) { + // Request hasn't been constructed yet, so build it. + try { + buildReplaceFwdAddressRequest(); + } catch (const std::exception& ex) { + // While unlikely, the build might fail if we have invalid + // data. Should that be the case, we need to fail the + // transaction. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE) + .arg(getRequestId()) + .arg(getNcr()->toText()) + .arg(ex.what()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } + } + + // Call sendUpdate() to initiate the async send. Note it also sets + // next event to NOP_EVT. + sendUpdate("Forward Add"); + break; + + case IO_COMPLETED_EVT: { + switch (getDnsUpdateStatus()) { + case DNSClient::SUCCESS: { + // We successfully received a response packet from the server. + const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); + if (rcode == dns::Rcode::NOERROR()) { + // We were able to add it. Mark it as done. + setForwardChangeCompleted(true); + + // If request calls for reverse update then do that next, + // otherwise we can process ok. + if (getReverseDomain()) { + transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); + } else { + transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); + } + } else { + // Any other value means cease. Really shouldn't happen. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_REJECTED) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()) + .arg(rcode.getCode()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } + + break; + } + + case DNSClient::TIMEOUT: + case DNSClient::OTHER: + // We couldn't send to the current server, log it and set up + // to select the next server for a retry. + // @note For now we treat OTHER as an IO error like TIMEOUT. It + // is not entirely clear if this is accurate. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR) + .arg(getRequestId()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + retryTransition(SELECTING_FWD_SERVER_ST); + break; + + case DNSClient::INVALID_RESPONSE: + // A response was received but was corrupt. Retry it like an IO + // error. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()); + + retryTransition(SELECTING_FWD_SERVER_ST); + break; + + default: + // Any other value and we will fail this transaction, something + // bigger is wrong. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS) + .arg(getRequestId()) + .arg(getDnsUpdateStatus()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } // end switch on dns_status + + break; + } // end case IO_COMPLETE_EVT + + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleAddTransaction::selectingRevServerHandler() { + switch(getNextEvent()) { + case SELECT_SERVER_EVT: + // First time through for this transaction, so initialize server + // selection. + initServerSelection(getReverseDomain()); + break; + case SERVER_IO_ERROR_EVT: + // We failed to communicate with current server. Attempt to select + // another one below. + break; + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } + + // Select the next server from the list of forward servers. + if (selectNextServer()) { + // We have a server to try. + transition(REPLACING_REV_PTRS_ST, SERVER_SELECTED_EVT); + } + else { + // Server list is exhausted, so fail the transaction. + transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); + } +} + + +void +SimpleAddTransaction::replacingRevPtrsHandler() { + if (doOnEntry()) { + // Clear the request on initial transition. This allows us to reuse + // the request on retries if necessary. + clearDnsUpdateRequest(); + } + + switch(getNextEvent()) { + case SERVER_SELECTED_EVT: + if (!getDnsUpdateRequest()) { + // Request hasn't been constructed yet, so build it. + try { + buildReplaceRevPtrsRequest(); + } catch (const std::exception& ex) { + // While unlikely, the build might fail if we have invalid + // data. Should that be the case, we need to fail the + // transaction. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE) + .arg(getRequestId()) + .arg(getNcr()->toText()) + .arg(ex.what()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } + } + + // Call sendUpdate() to initiate the async send. Note it also sets + // next event to NOP_EVT. + sendUpdate("Reverse Replace"); + break; + + case IO_COMPLETED_EVT: { + switch (getDnsUpdateStatus()) { + case DNSClient::SUCCESS: { + // We successfully received a response packet from the server. + const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); + if (rcode == dns::Rcode::NOERROR()) { + // We were able to update the reverse mapping. Mark it as done. + setReverseChangeCompleted(true); + transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); + } else { + // Per RFC4703 any other value means cease. + // If we get not authorized should try the next server in + // the list? @todo This needs some discussion perhaps. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()) + .arg(rcode.getCode()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } + + break; + } + + case DNSClient::TIMEOUT: + case DNSClient::OTHER: + // We couldn't send to the current server, log it and set up + // to select the next server for a retry. + // @note For now we treat OTHER as an IO error like TIMEOUT. It + // is not entirely clear if this is accurate. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR) + .arg(getRequestId()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + // If we are out of retries on this server, we go back and start + // all over on a new server. + retryTransition(SELECTING_REV_SERVER_ST); + break; + + case DNSClient::INVALID_RESPONSE: + // A response was received but was corrupt. Retry it like an IO + // error. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()); + + // If we are out of retries on this server, we go back and start + // all over on a new server. + retryTransition(SELECTING_REV_SERVER_ST); + break; + + default: + // Any other value and we will fail this transaction, something + // bigger is wrong. + LOG_ERROR(d2_to_dns_logger, + DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS) + .arg(getRequestId()) + .arg(getDnsUpdateStatus()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } // end switch on dns_status + + break; + } // end case IO_COMPLETE_EVT + + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleAddTransaction::processAddOkHandler() { + switch(getNextEvent()) { + case UPDATE_OK_EVT: + LOG_INFO(d2_to_dns_logger, DHCP_DDNS_ADD_SUCCEEDED) + .arg(getRequestId()) + .arg(getNcr()->toText()); + setNcrStatus(dhcp_ddns::ST_COMPLETED); + endModel(); + break; + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleAddTransaction::processAddFailedHandler() { + switch(getNextEvent()) { + case UPDATE_FAILED_EVT: + case NO_MORE_SERVERS_EVT: + setNcrStatus(dhcp_ddns::ST_FAILED); + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_ADD_FAILED) + .arg(getRequestId()) + .arg(transactionOutcomeString()); + endModel(); + break; + default: + // Event is invalid. + isc_throw(SimpleAddTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleAddTransaction::buildReplaceFwdAddressRequest() { + // Construct an empty request. + D2UpdateMessagePtr request = prepNewRequest(getForwardDomain()); + + // Construct dns::Name from NCR fqdn. + dns::Name fqdn(dns::Name(getNcr()->getFqdn())); + + // There are no prerequisites. + + // Build the Update Section. First we delete any pre-existing + // FQDN/IP and DHCID RRs. Then we add new ones. + + // Create the FQDN/IP 'delete' RR and add it to update section. + dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(), + getAddressRRType(), dns::RRTTL(0))); + + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Create the DHCID 'delete' RR and add it to the update section. + update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(), + dns::RRType::DHCID(), dns::RRTTL(0))); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Now make the new RRs. + // Create the TTL based on lease length. + dns::RRTTL lease_ttl(getNcr()->getLeaseLength()); + + // Create the FQDN/IP 'add' RR and add it to the to update section. + // Based on RFC 2136, section 2.5.1 + update.reset(new dns::RRset(fqdn, dns::RRClass::IN(), + getAddressRRType(), lease_ttl)); + + addLeaseAddressRdata(update); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Now create the FQDN/DHCID 'add' RR and add it to update section. + // Based on RFC 2136, section 2.5.1 + update.reset(new dns::RRset(fqdn, dns::RRClass::IN(), + dns::RRType::DHCID(), lease_ttl)); + + // We add the DHCID for auditing purposes and in the event + // conflict resoloution is later enabled. + addDhcidRdata(update); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Set the transaction's update request to the new request. + setDnsUpdateRequest(request); +} + +void +SimpleAddTransaction::buildReplaceRevPtrsRequest() { + // Construct an empty request. + D2UpdateMessagePtr request = prepNewRequest(getReverseDomain()); + + // Create the reverse IP address "FQDN". + std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress()); + dns::Name rev_ip(rev_addr); + + // Create the TTL based on lease length. + dns::RRTTL lease_ttl(getNcr()->getLeaseLength()); + + // There are no prerequisites. + + // Create the FQDN/IP PTR 'delete' RR for this IP and add it to + // the update section. + dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(), + dns::RRType::PTR(), dns::RRTTL(0))); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Create the DHCID 'delete' RR and add it to the update section. + update.reset(new dns::RRset(rev_ip, dns::RRClass::ANY(), + dns::RRType::DHCID(), dns::RRTTL(0))); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata + // then add it to update section. + update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(), + dns::RRType::PTR(), lease_ttl)); + addPtrRdata(update); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata + // then add it to update section. + update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(), + dns::RRType::DHCID(), lease_ttl)); + addDhcidRdata(update); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Set the transaction's update request to the new request. + setDnsUpdateRequest(request); +} + +} // namespace isc::d2 +} // namespace isc diff --git a/src/bin/d2/simple_add.h b/src/bin/d2/simple_add.h new file mode 100644 index 0000000000..25948addb8 --- /dev/null +++ b/src/bin/d2/simple_add.h @@ -0,0 +1,358 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SIMPLE_ADD_H +#define SIMPLE_ADD_H + +/// @file nc_add.h This file defines the class SimpleAddTransaction. + +#include +#include + +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 SimpleAddTransactionPtr; + +} // namespace isc::d2 +} // namespace isc +#endif diff --git a/src/bin/d2/simple_remove.cc b/src/bin/d2/simple_remove.cc new file mode 100644 index 0000000000..d914637c38 --- /dev/null +++ b/src/bin/d2/simple_remove.cc @@ -0,0 +1,530 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include + +#include + +namespace isc { +namespace d2 { + + +// SimpleRemoveTransaction states +const int SimpleRemoveTransaction::REMOVING_FWD_RRS_ST; +const int SimpleRemoveTransaction::REMOVING_REV_PTRS_ST; + +// SimpleRemoveTransaction events +// Currently SimpleRemoveTransaction does not define any events. + +SimpleRemoveTransaction:: +SimpleRemoveTransaction(asiolink::IOServicePtr& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr& forward_domain, + DdnsDomainPtr& reverse_domain, + D2CfgMgrPtr& cfg_mgr) + : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain, + cfg_mgr) { + if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) { + isc_throw (SimpleRemoveTransactionError, + "SimpleRemoveTransaction, request type must be CHG_REMOVE"); + } +} + +SimpleRemoveTransaction::~SimpleRemoveTransaction(){ +} + +void +SimpleRemoveTransaction::defineEvents() { + // Call superclass impl first. + NameChangeTransaction::defineEvents(); + + // Define SimpleRemoveTransaction events. + // Currently SimpleRemoveTransaction does not define any events. + // defineEvent(TBD_EVENT, "TBD_EVT"); +} + +void +SimpleRemoveTransaction::verifyEvents() { + // Call superclass implementation first to verify its events. These are + // events common to all transactions, and they must be defined. + // SELECT_SERVER_EVT + // SERVER_SELECTED_EVT + // SERVER_IO_ERROR_EVT + // NO_MORE_SERVERS_EVT + // IO_COMPLETED_EVT + // UPDATE_OK_EVT + // UPDATE_FAILED_EVT + NameChangeTransaction::verifyEvents(); + + // Verify SimpleRemoveTransaction events by attempting to fetch them. + // Currently SimpleRemoveTransaction does not define any events. + // getEvent(TBD_EVENT); +} + +void +SimpleRemoveTransaction::defineStates() { + // Call superclass impl first. + NameChangeTransaction::defineStates(); + + // Define SimpleRemoveTransaction states. + defineState(READY_ST, "READY_ST", + std::bind(&SimpleRemoveTransaction::readyHandler, this)); + + defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST", + std::bind(&SimpleRemoveTransaction::selectingFwdServerHandler, + this)); + + defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST", + std::bind(&SimpleRemoveTransaction::selectingRevServerHandler, + this)); + + defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST", + std::bind(&SimpleRemoveTransaction::removingFwdRRsHandler, + this)); + + defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST", + std::bind(&SimpleRemoveTransaction::removingRevPtrsHandler, + this)); + + defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST", + std::bind(&SimpleRemoveTransaction::processRemoveOkHandler, + this)); + + defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST", + std::bind(&SimpleRemoveTransaction::processRemoveFailedHandler, + this)); +} + +void +SimpleRemoveTransaction::verifyStates() { + // Call superclass implementation first to verify its states. These are + // states common to all transactions, and they must be defined. + // READY_ST + // SELECTING_FWD_SERVER_ST + // SELECTING_REV_SERVER_ST + // PROCESS_TRANS_OK_ST + // PROCESS_TRANS_FAILED_ST + NameChangeTransaction::verifyStates(); + + // Verify SimpleRemoveTransaction states by attempting to fetch them. + getStateInternal(REMOVING_FWD_RRS_ST); + getStateInternal(REMOVING_REV_PTRS_ST); +} + +void +SimpleRemoveTransaction::readyHandler() { + switch(getNextEvent()) { + case START_EVT: + if (getForwardDomain()) { + // Request includes a forward change, do that first. + transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT); + } else { + // Reverse change only, transition accordingly. + transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); + } + + break; + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleRemoveTransaction::selectingFwdServerHandler() { + switch(getNextEvent()) { + case SELECT_SERVER_EVT: + // First time through for this transaction, so initialize server + // selection. + initServerSelection(getForwardDomain()); + break; + case SERVER_IO_ERROR_EVT: + // We failed to communicate with current server. Attempt to select + // another one below. + break; + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } + + // Select the next server from the list of forward servers. + if (selectNextServer()) { + // We have a server to try. + transition(REMOVING_FWD_RRS_ST, SERVER_SELECTED_EVT); + } + else { + // Server list is exhausted, so fail the transaction. + transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); + } +} + +void +SimpleRemoveTransaction::removingFwdRRsHandler() { + if (doOnEntry()) { + // Clear the request on initial transition. This allows us to reuse + // the request on retries if necessary. + clearDnsUpdateRequest(); + } + + switch(getNextEvent()) { + case UPDATE_OK_EVT: + case SERVER_SELECTED_EVT: + if (!getDnsUpdateRequest()) { + // Request hasn't been constructed yet, so build it. + try { + buildRemoveFwdRRsRequest(); + } catch (const std::exception& ex) { + // While unlikely, the build might fail if we have invalid + // data. Should that be the case, we need to fail the + // transaction. + LOG_ERROR(d2_to_dns_logger, + DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE) + .arg(getRequestId()) + .arg(getNcr()->toText()) + .arg(ex.what()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } + } + + // Call sendUpdate() to initiate the async send. Note it also sets + // next event to NOP_EVT. + sendUpdate("Forward RR Remove"); + break; + + case IO_COMPLETED_EVT: { + switch (getDnsUpdateStatus()) { + case DNSClient::SUCCESS: { + // We successfully received a response packet from the server. + // The RCODE will be based on a value-dependent RRset search, + // see RFC 2136 section 3.2.3/3.2.4. + const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); + if ((rcode == dns::Rcode::NOERROR()) || + (rcode == dns::Rcode::NXRRSET())) { + // We were able to remove them or they were not there ( + // Rcode of NXRRSET means there are no matching RRsets). + // In either case, we consider it success and mark it as done. + setForwardChangeCompleted(true); + + // If request calls for reverse update then do that next, + // otherwise we can process ok. + if (getReverseDomain()) { + transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); + } else { + transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); + } + } else { + // Any other value means cease. + // If we get not authorized should try the next server in + // the list? @todo This needs some discussion perhaps. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()) + .arg(rcode.getCode()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } + + break; + } + + case DNSClient::TIMEOUT: + case DNSClient::OTHER: + // We couldn't send to the current server, log it and set up + // to select the next server for a retry. + // @note For now we treat OTHER as an IO error like TIMEOUT. It + // is not entirely clear if this is accurate. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR) + .arg(getRequestId()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + retryTransition(SELECTING_FWD_SERVER_ST); + break; + + case DNSClient::INVALID_RESPONSE: + // A response was received but was corrupt. Retry it like an IO + // error. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()); + + retryTransition(SELECTING_FWD_SERVER_ST); + break; + + default: + // Any other value and we will fail this transaction, something + // bigger is wrong. + LOG_ERROR(d2_to_dns_logger, + DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS) + .arg(getRequestId()) + .arg(getDnsUpdateStatus()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } // end switch on dns_status + + break; + } // end case IO_COMPLETE_EVT + + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + + +void +SimpleRemoveTransaction::selectingRevServerHandler() { + switch(getNextEvent()) { + case SELECT_SERVER_EVT: + // First time through for this transaction, so initialize server + // selection. + initServerSelection(getReverseDomain()); + break; + case SERVER_IO_ERROR_EVT: + // We failed to communicate with current server. Attempt to select + // another one below. + break; + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } + + // Select the next server from the list of forward servers. + if (selectNextServer()) { + // We have a server to try. + transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT); + } + else { + // Server list is exhausted, so fail the transaction. + transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); + } +} + + +void +SimpleRemoveTransaction::removingRevPtrsHandler() { + if (doOnEntry()) { + // Clear the request on initial transition. This allows us to reuse + // the request on retries if necessary. + clearDnsUpdateRequest(); + } + + switch(getNextEvent()) { + case SERVER_SELECTED_EVT: + if (!getDnsUpdateRequest()) { + // Request hasn't been constructed yet, so build it. + try { + buildRemoveRevPtrsRequest(); + } catch (const std::exception& ex) { + // While unlikely, the build might fail if we have invalid + // data. Should that be the case, we need to fail the + // transaction. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE) + .arg(getRequestId()) + .arg(getNcr()->toText()) + .arg(ex.what()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } + } + + // Call sendUpdate() to initiate the async send. Note it also sets + // next event to NOP_EVT. + sendUpdate("Reverse Remove"); + break; + + case IO_COMPLETED_EVT: { + switch (getDnsUpdateStatus()) { + case DNSClient::SUCCESS: { + // We successfully received a response packet from the server. + // The RCODE will be based on a value-dependent RRset search, + // see RFC 2136 section 3.2.3/3.2.4. + const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); + if ((rcode == dns::Rcode::NOERROR()) || + (rcode == dns::Rcode::NXRRSET())) { + // We were able to remove the reverse mapping or they were + // not there (Rcode of NXRRSET means there are no matching + // RRsets). In either case, mark it as done. + setReverseChangeCompleted(true); + transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); + } else { + // Per RFC4703 any other value means cease. + // If we get not authorized should try the next server in + // the list? @todo This needs some discussion perhaps. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()) + .arg(rcode.getCode()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } + + break; + } + + case DNSClient::TIMEOUT: + case DNSClient::OTHER: + // We couldn't send to the current server, log it and set up + // to select the next server for a retry. + // @note For now we treat OTHER as an IO error like TIMEOUT. It + // is not entirely clear if this is accurate. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR) + .arg(getRequestId()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + // If we are out of retries on this server, we go back and start + // all over on a new server. + retryTransition(SELECTING_REV_SERVER_ST); + break; + + case DNSClient::INVALID_RESPONSE: + // A response was received but was corrupt. Retry it like an IO + // error. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT) + .arg(getRequestId()) + .arg(getCurrentServer()->toText()) + .arg(getNcr()->getFqdn()); + + // If we are out of retries on this server, we go back and start + // all over on a new server. + retryTransition(SELECTING_REV_SERVER_ST); + break; + + default: + // Any other value and we will fail this transaction, something + // bigger is wrong. + LOG_ERROR(d2_to_dns_logger, + DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS) + .arg(getRequestId()) + .arg(getDnsUpdateStatus()) + .arg(getNcr()->getFqdn()) + .arg(getCurrentServer()->toText()); + + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + break; + } // end switch on dns_status + + break; + } // end case IO_COMPLETE_EVT + + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + + +void +SimpleRemoveTransaction::processRemoveOkHandler() { + switch(getNextEvent()) { + case UPDATE_OK_EVT: + LOG_INFO(d2_to_dns_logger, DHCP_DDNS_REMOVE_SUCCEEDED) + .arg(getRequestId()) + .arg(getNcr()->toText()); + setNcrStatus(dhcp_ddns::ST_COMPLETED); + endModel(); + break; + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleRemoveTransaction::processRemoveFailedHandler() { + switch(getNextEvent()) { + case UPDATE_FAILED_EVT: + case NO_MORE_SERVERS_EVT: + case SERVER_IO_ERROR_EVT: + setNcrStatus(dhcp_ddns::ST_FAILED); + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REMOVE_FAILED) + .arg(getRequestId()) + .arg(transactionOutcomeString()); + endModel(); + break; + default: + // Event is invalid. + isc_throw(SimpleRemoveTransactionError, + "Wrong event for context: " << getContextStr()); + } +} + +void +SimpleRemoveTransaction::buildRemoveFwdRRsRequest() { + // Construct an empty request. + D2UpdateMessagePtr request = prepNewRequest(getForwardDomain()); + + // There are no pre-requisites. + + // Build the Update Section + // Construct dns::Name from NCR fqdn. + dns::Name fqdn(dns::Name(getNcr()->getFqdn())); + + // Build the Update Section. + + // Create the FQDN/IP 'delete' RR and add it to update section. + dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(), + getAddressRRType(), dns::RRTTL(0))); + + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Create the DHCID 'delete' RR and add it to the update section. + update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(), + dns::RRType::DHCID(), dns::RRTTL(0))); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Set the transaction's update request to the new request. + setDnsUpdateRequest(request); +} + +void +SimpleRemoveTransaction::buildRemoveRevPtrsRequest() { + // Construct an empty request. + D2UpdateMessagePtr request = prepNewRequest(getReverseDomain()); + + // Create the reverse IP address "FQDN". + std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress()); + dns::Name rev_ip(rev_addr); + + /// TKM @todo - not sure we need this pre-req + // Create an assertion that the PTRDNAME in the PTR record matches the + // client's FQDN for the address that was released. + // Based on RFC 2136, section 3.2.3 + dns::RRsetPtr prereq(new dns::RRset(rev_ip, dns::RRClass::IN(), + dns::RRType::PTR(), dns::RRTTL(0))); + addPtrRdata(prereq); + request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); + + // Now, build the Update section. + + // Create a delete of any RRs for the FQDN and add it to update section. + // Based on RFC 2136, section 3.4.2.3 + dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(), + dns::RRType::ANY(), dns::RRTTL(0))); + request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); + + // Set the transaction's update request to the new request. + setDnsUpdateRequest(request); +} + +} // namespace isc::d2 +} // namespace isc diff --git a/src/bin/d2/simple_remove.h b/src/bin/d2/simple_remove.h new file mode 100644 index 0000000000..ff432541d1 --- /dev/null +++ b/src/bin/d2/simple_remove.h @@ -0,0 +1,357 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SIMPLE_REMOVE_H +#define SIMPLE_REMOVE_H + +/// @file nc_remove.h This file defines the class SimpleRemoveTransaction. + +#include + +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 SimpleRemoveTransactionPtr; + + +} // namespace isc::d2 +} // namespace isc +#endif diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am index 422d6401c1..38b764399f 100644 --- a/src/bin/d2/tests/Makefile.am +++ b/src/bin/d2/tests/Makefile.am @@ -60,6 +60,8 @@ d2_unittests_SOURCES += d2_simple_parser_unittest.cc d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h d2_unittests_SOURCES += get_config_unittest.cc d2_unittests_SOURCES += d2_command_unittest.cc +d2_unittests_SOURCES += simple_add_unittests.cc +d2_unittests_SOURCES += simple_remove_unittests.cc d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc index ff7d067d8b..73936e5bb0 100644 --- a/src/bin/d2/tests/d2_process_unittests.cc +++ b/src/bin/d2/tests/d2_process_unittests.cc @@ -284,7 +284,8 @@ TEST_F(D2ProcessTest, queueFullRecovery) { " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; // Start queue manager with known good config. @@ -511,7 +512,8 @@ TEST_F(D2ProcessTest, canShutdown) { " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; // Manually enqueue a request. This lets us test logic with queue diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc index 5d1cd4ba0c..68e9916424 100644 --- a/src/bin/d2/tests/d2_queue_mgr_unittests.cc +++ b/src/bin/d2/tests/d2_queue_mgr_unittests.cc @@ -37,7 +37,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}", // Valid Remove. "{" @@ -48,7 +49,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}", // Valid Add with IPv6 address "{" @@ -59,7 +61,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}" }; diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc index 0fd39bd175..9ddee7534e 100644 --- a/src/bin/d2/tests/d2_update_mgr_unittests.cc +++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc @@ -9,6 +9,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -95,7 +100,8 @@ public: " \"ip-address\" : \"192.168.1.2\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; const char* dhcids[] = { "111111", "222222", "333333", "444444"}; @@ -674,6 +680,9 @@ TEST_F(D2UpdateMgrTest, addTransaction) { ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); NameChangeTransactionPtr trans = (*pos).second; ASSERT_TRUE(trans); + + // Verify the correct type of transcation was created. + ASSERT_NO_THROW(dynamic_cast(*trans)); // At this point the transaction should have constructed // and sent the DNS request. @@ -728,6 +737,9 @@ TEST_F(D2UpdateMgrTest, removeTransaction) { NameChangeTransactionPtr trans = (*pos).second; ASSERT_TRUE(trans); + // Verify the correct type of transcation was created. + ASSERT_NO_THROW(dynamic_cast(*trans)); + // At this point the transaction should have constructed // and sent the DNS request. ASSERT_TRUE(trans->getCurrentServer()); @@ -858,4 +870,118 @@ TEST_F(D2UpdateMgrTest, multiTransactionTimeout) { } } +/// @brief Tests integration of SimpleAddTransaction +/// This test verifies that update manager can create and manage a +/// SimpleAddTransaction from start to finish. It utilizes a fake server +/// which responds to all requests sent with NOERROR, simulating a +/// successful addition. The transaction processes both forward and +/// reverse changes. +TEST_F(D2UpdateMgrTest, simpleAddTransaction) { + // Put each transaction on the queue. + canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD); + canned_ncrs_[0]->setReverseChange(true); + canned_ncrs_[0]->setConflictResolution(false); + ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0])); + + // Call sweep once, this should: + // 1. Dequeue the request + // 2. Create the transaction + // 3. Start the transaction + ASSERT_NO_THROW(update_mgr_->sweep()); + + // Get a copy of the transaction. + TransactionList::iterator pos = update_mgr_->transactionListBegin(); + ASSERT_TRUE (pos != update_mgr_->transactionListEnd()); + NameChangeTransactionPtr trans = (*pos).second; + ASSERT_TRUE(trans); + + // Verify the correct type of transcation was created. + ASSERT_NO_THROW(dynamic_cast(*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(*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()); +} + } diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc index 9f54a29c07..67d11d1903 100644 --- a/src/bin/d2/tests/nc_add_unittests.cc +++ b/src/bin/d2/tests/nc_add_unittests.cc @@ -272,7 +272,8 @@ TEST(NameAddTransaction, construction) { " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; dhcp_ddns::NameChangeRequestPtr ncr; diff --git a/src/bin/d2/tests/nc_remove_unittests.cc b/src/bin/d2/tests/nc_remove_unittests.cc index 123a7e1190..f5ced0a61b 100644 --- a/src/bin/d2/tests/nc_remove_unittests.cc +++ b/src/bin/d2/tests/nc_remove_unittests.cc @@ -275,7 +275,8 @@ TEST(NameRemoveTransaction, construction) { " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; dhcp_ddns::NameChangeRequestPtr ncr; diff --git a/src/bin/d2/tests/nc_test_utils.cc b/src/bin/d2/tests/nc_test_utils.cc index ccc2482359..01014c5655 100644 --- a/src/bin/d2/tests/nc_test_utils.cc +++ b/src/bin/d2/tests/nc_test_utils.cc @@ -266,7 +266,8 @@ TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type, " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; // Create NameChangeRequest from JSON string. @@ -324,7 +325,8 @@ TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type, " \"ip-address\" : \"2001:1::100\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; // Create NameChangeRequest from JSON string. @@ -813,5 +815,117 @@ std::string toHexText(const uint8_t* data, size_t len) { return (stream.str()); } +// Verifies that the contents of the given transaction's DNS update request +// is correct for replacing a forward DNS entry when not using conflict +// resolution. +void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran) { + const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest(); + ASSERT_TRUE(request); + + // Safety check. + dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr(); + ASSERT_TRUE(ncr); + + std::string exp_zone_name = tran.getForwardDomain()->getName(); + std::string exp_fqdn = ncr->getFqdn(); + const dns::RRType& exp_ip_rr_type = tran.getAddressRRType(); + + // Verify the zone section. + checkZone(request, exp_zone_name); + + // Verify the PREREQUISITE SECTION + // There should be no prerequisites. + dns::RRsetPtr rrset; + checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0); + + // Verify the UPDATE SECTION + // Should be 4 + // 1. delete of the FQDN/IP RR + // 2. delete of the DHCID RR + // 3. add of the FQDN/IP RR + // 4. add of the DHCID RR + checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4); + + // Fetch ttl. + uint32_t ttl = ncr->getLeaseLength(); + + // Verify the FQDN delete RR. + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 0)); + checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr); + + // Verify the DHCID delete RR. + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 1)); + checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(), + 0, ncr); + + // Verify the FQDN/IP add RR. + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 2)); + checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr); + + // Now, verify the DHCID add RR. + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 3)); + checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), + ttl, ncr); + + // Verify there are no RRs in the ADDITIONAL Section. + checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0); + + // Verify that it will render toWire without throwing. + dns::MessageRenderer renderer; + ASSERT_NO_THROW(request->toWire(renderer)); +} + +void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran) { + const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest(); + ASSERT_TRUE(request); + + // Safety check. + dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr(); + ASSERT_TRUE(ncr); + + std::string exp_zone_name = tran.getForwardDomain()->getName(); + std::string exp_fqdn = ncr->getFqdn(); + const dns::RRType& exp_ip_rr_type = tran.getAddressRRType(); + + // Verify the zone section. + checkZone(request, exp_zone_name); + + // Verify there no prerequisites. + checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0); + + // Verify there are 2 RRs in the UPDATE Section. + checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2); + + // Verify the FQDN delete RR. + dns::RRsetPtr rrset; + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 0)); + checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr); + + // Verify the DHCID delete RR. + ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage:: + SECTION_UPDATE, 1)); + checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(), + 0, ncr); + + // Verify that it will render toWire without throwing. + dns::MessageRenderer renderer; + ASSERT_NO_THROW(request->toWire(renderer)); +} + +void checkContext(NameChangeTransactionPtr trans, const int exp_state, + const int exp_evt, const std::string& file, int line) { + ASSERT_TRUE(trans); + ASSERT_TRUE(exp_state == trans->getCurrState() && exp_evt == trans->getNextEvent()) + << "expected state: " << trans->getStateLabel(exp_state) + << " event: " << trans->getEventLabel(exp_evt) + << ", actual context: " << trans->getContextStr() + << " at " << file << ":" << line; +} + }; // namespace isc::d2 }; // namespace isc diff --git a/src/bin/d2/tests/nc_test_utils.h b/src/bin/d2/tests/nc_test_utils.h index 9bdb0184b8..fa37bde2ed 100644 --- a/src/bin/d2/tests/nc_test_utils.h +++ b/src/bin/d2/tests/nc_test_utils.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -372,6 +372,21 @@ extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran); /// @param tran Transaction containing the request to be verified. extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran); +/// @brief Verifies a simple forward mapping replacement DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// replacing a forward DNS mapping when not using conflict resolution. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran); + +/// @brief Verifies a simple forward RR removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing forward RR DNS entries when not using conflict resolution. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran); /// @brief Creates a NameChangeRequest from JSON string. /// @@ -444,6 +459,19 @@ extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name, /// @param len size (in bytes) of data extern std::string toHexText(const uint8_t* data, size_t len); +/// @brief Verifies the current state and next event in a transaction +/// @param trans NameChangeTransaction to check +/// @param exp_state expected current state of the transaction +/// @param exp_event expected next event of the transaction +/// @param file source file name +/// @param line souce line number +extern void checkContext(NameChangeTransactionPtr trans, const int exp_state, + const int exp_evt, const std::string& file, int line); + +/// @brief Macro for calling checkContext() that supplies invocation location +#define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__) + + }; // namespace isc::d2 }; // namespace isc diff --git a/src/bin/d2/tests/nc_trans_unittests.cc b/src/bin/d2/tests/nc_trans_unittests.cc index 03a89e7bc3..4744477230 100644 --- a/src/bin/d2/tests/nc_trans_unittests.cc +++ b/src/bin/d2/tests/nc_trans_unittests.cc @@ -369,7 +369,8 @@ TEST(NameChangeTransaction, construction) { " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"0102030405060708\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; dhcp_ddns::NameChangeRequestPtr ncr; diff --git a/src/bin/d2/tests/simple_add_unittests.cc b/src/bin/d2/tests/simple_add_unittests.cc new file mode 100644 index 0000000000..a9cda70d8b --- /dev/null +++ b/src/bin/d2/tests/simple_add_unittests.cc @@ -0,0 +1,1216 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include +#include + +#include + +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 SimpleAddStubPtr; + +/// @brief Test fixture for testing SimpleAddTransaction +/// +/// Note this class uses SimpleAddStub class to exercise non-public +/// aspects of SimpleAddTransaction. +class SimpleAddTransactionTest : public TransactionTest { +public: + + SimpleAddTransactionTest() { + } + + virtual ~SimpleAddTransactionTest() { + } + + /// @brief Creates a transaction which requests an IPv4 DNS update. + /// + /// The transaction is constructed around a predefined (i.e. "canned") + /// IPv4 NameChangeRequest. The request has both forward and reverse DNS + /// changes requested. Based upon the change mask, the transaction + /// will have either the forward, reverse, or both domains populated. + /// + /// @param change_mask determines which change directions are requested + SimpleAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) { + // Creates IPv4 remove request, forward, and reverse domains. + setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask); + + // Now create the test transaction as would occur in update manager. + return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_, + forward_domain_, + reverse_domain_, cfg_mgr_))); + } + + /// @brief Creates a transaction which requests an IPv6 DNS update. + /// + /// The transaction is constructed around a predefined (i.e. "canned") + /// IPv6 NameChangeRequest. The request has both forward and reverse DNS + /// changes requested. Based upon the change mask, the transaction + /// will have either the forward, reverse, or both domains populated. + /// + /// @param change_mask determines which change directions are requested + SimpleAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) { + // Creates IPv6 remove request, forward, and reverse domains. + setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask); + + // Now create the test transaction as would occur in update manager. + return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_, + forward_domain_, + reverse_domain_, + cfg_mgr_))); + } + + /// @brief Create a test transaction at a known point in the state model. + /// + /// Method prepares a new test transaction and sets its state and next + /// event values to those given. This makes the transaction appear to + /// be at that point in the state model without having to transition it + /// through prerequisite states. It also provides the ability to set + /// which change directions are requested: forward change only, reverse + /// change only, or both. + /// + /// @param state value to set as the current state + /// @param event value to post as the next event + /// @param change_mask determines which change directions are requested + /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6) + /// transaction. + SimpleAddStubPtr prepHandlerTest(unsigned int state, unsigned int event, + unsigned int change_mask = FWD_AND_REV_CHG, + short family = AF_INET) { + SimpleAddStubPtr name_add = (family == AF_INET ? + makeTransaction4(change_mask) : + makeTransaction6(change_mask)); + name_add->initDictionaries(); + name_add->postNextEvent(event); + name_add->setState(state); + return (name_add); + } +}; + +/// @brief Tests SimpleAddTransaction construction. +/// This test verifies that: +/// 1. Construction with invalid type of request +/// 2. Valid construction functions properly +TEST(SimpleAddTransaction, construction) { + asiolink::IOServicePtr io_service(new isc::asiolink::IOService()); + D2CfgMgrPtr cfg_mgr(new D2CfgMgr()); + + const char* msg_str = + "{" + " \"change-type\" : 1 , " + " \"forward-change\" : true , " + " \"reverse-change\" : true , " + " \"fqdn\" : \"example.com.\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"0102030405060708\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " + "}"; + + dhcp_ddns::NameChangeRequestPtr ncr; + DnsServerInfoStoragePtr servers; + DdnsDomainPtr forward_domain; + DdnsDomainPtr reverse_domain; + DdnsDomainPtr empty_domain; + + ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str)); + ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers))); + ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers))); + + // Verify that construction with wrong change type fails. + EXPECT_THROW(SimpleAddTransaction(io_service, ncr, + forward_domain, reverse_domain, cfg_mgr), + SimpleAddTransactionError); + + // Verify that a valid construction attempt works. + ncr->setChangeType(isc::dhcp_ddns::CHG_ADD); + EXPECT_NO_THROW(SimpleAddTransaction(io_service, ncr, + forward_domain, reverse_domain, + cfg_mgr)); +} + +/// @brief Tests event and state dictionary construction and verification. +TEST_F(SimpleAddTransactionTest, dictionaryCheck) { + SimpleAddStubPtr name_add; + ASSERT_NO_THROW(name_add = makeTransaction4()); + // Verify that the event and state dictionary validation fails prior + // dictionary construction. + ASSERT_THROW(name_add->verifyEvents(), StateModelError); + ASSERT_THROW(name_add->verifyStates(), StateModelError); + + // Construct both dictionaries. + ASSERT_NO_THROW(name_add->defineEvents()); + ASSERT_NO_THROW(name_add->defineStates()); + + // Verify both event and state dictionaries now pass validation. + ASSERT_NO_THROW(name_add->verifyEvents()); + ASSERT_NO_THROW(name_add->verifyStates()); +} + +/// @brief Tests construction of a DNS update request for replacing a forward +/// dns entry. +TEST_F(SimpleAddTransactionTest, buildReplaceFwdAddressRequest) { + // Create a IPv4 forward replace transaction. + // Verify the request builds without error. + // and then verify the request contents. + SimpleAddStubPtr name_add; + ASSERT_NO_THROW(name_add = makeTransaction4()); + ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest()); + checkSimpleReplaceFwdAddressRequest(*name_add); + + // Create a IPv6 forward replace transaction. + // Verify the request builds without error. + // and then verify the request contents. + ASSERT_NO_THROW(name_add = makeTransaction6()); + ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest()); + checkSimpleReplaceFwdAddressRequest(*name_add); +} + +/// @brief Tests the construction of a DNS update request for replacing a +/// reverse dns entry. +TEST_F(SimpleAddTransactionTest, buildReplaceRevPtrsRequest) { + // Create a IPv4 reverse replace transaction. + // Verify the request builds without error. + // and then verify the request contents. + SimpleAddStubPtr name_add; + ASSERT_NO_THROW(name_add = makeTransaction4()); + ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest()); + checkReplaceRevPtrsRequest(*name_add); + + // Create a IPv6 reverse replace transaction. + // Verify the request builds without error. + // and then verify the request contents. + ASSERT_NO_THROW(name_add = makeTransaction6()); + ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest()); + checkReplaceRevPtrsRequest(*name_add); +} + +// Tests the readyHandler functionality. +// It verifies behavior for the following scenarios: +// +// 1. Posted event is START_EVT and request includes only a forward change +// 2. Posted event is START_EVT and request includes both a forward and a +// reverse change +// 3. Posted event is START_EVT and request includes only a reverse change +// 4. Posted event is invalid +// +TEST_F(SimpleAddTransactionTest, readyHandler) { + SimpleAddStubPtr name_add; + + // Create a transaction which includes only a forward change. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::READY_ST, + StateModel::START_EVT, FORWARD_CHG) + ); + + // Run readyHandler. + EXPECT_NO_THROW(name_add->readyHandler()); + + // Verify that a request requiring only a forward change, transitions to + // selecting a forward server. + CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST, + NameChangeTransaction::SELECT_SERVER_EVT); + + // Create a transaction which includes both a forward and a reverse change. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::READY_ST, + StateModel::START_EVT, FWD_AND_REV_CHG) + ); + + // Run readyHandler. + EXPECT_NO_THROW(name_add->readyHandler()); + + // Verify that a request requiring both forward and reverse, starts with + // the forward change by transitioning to selecting a forward server. + CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST, + NameChangeTransaction::SELECT_SERVER_EVT); + + // Create and prep a reverse only transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::READY_ST, + StateModel::START_EVT, REVERSE_CHG) + ); + + // Run readyHandler. + EXPECT_NO_THROW(name_add->readyHandler()); + + // Verify that a request requiring only a reverse change, transitions to + // selecting a reverse server. + CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_REV_SERVER_ST, + NameChangeTransaction::SELECT_SERVER_EVT); + + // Create and prep transaction, poised to run the handler but with an + // invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::READY_ST, StateModel::NOP_EVT) + ); + + // Running the readyHandler should throw. + EXPECT_THROW(name_add->readyHandler(), SimpleAddTransactionError); +} + +// Tests the selectingFwdServerHandler functionality. +// It verifies behavior for the following scenarios: +// +// 1. Posted event is SELECT_SERVER_EVT +// 2. Posted event is SERVER_IO_ERROR_EVT +// 3. Posted event is invalid +// +TEST_F(SimpleAddTransactionTest, selectingFwdServerHandler) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST, + NameChangeTransaction::SELECT_SERVER_EVT) + ); + + // Call selectingFwdServerHandler enough times to select all of the + // servers in it's current domain. The first time, it will be with + // next event of SELECT_SERVER_EVT. Thereafter it will be with a next + // event of SERVER_IO_ERROR_EVT. + int num_servers = name_add->getForwardDomain()->getServers()->size(); + for (int i = 0; i < num_servers; ++i) { + SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers + << " selections: " << i); + // Run selectingFwdServerHandler. + ASSERT_NO_THROW(name_add->selectingFwdServerHandler()); + + // Verify that a server was selected. + ASSERT_TRUE(name_add->getCurrentServer()); + + // Verify that we transitioned correctly. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + + // Post a server IO error event. This simulates an IO error occurring + // and a need to select the new server. + ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction:: + SERVER_IO_ERROR_EVT)); + } + + // We should have exhausted the list of servers. Processing another + // SERVER_IO_ERROR_EVT should transition us to failure. + EXPECT_NO_THROW(name_add->selectingFwdServerHandler()); + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::NO_MORE_SERVERS_EVT); + + // Create and prep transaction, poised to run the handler but with an + // invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST, + StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->selectingFwdServerHandler(), + SimpleAddTransactionError); +} + +// ************************ replacingFwdAddrHandler Tests ***************** + +// Tests that replacingFwdAddrsHandler rejects invalid events. +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler but with + // an invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->replacingFwdAddrsHandler(), SimpleAddTransactionError); +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes only a forward change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request is sent without error. +// A server response is received which indicates successful update. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, FORWARD_CHG) + ); + + // Should not be an update message yet. + D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest(); + ASSERT_FALSE(update_msg); + + // At this point completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Run replacingFwdAddrsHandler to construct and send the request. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Verify that an update message was constructed properly. + checkSimpleReplaceFwdAddressRequest(*name_add); + + // Verify that we are still in this state and next event is NOP_EVT. + // This indicates we "sent" the message and are waiting for IO completion. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::NOP_EVT); + + // Simulate receiving a successful update response. + name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR()); + + // Run replacingFwdAddrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Forward completion should be true, reverse should be false. + EXPECT_TRUE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Since it is a forward only change, we should be done. + // Verify that we transitioned correctly. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST, + NameChangeTransaction::UPDATE_OK_EVT); +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes a forward and reverse change. +// The update request is sent without error. +// A server response is received which indicates the update was rejected. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) { + SimpleAddStubPtr name_add; + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectFwdServer()); + + // Run replacingFwdAddrsHandler to construct and send the request. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Simulate receiving server rejection response. Per RFC, anything other + // than no error or FQDN in use is failure. Arbitrarily choosing refused. + name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED()); + + // Run replacingFwdAddrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Completion flags should still be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // We should have failed the transaction. Verify that we transitioned + // correctly. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes a forward and reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_Timeout) { + SimpleAddStubPtr name_add; + + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectFwdServer()); + + + // Verify that we can make maximum number of update attempts permitted + // and then transition to selecting a new server. + int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER; + for (int i = 1; i <= max_tries; ++i) { + const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest(); + + // Run replacingFwdAddrsHandler to send the request. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest(); + if (i == 1) { + // First time out we should build the message. + EXPECT_FALSE(prev_msg); + EXPECT_TRUE(curr_msg); + } else { + // Subsequent passes should reuse the request. We are only + // looking to check that we have not replaced the pointer value + // with a new pointer. This tests the on_entry() logic which + // clears the request ONLY upon initial entry into the state. + EXPECT_TRUE(prev_msg == curr_msg); + } + + // Simulate a server IO timeout. + name_add->setDnsUpdateStatus(DNSClient::TIMEOUT); + name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT); + + // Run replacingFwdAddrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + if (i < max_tries) { + // We should be ready to try again. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + } else { + // Server retries should be exhausted, time for a new server. + CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST, + NameChangeTransaction::SERVER_IO_ERROR_EVT); + } + } +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes a forward and reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request is sent but a corrupt response is received, this occurs +// MAX_UPDATE_TRIES_PER_SERVER times. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) { + SimpleAddStubPtr name_add; + + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectFwdServer()); + + // Verify that we can make maximum number of update attempts permitted + // and then transition to selecting a new server. + int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER; + for (int i = 1; i <= max_tries; ++i) { + const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest(); + + // Run replacingFwdAddrsHandler to send the request. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest(); + if (i == 1) { + // First time out we should build the message. + EXPECT_FALSE(prev_msg); + EXPECT_TRUE(curr_msg); + } else { + // Subsequent passes should reuse the request. We are only + // looking to check that we have not replaced the pointer value + // with a new pointer. This tests the on_entry() logic which + // clears the request ONLY upon initial entry into the state. + EXPECT_TRUE(prev_msg == curr_msg); + } + + // Simulate a corrupt server response. + name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE); + name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT); + + // Run replacingFwdAddrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + if (i < max_tries) { + // We should be ready to try again. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + } else { + // Server retries should be exhausted, time for a new server. + CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST, + NameChangeTransaction::SERVER_IO_ERROR_EVT); + } + } +} + +// Tests the selectingRevServerHandler functionality. +// It verifies behavior for the following scenarios: +// +// 1. Posted event is SELECT_SERVER_EVT +// 2. Posted event is SERVER_IO_ERROR_EVT +// 3. Posted event is invalid +// +TEST_F(SimpleAddTransactionTest, selectingRevServerHandler) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST, + NameChangeTransaction::SELECT_SERVER_EVT) + ); + + // Call selectingRevServerHandler enough times to select all of the + // servers in it's current domain. The first time, it will be with + // next event of SELECT_SERVER_EVT. Thereafter it will be with a next + // event of SERVER_IO_ERROR_EVT. + int num_servers = name_add->getReverseDomain()->getServers()->size(); + for (int i = 0; i < num_servers; ++i) { + SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers + << " selections: " << i); + // Run selectingRevServerHandler. + ASSERT_NO_THROW(name_add->selectingRevServerHandler()); + + // Verify that a server was selected. + ASSERT_TRUE(name_add->getCurrentServer()); + + // Verify that we transitioned correctly. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + + // Post a server IO error event. This simulates an IO error occurring + // and a need to select the new server. + ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction:: + SERVER_IO_ERROR_EVT)); + } + + // We should have exhausted the list of servers. Processing another + // SERVER_IO_ERROR_EVT should transition us to failure. + EXPECT_NO_THROW(name_add->selectingRevServerHandler()); + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::NO_MORE_SERVERS_EVT); + + // Create and prep transaction, poised to run the handler but with an + // invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST, + StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->selectingRevServerHandler(), + SimpleAddTransactionError); +} + +//************************** replacingRevPtrsHandler tests ***************** + +// Tests that replacingRevPtrsHandler rejects invalid events. +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler but with + // an invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->replacingRevPtrsHandler(), + SimpleAddTransactionError); +} + +// Tests replacingRevPtrsHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request is sent without error. +// A server response is received which indicates successful update. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + // Should not be an update message yet. + D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest(); + ASSERT_FALSE(update_msg); + + // At this point completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Run replacingRevPtrsHandler to construct and send the request. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Verify that an update message was constructed properly. + checkReplaceRevPtrsRequest(*name_add); + + // Verify that we are still in this state and next event is NOP_EVT. + // This indicates we "sent" the message and are waiting for IO completion. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::NOP_EVT); + + // Simulate receiving a successful update response. + name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR()); + + // Run replacingRevPtrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Forward completion should be false, reverse should be true. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_TRUE(name_add->getReverseChangeCompleted()); + + // Since it is a reverse change, we should be done. + // Verify that we transitioned correctly. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST, + NameChangeTransaction::UPDATE_OK_EVT); +} + +// Tests replacingRevPtrsHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request is sent without error. +// A server response is received which indicates the update was rejected. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_OtherRcode) { + SimpleAddStubPtr name_add; + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectRevServer()); + + // Run replacingRevPtrsHandler to construct and send the request. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Simulate receiving server rejection response. Per RFC, anything other + // than no error is failure. Arbitrarily choosing refused. + name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED()); + + // Run replacingRevPtrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Completion flags should still be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // We should have failed the transaction. Verify that we transitioned + // correctly. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +// Tests replacingRevPtrsHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_Timeout) { + SimpleAddStubPtr name_add; + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectRevServer()); + + // Verify that we can make maximum number of update attempts permitted + // and then transition to selecting a new server. + int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER; + for (int i = 1; i <= max_tries; ++i) { + const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest(); + + // Run replacingRevPtrsHandler to send the request. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest(); + if (i == 1) { + // First time out we should build the message. + EXPECT_FALSE(prev_msg); + EXPECT_TRUE(curr_msg); + } else { + // Subsequent passes should reuse the request. We are only + // looking to check that we have not replaced the pointer value + // with a new pointer. This tests the on_entry() logic which + // clears the request ONLY upon initial entry into the state. + EXPECT_TRUE(prev_msg == curr_msg); + } + + // Simulate a server IO timeout. + name_add->setDnsUpdateStatus(DNSClient::TIMEOUT); + name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT); + + // Run replacingRevPtrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + if (i < max_tries) { + // We should be ready to try again. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + } else { + // Server retries should be exhausted, time for a new server. + CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST, + NameChangeTransaction::SERVER_IO_ERROR_EVT); + } + } +} + +// Tests replacingRevPtrsHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The update request is sent but a corrupt response is received, this occurs +// MAX_UPDATE_TRIES_PER_SERVER times. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) { + SimpleAddStubPtr name_add; + // Create the transaction. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + // Select a server to satisfy log statements. + ASSERT_TRUE(name_add->selectRevServer()); + + // Verify that we can make maximum number of update attempts permitted + // and then transition to selecting a new server. + int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER; + for (int i = 1; i <= max_tries; ++i) { + const D2UpdateMessagePtr prev_msg = name_add->getDnsUpdateRequest(); + + // Run replacingRevPtrsHandler to send the request. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + const D2UpdateMessagePtr curr_msg = name_add->getDnsUpdateRequest(); + if (i == 1) { + // First time out we should build the message. + EXPECT_FALSE(prev_msg); + EXPECT_TRUE(curr_msg); + } else { + // Subsequent passes should reuse the request. We are only + // looking to check that we have not replaced the pointer value + // with a new pointer. This tests the on_entry() logic which + // clears the request ONLY upon initial entry into the state. + EXPECT_TRUE(prev_msg == curr_msg); + } + + // Simulate a server corrupt response. + name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE); + name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT); + + // Run replacingRevPtrsHandler again to process the response. + EXPECT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + if (i < max_tries) { + // We should be ready to try again. + CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT); + } else { + // Server retries should be exhausted, time for a new server. + CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST, + NameChangeTransaction::SERVER_IO_ERROR_EVT); + } + } +} + +// Tests the processAddOkHandler functionality. +// It verifies behavior for the following scenarios: +// +// 1. Posted event is UPDATE_OK_EVT +// 2. Posted event is invalid +// +TEST_F(SimpleAddTransactionTest, processAddOkHandler) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST, + NameChangeTransaction::UPDATE_OK_EVT) + ); + + // Run processAddOkHandler. + EXPECT_NO_THROW(name_add->processAddOkHandler()); + + // Verify that a server was selected. + EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus()); + + // Verify that the model has ended. + CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT); + + // Create and prep transaction, poised to run the handler but with an + // invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST, + StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->processAddOkHandler(), SimpleAddTransactionError); +} + +// Tests the processAddFailedHandler functionality. +// It verifies behavior for the following scenarios: +// +// 1. Posted event is UPDATE_FAILED_EVT +// 2. Posted event is invalid +// +TEST_F(SimpleAddTransactionTest, processAddFailedHandler) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT) + ); + + // Run processAddFailedHandler. + EXPECT_NO_THROW(name_add->processAddFailedHandler()); + + // Verify that a server was selected. + EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus()); + + // Verify that the model has ended. (Remember, the transaction failed NOT + // the model. The model should have ended normally.) + CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT); + + // Create and prep transaction, poised to run the handler but with an + // invalid event. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + StateModel::NOP_EVT) + ); + + // Running the handler should throw. + EXPECT_THROW(name_add->processAddFailedHandler(), SimpleAddTransactionError); +} + +// Tests the processAddFailedHandler functionality. +// It verifies behavior for posted event of NO_MORE_SERVERS_EVT. +TEST_F(SimpleAddTransactionTest, processAddFailedHandler_NoMoreServers) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::NO_MORE_SERVERS_EVT) + ); + + // Run processAddFailedHandler. + EXPECT_NO_THROW(name_add->processAddFailedHandler()); + + // Verify that a server was selected. + EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus()); + + // Verify that the model has ended. (Remember, the transaction failed NOT + // the model. The model should have ended normally.) + CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT); +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes only a forward change. +// Initial posted event is SERVER_SELECTED_EVT. +// The send update request fails due to an unexpected exception. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG) + ); + + name_add->simulate_send_exception_ = true; + + // Run replacingFwdAddrsHandler to construct and send the request. + ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Since IO exceptions should be gracefully handled, any that occur + // are unanticipated, and deemed unrecoverable, so the transaction should + // be transitioned to failure. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +// Tests replacingRevPtrHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The send update request fails due to an unexpected exception. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + name_add->simulate_send_exception_ = true; + + // Run replacingRevPtrsHandler to construct and send the request. + ASSERT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Since IO exceptions should be gracefully handled, any that occur + // are unanticipated, and deemed unrecoverable, so the transaction should + // be transitioned to failure. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +// Tests replacingFwdAddrsHandler with the following scenario: +// +// The request includes only a forward change. +// Initial posted event is SERVER_SELECTED_EVT. +// The request build fails due to an unexpected exception. +// +TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG) + ); + + // Set the one-shot exception simulation flag. + name_add->simulate_build_request_exception_ = true; + + // Run replacingFwdAddrsHandler to construct and send the request. + // This should fail with a build request throw which should be caught + // in the state handler. + ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler()); + + // Verify we did not attempt to send anything. + EXPECT_EQ(0, name_add->getUpdateAttempts()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Since IO exceptions should be gracefully handled, any that occur + // are unanticipated, and deemed unrecoverable, so the transaction should + // be transitioned to failure. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +// Tests replacingRevPtrHandler with the following scenario: +// +// The request includes only a reverse change. +// Initial posted event is SERVER_SELECTED_EVT. +// The request build fails due to an unexpected exception. +// +TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) { + SimpleAddStubPtr name_add; + // Create and prep a transaction, poised to run the handler. + ASSERT_NO_THROW( + name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST, + NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG) + ); + + // Set the one-shot exception simulation flag. + name_add->simulate_build_request_exception_ = true; + + // Run replacingRevPtrsHandler to construct and send the request. + // This should fail with a build request throw which should be caught + // in the state handler. + ASSERT_NO_THROW(name_add->replacingRevPtrsHandler()); + + // Verify we did not attempt to send anything. + EXPECT_EQ(0, name_add->getUpdateAttempts()); + + // Completion flags should be false. + EXPECT_FALSE(name_add->getForwardChangeCompleted()); + EXPECT_FALSE(name_add->getReverseChangeCompleted()); + + // Since IO exceptions should be gracefully handled, any that occur + // are unanticipated, and deemed unrecoverable, so the transaction should + // be transitioned to failure. + CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST, + NameChangeTransaction::UPDATE_FAILED_EVT); +} + +} diff --git a/src/bin/d2/tests/simple_remove_unittests.cc b/src/bin/d2/tests/simple_remove_unittests.cc new file mode 100644 index 0000000000..98a70eb612 --- /dev/null +++ b/src/bin/d2/tests/simple_remove_unittests.cc @@ -0,0 +1,1299 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include +#include + +#include + +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 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); +} + +} diff --git a/src/bin/dhcp4/tests/d2_unittest.cc b/src/bin/dhcp4/tests/d2_unittest.cc index d0e25a6439..0f2d495033 100644 --- a/src/bin/dhcp4/tests/d2_unittest.cc +++ b/src/bin/dhcp4/tests/d2_unittest.cc @@ -60,7 +60,8 @@ Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) { << dhcid_id_num << "\" , " " \"lease-expires-on\" : \"20140121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str())); diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc index 25a70e413f..f54f3fc332 100644 --- a/src/bin/dhcp6/tests/d2_unittest.cc +++ b/src/bin/dhcp6/tests/d2_unittest.cc @@ -62,7 +62,8 @@ Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) { << dhcid_id_num << "\" , " " \"lease-expires-on\" : \"20140121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str())); diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc index aabf8d24ae..f385484349 100644 --- a/src/lib/dhcp_ddns/ncr_msg.cc +++ b/src/lib/dhcp_ddns/ncr_msg.cc @@ -200,7 +200,8 @@ operator<<(std::ostream& os, const D2Dhcid& dhcid) { NameChangeRequest::NameChangeRequest() : change_type_(CHG_ADD), forward_change_(false), reverse_change_(false), fqdn_(""), ip_io_address_("0.0.0.0"), - dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) { + dhcid_(), lease_expires_on_(), lease_length_(0), conflict_resolution_(true), + status_(ST_NEW) { } NameChangeRequest::NameChangeRequest(const NameChangeType change_type, @@ -208,11 +209,13 @@ NameChangeRequest::NameChangeRequest(const NameChangeType change_type, const std::string& fqdn, const std::string& ip_address, const D2Dhcid& dhcid, const uint64_t lease_expires_on, - const uint32_t lease_length) + const uint32_t lease_length, + const bool conflict_resolution) : change_type_(change_type), forward_change_(forward_change), reverse_change_(reverse_change), fqdn_(fqdn), ip_io_address_("0.0.0.0"), dhcid_(dhcid), lease_expires_on_(lease_expires_on), - lease_length_(lease_length), status_(ST_NEW) { + lease_length_(lease_length), conflict_resolution_(conflict_resolution), + status_(ST_NEW) { // User setter to validate fqdn. setFqdn(fqdn); @@ -341,6 +344,10 @@ NameChangeRequest::fromJSON(const std::string& json) { element = ncr->getElement("lease-length", element_map); ncr->setLeaseLength(element); + /// @todo Should this be optional (i.e. backward compatible)? + element = ncr->getElement("use-conflict-resolution", element_map); + ncr->setConflictResolution(element); + // All members were in the Element set and were correct lexically. Now // validate the overall content semantically. This will throw an // NcrMessageError if anything is amiss. @@ -366,7 +373,9 @@ NameChangeRequest::toJSON() const { << "\"ip-address\":\"" << getIpAddress() << "\"," << "\"dhcid\":\"" << getDhcid().toStr() << "\"," << "\"lease-expires-on\":\"" << getLeaseExpiresOnStr() << "\"," - << "\"lease-length\":" << getLeaseLength() << "}"; + << "\"lease-length\":" << getLeaseLength() << "," + << "\"use-conflict-resolution\":" + << (useConflictResolution() ? "true" : "false") << "}"; return (stream.str()); } @@ -576,6 +585,27 @@ NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) { setLeaseLength(static_cast(value)); } +void +NameChangeRequest::setConflictResolution(const bool value) { + conflict_resolution_ = value; +} + +void +NameChangeRequest::setConflictResolution(isc::data::ConstElementPtr element) { + bool value; + try { + // Get the element's boolean value. + value = element->boolValue(); + } catch (const isc::data::TypeError& ex) { + // We expect a boolean Element type, don't have one. + isc_throw(NcrMessageError, + "Wrong data type for use-conflict-resolution: " << ex.what()); + } + + // Good to go, make the assignment. + setConflictResolution(value); +} + void NameChangeRequest::setStatus(const NameChangeStatus value) { status_ = value; @@ -606,7 +636,9 @@ NameChangeRequest::toText() const { << "IP Address: [" << ip_io_address_ << "]" << std::endl << "DHCID: [" << dhcid_.toStr() << "]" << std::endl << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl - << "Lease Length: " << lease_length_ << std::endl; + << "Lease Length: " << lease_length_ << std::endl + << "Conflict Resolution: " << (conflict_resolution_ ? "yes" : "no") + << std::endl; return (stream.str()); } @@ -620,7 +652,8 @@ NameChangeRequest::operator == (const NameChangeRequest& other) { (ip_io_address_ == other.ip_io_address_) && (dhcid_ == other.dhcid_) && (lease_expires_on_ == other.lease_expires_on_) && - (lease_length_ == other.lease_length_)); + (lease_length_ == other.lease_length_) && + (conflict_resolution_ == other.conflict_resolution_)); } bool diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h index 958f57509d..1a718a1f65 100644 --- a/src/lib/dhcp_ddns/ncr_msg.h +++ b/src/lib/dhcp_ddns/ncr_msg.h @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -253,12 +253,15 @@ public: /// expires. /// @param lease_length the amount of time in seconds for which the /// lease is valid (TTL). + /// @param conflict_resolution indicates whether or not conflict resolution + /// (per RFC 4703) is enabled. NameChangeRequest(const NameChangeType change_type, const bool forward_change, const bool reverse_change, const std::string& fqdn, const std::string& ip_address, const D2Dhcid& dhcid, const uint64_t lease_expires_on, - const uint32_t lease_length); + const uint32_t lease_length, + const bool conflict_resolution = true); /// @brief Static method for creating a NameChangeRequest from a /// buffer containing a marshalled request in a given format. @@ -321,7 +324,8 @@ public: /// "ip-address" : "
", /// "dhcid" : "", /// "lease-expires-on" : "", - /// "lease-length" : + /// "lease-length" : , + /// "use-conflict-resolution": /// } /// @endcode /// @@ -357,6 +361,8 @@ public: /// - SS - seconds of the minute (0-59) /// - lease-length - the length of the lease in seconds. This is an /// integer and may range between 1 and 4294967295 (2^32 - 1) inclusive. + /// - use-conflict-resolution - when true, follow RFC 4703 which uses + /// DHCID records to prohibit multiple clients from updating an FQDN /// /// Examples: /// @@ -371,7 +377,8 @@ public: /// "ip-address" : "192.168.2.1" , /// "dhcid" : "010203040A7F8E3D" , /// "lease-expires-on" : "20130121132405", - /// "lease-length" : 1300 + /// "lease-length" : 1300, + /// "use-conflict-resolution": true /// } /// @endcode /// @@ -386,7 +393,8 @@ public: /// "ip-address" : "2001::db8:1::2", /// "dhcid" : "010203040A7F8E3D" , " /// "lease-expires-on" : "20130121132405", - /// "lease-length" : 27400 + /// "lease-length" : 27400, + /// "use-conflict-resolution": true /// } /// @endcode /// @@ -649,6 +657,28 @@ public: /// Element void setLeaseLength(isc::data::ConstElementPtr element); + /// @brief Checks if conflict resolution is enabled + /// + /// @return a true if the conflict resolution is enabled. + bool useConflictResolution() const { + return (conflict_resolution_); + } + + /// @brief Sets the conflict resolution flag to the given value. + /// + /// @param value contains the new value to assign to the conflict + /// resolution flag + void setConflictResolution(const bool value); + + /// @brief Sets the conflict resolution flag to the value of the given Element. + /// + /// @param element is a boolean Element containing the conflict resolution flag + /// value. + /// + /// @throw NcrMessageError if the element is not a boolean + /// Element + void setConflictResolution(isc::data::ConstElementPtr element); + /// @brief Fetches the request status. /// /// @return the request status as a NameChangeStatus @@ -717,6 +747,9 @@ private: /// @brief The amount of time in seconds for which the lease is valid (TTL). uint32_t lease_length_; + /// @brief Indicates if conflict resoltuion is enabled. + bool conflict_resolution_; + /// @brief The processing status of the request. Used internally. NameChangeStatus status_; }; diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc index f910d5eada..c12b646afb 100644 --- a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc +++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc @@ -41,7 +41,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Valid Remove. "{" @@ -52,7 +53,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": false" "}", // Valid Add with IPv6 address "{" @@ -63,7 +65,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true" "}" }; diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc index fe76829793..059c6bd76b 100644 --- a/src/lib/dhcp_ddns/tests/ncr_unittests.cc +++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -34,7 +35,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Valid Remove. "{" @@ -45,7 +47,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Valid Add with IPv6 address "{" @@ -56,7 +59,8 @@ const char *valid_msgs[] = " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}" }; @@ -73,7 +77,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Invalid forward change. "{" @@ -84,7 +89,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Invalid reverse change. "{" @@ -95,7 +101,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Forward and reverse change both false. "{" @@ -106,7 +113,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Blank FQDN "{" @@ -117,7 +125,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Malformed FQDN "{" @@ -128,7 +137,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Bad IP address "{" @@ -140,6 +150,7 @@ const char *invalid_msgs[] = " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " " \"lease-length\" : 1300 " + " \"use-conflict-resolution\": true" "}", // Blank DHCID "{" @@ -150,7 +161,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Odd number of digits in DHCID "{" @@ -161,7 +173,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Text in DHCID "{" @@ -172,7 +185,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"THIS IS BOGUS!!!\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Invalid lease expiration string "{" @@ -183,7 +197,8 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": true" "}", // Non-integer for lease length. "{" @@ -194,9 +209,32 @@ const char *invalid_msgs[] = " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20130121132405\" , " - " \"lease-length\" : \"BOGUS\" " + " \"lease-length\" : \"BOGUS\", " + " \"use-conflict-resolution\": true" + "}", + // Missing use-conflict-resolution + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300 " + "}", + // Invalid use-conflict-resolution + "{" + " \"change-type\" : 0 , " + " \"forward-change\" : true , " + " \"reverse-change\" : false , " + " \"fqdn\" : \"walah.walah.com\" , " + " \"ip-address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"010203040A7F8E3D\" , " + " \"lease-expires-on\" : \"20130121132405\" , " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\": 777" "}" - }; /// @brief Tests the NameChangeRequest constructors. @@ -471,13 +509,14 @@ TEST(NameChangeRequestTest, basicJsonTest) { "\"ip-address\":\"192.168.2.1\"," "\"dhcid\":\"010203040A7F8E3D\"," "\"lease-expires-on\":\"20130121132405\"," - "\"lease-length\":1300" + "\"lease-length\":1300," + "\"use-conflict-resolution\":true" "}"; // Verify that a NameChangeRequests can be instantiated from the // a valid JSON rendition. NameChangeRequestPtr ncr; - ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str)); + ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(msg_str)); ASSERT_TRUE(ncr); // Verify that the JSON string created by the new request equals the @@ -556,7 +595,8 @@ TEST(NameChangeRequestTest, toFromBufferTest) { "\"ip-address\":\"192.168.2.1\"," "\"dhcid\":\"010203040A7F8E3D\"," "\"lease-expires-on\":\"20130121132405\"," - "\"lease-length\":1300" + "\"lease-length\":1300," + "\"use-conflict-resolution\":true" "}"; // Create a request from JSON directly. diff --git a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc index fef107b2ca..1781ff95c4 100644 --- a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc +++ b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc @@ -180,7 +180,8 @@ public: " \"ip-address\" : \"192.168.2.1\" , " " \"dhcid\" : \"010203040A7F8E3D\" , " " \"lease-expires-on\" : \"20140121132405\" , " - " \"lease-length\" : 1300 " + " \"lease-length\" : 1300, " + " \"use-conflict-resolution\" : true " "}"; return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));