]> granicus.if.org Git - pdns/commitdiff
dnsdist: Keep the servers ordered inside pools
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 26 May 2016 17:42:36 +0000 (19:42 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 3 Jun 2016 13:10:09 +0000 (15:10 +0200)
Several policies expect the servers to be ordered on their 'order'.
In addition to that, we keep the servers in a `NumberedVector` to
be able to pass them as a Lua table to Lua custom policies, and
that means we need to get the numbers right there too, especially
when we remove a server from a pool.

pdns/dnsdist.cc
regression-tests.dnsdist/test_Routing.py

index 1035b6bebbf5ebff7b86e8c1b919817e36dcf342..a496e2b6e340c6b0e2ce58fef15f0f6a1c465fe0 100644 (file)
@@ -624,6 +624,15 @@ void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<Dow
     vinfolog("Adding server to default pool");
   }
   pool->servers.push_back(make_pair(++count, server));
+  /* we need to reorder based on the server 'order' */
+  std::stable_sort(pool->servers.begin(), pool->servers.end(), [](const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& a, const std::pair<unsigned int,std::shared_ptr<DownstreamState> >& b) {
+      return a.second->order < b.second->order;
+    });
+  /* and now we need to renumber for Lua (custom policies) */
+  size_t idx = 1;
+  for (auto& server : pool->servers) {
+    server.first = idx++;
+  }
 }
 
 void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server)
@@ -637,10 +646,21 @@ void removeServerFromPool(pools_t& pools, const string& poolName, std::shared_pt
     vinfolog("Removing server from default pool");
   }
 
-  for (NumberedVector<shared_ptr<DownstreamState> >::iterator it = pool->servers.begin(); it != pool->servers.end(); it++) {
-    if (it->second == server) {
-      pool->servers.erase(it);
-      break;
+  size_t idx = 1;
+  bool found = false;
+  for (NumberedVector<shared_ptr<DownstreamState> >::iterator it = pool->servers.begin(); it != pool->servers.end();) {
+    if (found) {
+      /* we need to renumber the servers placed
+         after the removed one, for Lua (custom policies) */
+      it->first = idx++;
+      it++;
+    }
+    else if (it->second == server) {
+      it = pool->servers.erase(it);
+      found = true;
+    } else {
+      idx++;
+      it++;
     }
   }
 }
index f51be3470c87a9ca03f94c86089c3822608c0485..807cdea4e6ba3acdb85b5eb0463ab18ef2eb8144 100644 (file)
@@ -296,3 +296,68 @@ class TestRoutingRoundRobinLBOneDown(DNSDistTest):
             total += value
 
         self.assertEquals(total, numberOfQueries * 2)
+
+class TestRoutingOrder(DNSDistTest):
+
+    _testServer2Port = 5351
+    _config_params = ['_testServerPort', '_testServer2Port']
+    _config_template = """
+    setServerPolicy(firstAvailable)
+    s1 = newServer{address="127.0.0.1:%s", order=2}
+    s1:setUp()
+    s2 = newServer{address="127.0.0.1:%s", order=1}
+    s2:setUp()
+    """
+
+    @classmethod
+    def startResponders(cls):
+        print("Launching responders..")
+        cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort])
+        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.start()
+        cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port])
+        cls._UDPResponder2.setDaemon(True)
+        cls._UDPResponder2.start()
+
+        cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort])
+        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.start()
+
+        cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port])
+        cls._TCPResponder2.setDaemon(True)
+        cls._TCPResponder2.start()
+
+    def testOrder(self):
+        """
+        Routing: firstAvailable policy based on 'order'
+
+        Send 50 A queries to "order.routing.tests.powerdns.com.",
+        check that dnsdist routes all of it to the second backend
+        because it has the lower order value.
+        """
+        numberOfQueries = 50
+        name = 'order.routing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        for _ in range(numberOfQueries):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+
+        total = 0
+        self.assertEquals(self._responsesCounter['UDP Responder'], 0)
+        self.assertEquals(self._responsesCounter['UDP Responder 2'], numberOfQueries)
+        self.assertEquals(self._responsesCounter['TCP Responder'], 0)
+        self.assertEquals(self._responsesCounter['TCP Responder 2'], numberOfQueries)