]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add a unit test for the outgoing connection cache
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 3 Nov 2021 13:43:00 +0000 (14:43 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 8 Nov 2021 09:26:12 +0000 (10:26 +0100)
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-tcp-downstream.hh
pdns/dnsdistdist/test-dnsdist-connections-cache.cc [new file with mode: 0644]

index 76260871af0e0709c556f65243d0346d893a039b..f1a614026b551d1edc4df1b091a760f1cf6b5427 100644 (file)
@@ -285,6 +285,7 @@ testrunner_SOURCES = \
        test-credentials_cc.cc \
        test-delaypipe_hh.cc \
        test-dnscrypt_cc.cc \
+       test-dnsdist-connections-cache.cc \
        test-dnsdist_cc.cc \
        test-dnsdistdynblocks_hh.cc \
        test-dnsdistkvs_cc.cc \
index 0eb6c1a2258d1ced8b0ecb5beb4b5e718b35d95f..ace31e4e4817e789b211cc957f717864e7c2bcd1 100644 (file)
@@ -316,26 +316,6 @@ public:
     s_maxIdleTime = max;
   }
 
-  bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
-  {
-    if (!conn->canBeReused()) {
-      return false;
-    }
-
-    /* for connections that have not been used very recently,
-       check whether they have been closed in the meantime */
-    if (freshCutOff < conn->getLastDataReceivedTime()) {
-      /* used recently enough, skip the check */
-      return true;
-    }
-
-    if (conn->isUsable()) {
-      return true;
-    }
-
-    return false;
-  }
-
   std::shared_ptr<T> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now, std::string&& proxyProtocolPayload)
   {
     struct timeval freshCutOff = now;
@@ -375,7 +355,11 @@ public:
 
     auto newConnection = std::make_shared<T>(ds, mplexer, now, std::move(proxyProtocolPayload));
     if (!haveProxyProtocol) {
-      d_downstreamConnections[backendId].push_front(newConnection);
+      auto& list = d_downstreamConnections[backendId];
+      if (list.size() == s_maxCachedConnectionsPerDownstream) {
+        list.pop_back();
+      }
+      list.push_front(newConnection);
     }
 
     return newConnection;
@@ -446,6 +430,15 @@ public:
     return count;
   }
 
+  size_t count() const
+  {
+    size_t count = 0;
+    for (const auto& downstream : d_downstreamConnections) {
+      count += downstream.second.size();
+    }
+    return count;
+  }
+
   bool removeDownstreamConnection(std::shared_ptr<T>& conn)
   {
     bool found = false;
@@ -467,6 +460,26 @@ public:
 
 protected:
 
+  bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
+  {
+    if (!conn->canBeReused()) {
+      return false;
+    }
+
+    /* for connections that have not been used very recently,
+       check whether they have been closed in the meantime */
+    if (freshCutOff < conn->getLastDataReceivedTime()) {
+      /* used recently enough, skip the check */
+      return true;
+    }
+
+    if (conn->isUsable()) {
+      return true;
+    }
+
+    return false;
+  }
+
   static size_t s_maxCachedConnectionsPerDownstream;
   static uint16_t s_cleanupInterval;
   static uint16_t s_maxIdleTime;
diff --git a/pdns/dnsdistdist/test-dnsdist-connections-cache.cc b/pdns/dnsdistdist/test-dnsdist-connections-cache.cc
new file mode 100644 (file)
index 0000000..6f708de
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-tcp-downstream.hh"
+
+class MockupConnection
+{
+public:
+  MockupConnection(const std::shared_ptr<DownstreamState>&, std::unique_ptr<FDMultiplexer>&, const struct timeval&, std::string&&)
+  {
+  }
+
+  bool canBeReused() const
+  {
+    return d_reusable;
+  }
+
+  bool isUsable() const
+  {
+    return d_usable;
+  }
+
+  bool willBeReusable(bool) const
+  {
+    return d_reusable;
+  }
+
+  void setReused()
+  {
+  }
+
+  struct timeval getLastDataReceivedTime() const
+  {
+    return d_lastDataReceivedTime;
+  }
+
+  bool isIdle() const
+  {
+    return d_idle;
+  }
+
+  void stopIO()
+  {
+  }
+
+  struct timeval d_lastDataReceivedTime{0, 0};
+  bool d_reusable{true};
+  bool d_usable{true};
+  bool d_idle{false};
+};
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_connections_cache)
+
+BOOST_AUTO_TEST_CASE(test_ConnectionsCache)
+{
+  DownstreamConnectionsManager<MockupConnection> manager;
+  const size_t maxConnPerDownstream = 5;
+  const uint16_t cleanupInterval = 1;
+  const uint16_t maxIdleTime = 5;
+  manager.setMaxCachedConnectionsPerDownstream(maxConnPerDownstream);
+  manager.setCleanupInterval(cleanupInterval);
+  manager.setMaxIdleTime(maxIdleTime);
+
+  auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+  auto downstream1 = std::make_shared<DownstreamState>(ComboAddress("192.0.2.1"));
+  auto downstream2 = std::make_shared<DownstreamState>(ComboAddress("192.0.2.2"));
+  struct timeval now;
+  gettimeofday(&now, nullptr);
+
+  auto conn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+  BOOST_REQUIRE(conn != nullptr);
+  BOOST_CHECK_EQUAL(manager.count(), 1U);
+
+  /* since the connection can be reused, we should get the same one */
+  {
+    auto conn1 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+    BOOST_CHECK(conn.get() == conn1.get());
+    BOOST_CHECK_EQUAL(manager.count(), 1U);
+  }
+
+  /* if we mark it non-usable, we should get a new one */
+  conn->d_usable = false;
+  auto conn2 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+  BOOST_CHECK(conn.get() != conn2.get());
+  BOOST_CHECK_EQUAL(manager.count(), 2U);
+
+  /* since the second connection can be reused, we should get it */
+  {
+    auto conn3 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+    BOOST_CHECK(conn3.get() == conn2.get());
+    BOOST_CHECK_EQUAL(manager.count(), 2U);
+  }
+
+  /* different downstream so different connection */
+  auto differentConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string());
+  BOOST_REQUIRE(differentConn != nullptr);
+  BOOST_CHECK(differentConn.get() != conn.get());
+  BOOST_CHECK(differentConn.get() != conn2.get());
+  BOOST_CHECK_EQUAL(manager.count(), 3U);
+  {
+    /* but we should be able to reuse it */
+    auto sameConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string());
+    BOOST_CHECK(sameConn.get() == differentConn.get());
+    BOOST_CHECK_EQUAL(manager.count(), 3U);
+  }
+
+  struct timeval later = now;
+  later.tv_sec += cleanupInterval + 1;
+
+  /* mark the second connection as no longer usable */
+  conn2->d_usable = false;
+  /* first one as well but still fresh so it will not get checked */
+  conn->d_usable = true;
+  conn->d_lastDataReceivedTime = later;
+  /* third one is usable but idle for too long */
+  differentConn->d_idle = true;
+  differentConn->d_lastDataReceivedTime = later;
+  differentConn->d_lastDataReceivedTime.tv_sec -= (maxIdleTime + 1);
+
+  /* we should not do an actual cleanup attempt since the last cleanup was done recently */
+  manager.cleanupClosedConnections(now);
+  BOOST_CHECK_EQUAL(manager.count(), 3U);
+
+  manager.cleanupClosedConnections(later);
+  BOOST_CHECK_EQUAL(manager.count(), 1U);
+
+  /* mark the remaining conn as non-usable, to get new ones */
+  conn->d_usable = false;
+  conn->d_lastDataReceivedTime.tv_sec = 0;
+
+  std::vector<std::shared_ptr<MockupConnection>> conns = { conn };
+  while (conns.size() < maxConnPerDownstream) {
+    auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+    newConn->d_usable = false;
+    conns.push_back(newConn);
+    BOOST_CHECK_EQUAL(manager.count(), conns.size());
+  }
+
+  /* if we add a new one, the oldest should get expunged */
+  auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+  BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream);
+
+  {
+    /* mark all connections as not usable anymore */
+    for (auto& c : conns) {
+      c->d_usable = false;
+    }
+
+    /* except the last one */
+    newConn->d_usable = true;
+
+    BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream);
+    later.tv_sec += cleanupInterval + 1;
+    manager.cleanupClosedConnections(later);
+    BOOST_CHECK_EQUAL(manager.count(), 1U);
+  }
+
+  conns.clear();
+  auto cleared = manager.clear();
+  BOOST_CHECK_EQUAL(cleared, 1U);
+}
+
+BOOST_AUTO_TEST_SUITE_END();