summaryrefslogtreecommitdiffstats
path: root/src/bin/d2/d2_update_mgr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/d2/d2_update_mgr.cc')
-rw-r--r--src/bin/d2/d2_update_mgr.cc295
1 files changed, 295 insertions, 0 deletions
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
new file mode 100644
index 0000000..223430e
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -0,0 +1,295 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2/d2_update_mgr.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.h>
+#include <d2/simple_add.h>
+#include <d2/simple_remove.h>
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+namespace isc {
+namespace d2 {
+
+const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
+
+D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ asiolink::IOServicePtr& io_service,
+ const size_t max_transactions)
+ :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
+ if (!queue_mgr_) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr queue manager cannot be null");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw(D2UpdateMgrError,
+ "D2UpdateMgr configuration manager cannot be null");
+ }
+
+ if (!io_service_) {
+ isc_throw(D2UpdateMgrError, "IOServicePtr cannot be null");
+ }
+
+ // Use setter to do validation.
+ setMaxTransactions(max_transactions);
+}
+
+D2UpdateMgr::~D2UpdateMgr() {
+ transaction_list_.clear();
+}
+
+void D2UpdateMgr::sweep() {
+ // cleanup finished transactions;
+ checkFinishedTransactions();
+
+ // if the queue isn't empty, find the next suitable job and
+ // start a transaction for it.
+ // @todo - Do we want to queue max transactions? The logic here will only
+ // start one new transaction per invocation. On the other hand a busy
+ // system will generate many IO events and this method will be called
+ // frequently. It will likely achieve max transactions quickly on its own.
+ if (getQueueCount() > 0) {
+ if (getTransactionCount() >= max_transactions_) {
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_AT_MAX_TRANSACTIONS).arg(getQueueCount())
+ .arg(getMaxTransactions());
+
+ return;
+ }
+
+ // We are not at maximum transactions, so pick and start the next job.
+ pickNextJob();
+ }
+}
+
+void
+D2UpdateMgr::checkFinishedTransactions() {
+ // Cycle through transaction list and do whatever needs to be done
+ // for finished transactions.
+ // At the moment all we do is remove them from the list. This is likely
+ // to expand as DHCP_DDNS matures.
+ // NOTE: One must use postfix increments of the iterator on the calls
+ // to erase. This replaces the old iterator which becomes invalid by the
+ // erase with the next valid iterator. Prefix incrementing will not
+ // work.
+ TransactionList::iterator it = transaction_list_.begin();
+ while (it != transaction_list_.end()) {
+ NameChangeTransactionPtr trans = (*it).second;
+ if (trans->isModelDone()) {
+ // @todo Additional actions based on NCR status could be
+ // performed here.
+ transaction_list_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void D2UpdateMgr::pickNextJob() {
+ // Start at the front of the queue, looking for the first entry for
+ // which no transaction is in progress. If we find an eligible entry
+ // remove it from the queue and make a transaction for it.
+ // Requests and transactions are associated by DHCID. If a request has
+ // the same DHCID as a transaction, they are presumed to be for the same
+ // "end user".
+ size_t queue_count = getQueueCount();
+ for (size_t index = 0; index < queue_count; ++index) {
+ dhcp_ddns::NameChangeRequestPtr found_ncr = queue_mgr_->peekAt(index);
+ if (!hasTransaction(found_ncr->getDhcid())) {
+ queue_mgr_->dequeueAt(index);
+ makeTransaction(found_ncr);
+ return;
+ }
+ }
+
+ // There were no eligible jobs. All of the current DHCIDs already have
+ // transactions pending.
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_NO_ELIGIBLE_JOBS)
+ .arg(getQueueCount()).arg(getTransactionCount());
+}
+
+void
+D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
+ // First lets ensure there is not a transaction in progress for this
+ // DHCID. (pickNextJob should ensure this, as it is the only real caller
+ // but for safety's sake we'll check).
+ const TransactionKey& key = next_ncr->getDhcid();
+ if (findTransaction(key) != transactionListEnd()) {
+ // This is programmatic error. Caller(s) should be checking this.
+ isc_throw(D2UpdateMgrError, "Transaction already in progress for: "
+ << key.toStr());
+ }
+
+ int direction_count = 0;
+ // If forward change is enabled, match to forward servers.
+ DdnsDomainPtr forward_domain;
+ if (next_ncr->isForwardChange()) {
+ if (!cfg_mgr_->forwardUpdatesEnabled()) {
+ next_ncr->setForwardChange(false);
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_FWD_REQUEST_IGNORED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ } else {
+ bool matched = cfg_mgr_->matchForward(next_ncr->getFqdn(),
+ forward_domain);
+ // Could not find a match for forward DNS server. Log it and get
+ // out. This has the net affect of dropping the request on the
+ // floor.
+ if (!matched) {
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_NO_FWD_MATCH_ERROR)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ ++direction_count;
+ }
+ }
+
+ // If reverse change is enabled, match to reverse servers.
+ DdnsDomainPtr reverse_domain;
+ if (next_ncr->isReverseChange()) {
+ if (!cfg_mgr_->reverseUpdatesEnabled()) {
+ next_ncr->setReverseChange(false);
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_REV_REQUEST_IGNORED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ } else {
+ bool matched = cfg_mgr_->matchReverse(next_ncr->getIpAddress(),
+ reverse_domain);
+ // Could not find a match for reverse DNS server. Log it and get
+ // out. This has the net affect of dropping the request on the
+ // floor.
+ if (!matched) {
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_NO_REV_MATCH_ERROR)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ ++direction_count;
+ }
+ }
+
+ // If there is nothing to actually do, then the request falls on the floor.
+ // Should we log this?
+ if (!direction_count) {
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_REQUEST_DROPPED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ // We matched to the required servers, so construct the transaction.
+ // @todo If multi-threading is implemented, one would pass in an
+ // empty IOServicePtr, rather than our instance value. This would cause
+ // the transaction to instantiate its own, separate IOService to handle
+ // the transaction's IO.
+ NameChangeTransactionPtr trans;
+ if (next_ncr->getChangeType() == dhcp_ddns::CHG_ADD) {
+ 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 {
+ 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.
+ transaction_list_[key] = trans;
+
+ // Start it.
+ trans->startTransaction();
+}
+
+TransactionList::iterator
+D2UpdateMgr::findTransaction(const TransactionKey& key) {
+ return (transaction_list_.find(key));
+}
+
+bool
+D2UpdateMgr::hasTransaction(const TransactionKey& key) {
+ return (findTransaction(key) != transactionListEnd());
+}
+
+void
+D2UpdateMgr::removeTransaction(const TransactionKey& key) {
+ TransactionList::iterator pos = findTransaction(key);
+ if (pos != transactionListEnd()) {
+ transaction_list_.erase(pos);
+ }
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListBegin() {
+ return (transaction_list_.begin());
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListEnd() {
+ return (transaction_list_.end());
+}
+
+void
+D2UpdateMgr::clearTransactionList() {
+ // @todo for now this just wipes them out. We might need something
+ // more elegant, that allows a cancel first.
+ transaction_list_.clear();
+}
+
+void
+D2UpdateMgr::setMaxTransactions(const size_t new_trans_max) {
+ // Obviously we need at room for at least one transaction.
+ if (new_trans_max < 1) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr"
+ " maximum transactions limit must be greater than zero");
+ }
+
+ // Do not allow the list maximum to be set to less then current list size.
+ if (new_trans_max < getTransactionCount()) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr maximum transaction limit "
+ "cannot be less than the current transaction count :"
+ << getTransactionCount());
+ }
+
+ max_transactions_ = new_trans_max;
+}
+
+size_t
+D2UpdateMgr::getQueueCount() const {
+ return (queue_mgr_->getQueueSize());
+}
+
+size_t
+D2UpdateMgr::getTransactionCount() const {
+ return (transaction_list_.size());
+}
+
+
+} // namespace isc::d2
+} // namespace isc