6 from dnsdisttests
import DNSDistTest
8 class TestRoutingPoolRouting(DNSDistTest
):
10 _config_template
= """
11 newServer{address="127.0.0.1:%s", pool="real"}
12 addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
15 def testPolicyPoolAction(self
):
17 Routing: Set pool by qname via PoolAction
19 Send an A query to "poolaction.routing.tests.powerdns.com.",
20 check that dnsdist routes the query to the "real" pool.
22 name
= 'poolaction.routing.tests.powerdns.com.'
23 query
= dns
.message
.make_query(name
, 'A', 'IN')
24 response
= dns
.message
.make_response(query
)
25 rrset
= dns
.rrset
.from_text(name
,
30 response
.answer
.append(rrset
)
32 for method
in ("sendUDPQuery", "sendTCPQuery"):
33 sender
= getattr(self
, method
)
34 (receivedQuery
, receivedResponse
) = sender(query
, response
)
35 receivedQuery
.id = query
.id
36 self
.assertEquals(query
, receivedQuery
)
37 self
.assertEquals(response
, receivedResponse
)
39 def testDefaultPool(self
):
41 Routing: Set pool by qname canary
43 Send an A query to "notpool.routing.tests.powerdns.com.",
44 check that dnsdist sends no response (no servers
47 name
= 'notpool.routing.tests.powerdns.com.'
48 query
= dns
.message
.make_query(name
, 'A', 'IN')
50 for method
in ("sendUDPQuery", "sendTCPQuery"):
51 sender
= getattr(self
, method
)
52 (_
, receivedResponse
) = sender(query
, response
=None, useQueue
=False)
53 self
.assertEquals(receivedResponse
, None)
55 class TestRoutingQPSPoolRouting(DNSDistTest
):
56 _config_template
= """
57 newServer{address="127.0.0.1:%s", pool="regular"}
58 addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
61 def testQPSPoolAction(self
):
63 Routing: Set pool by QPS via action
65 Send queries to "qpspoolaction.routing.tests.powerdns.com."
66 check that dnsdist does not route the query to the "regular" pool
67 when the max QPS has been reached.
70 name
= 'qpspoolaction.routing.tests.powerdns.com.'
71 query
= dns
.message
.make_query(name
, 'A', 'IN')
72 response
= dns
.message
.make_response(query
)
73 rrset
= dns
.rrset
.from_text(name
,
78 response
.answer
.append(rrset
)
80 for _
in range(maxQPS
):
81 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
82 receivedQuery
.id = query
.id
83 self
.assertEquals(query
, receivedQuery
)
84 self
.assertEquals(response
, receivedResponse
)
86 # we should now be sent to the "abuse" pool which is empty,
87 # so the queries should be dropped
88 (_
, receivedResponse
) = self
.sendUDPQuery(query
, response
=None, useQueue
=False)
89 self
.assertEquals(receivedResponse
, None)
93 # again, over TCP this time
94 for _
in range(maxQPS
):
95 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
96 receivedQuery
.id = query
.id
97 self
.assertEquals(query
, receivedQuery
)
98 self
.assertEquals(response
, receivedResponse
)
101 (_
, receivedResponse
) = self
.sendTCPQuery(query
, response
=None, useQueue
=False)
102 self
.assertEquals(receivedResponse
, None)
105 class TestRoutingRoundRobinLB(DNSDistTest
):
107 _testServer2Port
= 5351
108 _config_params
= ['_testServerPort', '_testServer2Port']
109 _config_template
= """
110 setServerPolicy(roundrobin)
111 s1 = newServer{address="127.0.0.1:%s"}
113 s2 = newServer{address="127.0.0.1:%s"}
118 def startResponders(cls
):
119 print("Launching responders..")
120 cls
._UDPResponder
= threading
.Thread(name
='UDP Responder', target
=cls
.UDPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
121 cls
._UDPResponder
.setDaemon(True)
122 cls
._UDPResponder
.start()
123 cls
._UDPResponder
2 = threading
.Thread(name
='UDP Responder 2', target
=cls
.UDPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
124 cls
._UDPResponder
2.setDaemon(True)
125 cls
._UDPResponder
2.start()
127 cls
._TCPResponder
= threading
.Thread(name
='TCP Responder', target
=cls
.TCPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
128 cls
._TCPResponder
.setDaemon(True)
129 cls
._TCPResponder
.start()
131 cls
._TCPResponder
2 = threading
.Thread(name
='TCP Responder 2', target
=cls
.TCPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
132 cls
._TCPResponder
2.setDaemon(True)
133 cls
._TCPResponder
2.start()
139 Send 10 A queries to "rr.routing.tests.powerdns.com.",
140 check that dnsdist routes half of it to each backend.
143 name
= 'rr.routing.tests.powerdns.com.'
144 query
= dns
.message
.make_query(name
, 'A', 'IN')
145 response
= dns
.message
.make_response(query
)
146 rrset
= dns
.rrset
.from_text(name
,
151 response
.answer
.append(rrset
)
153 # the round robin counter is shared for UDP and TCP,
154 # so we need to do UDP then TCP to have a clean count
155 for _
in range(numberOfQueries
):
156 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
157 receivedQuery
.id = query
.id
158 self
.assertEquals(query
, receivedQuery
)
159 self
.assertEquals(response
, receivedResponse
)
161 for _
in range(numberOfQueries
):
162 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
163 receivedQuery
.id = query
.id
164 self
.assertEquals(query
, receivedQuery
)
165 self
.assertEquals(response
, receivedResponse
)
167 for key
in self
._responsesCounter
:
168 value
= self
._responsesCounter
[key
]
169 self
.assertEquals(value
, numberOfQueries
/ 2)
171 class TestRoutingRoundRobinLBOneDown(DNSDistTest
):
173 _testServer2Port
= 5351
174 _config_params
= ['_testServerPort', '_testServer2Port']
175 _config_template
= """
176 setServerPolicy(roundrobin)
177 s1 = newServer{address="127.0.0.1:%s"}
179 s2 = newServer{address="127.0.0.1:%s"}
183 def testRRWithOneDown(self
):
185 Routing: Round Robin with one server down
187 Send 100 A queries to "rr.routing.tests.powerdns.com.",
188 check that dnsdist routes all of it to the only backend up.
191 name
= 'rr.routing.tests.powerdns.com.'
192 query
= dns
.message
.make_query(name
, 'A', 'IN')
193 response
= dns
.message
.make_response(query
)
194 rrset
= dns
.rrset
.from_text(name
,
199 response
.answer
.append(rrset
)
201 # the round robin counter is shared for UDP and TCP,
202 # so we need to do UDP then TCP to have a clean count
203 for _
in range(numberOfQueries
):
204 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
205 receivedQuery
.id = query
.id
206 self
.assertEquals(query
, receivedQuery
)
207 self
.assertEquals(response
, receivedResponse
)
209 for _
in range(numberOfQueries
):
210 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
211 receivedQuery
.id = query
.id
212 self
.assertEquals(query
, receivedQuery
)
213 self
.assertEquals(response
, receivedResponse
)
216 for key
in self
._responsesCounter
:
217 value
= self
._responsesCounter
[key
]
218 self
.assertTrue(value
== numberOfQueries
or value
== 0)
221 self
.assertEquals(total
, numberOfQueries
* 2)
223 class TestRoutingRoundRobinLBAllDown(DNSDistTest
):
225 _testServer2Port
= 5351
226 _config_params
= ['_testServerPort', '_testServer2Port']
227 _config_template
= """
228 setServerPolicy(roundrobin)
229 setRoundRobinFailOnNoServer(true)
230 s1 = newServer{address="127.0.0.1:%s"}
232 s2 = newServer{address="127.0.0.1:%s"}
236 def testRRWithAllDown(self
):
238 Routing: Round Robin with all servers down
241 name
= 'alldown.rr.routing.tests.powerdns.com.'
242 query
= dns
.message
.make_query(name
, 'A', 'IN')
243 response
= dns
.message
.make_response(query
)
244 rrset
= dns
.rrset
.from_text(name
,
249 response
.answer
.append(rrset
)
251 for method
in ("sendUDPQuery", "sendTCPQuery"):
252 sender
= getattr(self
, method
)
253 (_
, receivedResponse
) = sender(query
, response
=None, useQueue
=False)
254 self
.assertEquals(receivedResponse
, None)
256 class TestRoutingOrder(DNSDistTest
):
258 _testServer2Port
= 5351
259 _config_params
= ['_testServerPort', '_testServer2Port']
260 _config_template
= """
261 setServerPolicy(firstAvailable)
262 s1 = newServer{address="127.0.0.1:%s", order=2}
264 s2 = newServer{address="127.0.0.1:%s", order=1}
269 def startResponders(cls
):
270 print("Launching responders..")
271 cls
._UDPResponder
= threading
.Thread(name
='UDP Responder', target
=cls
.UDPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
272 cls
._UDPResponder
.setDaemon(True)
273 cls
._UDPResponder
.start()
274 cls
._UDPResponder
2 = threading
.Thread(name
='UDP Responder 2', target
=cls
.UDPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
275 cls
._UDPResponder
2.setDaemon(True)
276 cls
._UDPResponder
2.start()
278 cls
._TCPResponder
= threading
.Thread(name
='TCP Responder', target
=cls
.TCPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
279 cls
._TCPResponder
.setDaemon(True)
280 cls
._TCPResponder
.start()
282 cls
._TCPResponder
2 = threading
.Thread(name
='TCP Responder 2', target
=cls
.TCPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
283 cls
._TCPResponder
2.setDaemon(True)
284 cls
._TCPResponder
2.start()
288 Routing: firstAvailable policy based on 'order'
290 Send 50 A queries to "order.routing.tests.powerdns.com.",
291 check that dnsdist routes all of it to the second backend
292 because it has the lower order value.
295 name
= 'order.routing.tests.powerdns.com.'
296 query
= dns
.message
.make_query(name
, 'A', 'IN')
297 response
= dns
.message
.make_response(query
)
298 rrset
= dns
.rrset
.from_text(name
,
303 response
.answer
.append(rrset
)
305 for _
in range(numberOfQueries
):
306 for method
in ("sendUDPQuery", "sendTCPQuery"):
307 sender
= getattr(self
, method
)
308 (receivedQuery
, receivedResponse
) = sender(query
, response
)
309 receivedQuery
.id = query
.id
310 self
.assertEquals(query
, receivedQuery
)
311 self
.assertEquals(response
, receivedResponse
)
314 if 'UDP Responder' in self
._responsesCounter
:
315 self
.assertEquals(self
._responsesCounter
['UDP Responder'], 0)
316 self
.assertEquals(self
._responsesCounter
['UDP Responder 2'], numberOfQueries
)
317 if 'TCP Responder' in self
._responsesCounter
:
318 self
.assertEquals(self
._responsesCounter
['TCP Responder'], 0)
319 self
.assertEquals(self
._responsesCounter
['TCP Responder 2'], numberOfQueries
)
321 class TestRoutingNoServer(DNSDistTest
):
323 _config_template
= """
324 newServer{address="127.0.0.1:%s", pool="real"}
325 setServFailWhenNoServer(true)
328 def testPolicyPoolNoServer(self
):
330 Routing: No server should return ServFail
332 name
= 'noserver.routing.tests.powerdns.com.'
333 query
= dns
.message
.make_query(name
, 'A', 'IN')
334 expectedResponse
= dns
.message
.make_response(query
)
335 expectedResponse
.set_rcode(dns
.rcode
.SERVFAIL
)
337 for method
in ("sendUDPQuery", "sendTCPQuery"):
338 sender
= getattr(self
, method
)
339 (_
, receivedResponse
) = sender(query
, response
=None, useQueue
=False)
340 self
.assertEquals(receivedResponse
, expectedResponse
)
342 class TestRoutingWRandom(DNSDistTest
):
344 _testServer2Port
= 5351
345 _config_params
= ['_testServerPort', '_testServer2Port']
346 _config_template
= """
347 setServerPolicy(wrandom)
348 s1 = newServer{address="127.0.0.1:%s", weight=1}
350 s2 = newServer{address="127.0.0.1:%s", weight=2}
355 def startResponders(cls
):
356 print("Launching responders..")
357 cls
._UDPResponder
= threading
.Thread(name
='UDP Responder', target
=cls
.UDPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
358 cls
._UDPResponder
.setDaemon(True)
359 cls
._UDPResponder
.start()
360 cls
._UDPResponder
2 = threading
.Thread(name
='UDP Responder 2', target
=cls
.UDPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
361 cls
._UDPResponder
2.setDaemon(True)
362 cls
._UDPResponder
2.start()
364 cls
._TCPResponder
= threading
.Thread(name
='TCP Responder', target
=cls
.TCPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
365 cls
._TCPResponder
.setDaemon(True)
366 cls
._TCPResponder
.start()
368 cls
._TCPResponder
2 = threading
.Thread(name
='TCP Responder 2', target
=cls
.TCPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
369 cls
._TCPResponder
2.setDaemon(True)
370 cls
._TCPResponder
2.start()
372 def testWRandom(self
):
376 Send 100 A queries to "wrandom.routing.tests.powerdns.com.",
377 check that dnsdist routes less than half to one, more to the other.
379 numberOfQueries
= 100
380 name
= 'wrandom.routing.tests.powerdns.com.'
381 query
= dns
.message
.make_query(name
, 'A', 'IN')
382 response
= dns
.message
.make_response(query
)
383 rrset
= dns
.rrset
.from_text(name
,
388 response
.answer
.append(rrset
)
390 # the counter is shared for UDP and TCP,
391 # so we need to do UDP then TCP to have a clean count
392 for _
in range(numberOfQueries
):
393 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
394 receivedQuery
.id = query
.id
395 self
.assertEquals(query
, receivedQuery
)
396 self
.assertEquals(response
, receivedResponse
)
398 for _
in range(numberOfQueries
):
399 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
400 receivedQuery
.id = query
.id
401 self
.assertEquals(query
, receivedQuery
)
402 self
.assertEquals(response
, receivedResponse
)
404 # The lower weight downstream should receive less than half the queries
405 self
.assertLess(self
._responsesCounter
['UDP Responder'], numberOfQueries
* 0.50)
406 self
.assertLess(self
._responsesCounter
['TCP Responder'], numberOfQueries
* 0.50)
408 # The higher weight downstream should receive more than half the queries
409 self
.assertGreater(self
._responsesCounter
['UDP Responder 2'], numberOfQueries
* 0.50)
410 self
.assertGreater(self
._responsesCounter
['TCP Responder 2'], numberOfQueries
* 0.50)
413 class TestRoutingHighValueWRandom(DNSDistTest
):
415 _testServer2Port
= 5351
416 _consoleKey
= DNSDistTest
.generateConsoleKey()
417 _consoleKeyB64
= base64
.b64encode(_consoleKey
).decode('ascii')
418 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
419 _config_template
= """
421 controlSocket("127.0.0.1:%s")
422 setServerPolicy(wrandom)
423 s1 = newServer{address="127.0.0.1:%s", weight=2000000000}
425 s2 = newServer{address="127.0.0.1:%s", weight=2000000000}
430 def startResponders(cls
):
431 print("Launching responders..")
432 cls
._UDPResponder
= threading
.Thread(name
='UDP Responder', target
=cls
.UDPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
433 cls
._UDPResponder
.setDaemon(True)
434 cls
._UDPResponder
.start()
435 cls
._UDPResponder
2 = threading
.Thread(name
='UDP Responder 2', target
=cls
.UDPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
436 cls
._UDPResponder
2.setDaemon(True)
437 cls
._UDPResponder
2.start()
439 cls
._TCPResponder
= threading
.Thread(name
='TCP Responder', target
=cls
.TCPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
440 cls
._TCPResponder
.setDaemon(True)
441 cls
._TCPResponder
.start()
443 cls
._TCPResponder
2 = threading
.Thread(name
='TCP Responder 2', target
=cls
.TCPResponder
, args
=[cls
._testServer
2Port
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
444 cls
._TCPResponder
2.setDaemon(True)
445 cls
._TCPResponder
2.start()
447 def testHighValueWRandom(self
):
449 Routing: WRandom (overflow)
451 Send 100 A queries to "wrandom-overflow.routing.tests.powerdns.com.",
452 check that dnsdist routes to each downstream, rather than failing with
455 numberOfQueries
= 100
456 name
= 'wrandom-overflow.routing.tests.powerdns.com.'
457 query
= dns
.message
.make_query(name
, 'A', 'IN')
458 response
= dns
.message
.make_response(query
)
459 rrset
= dns
.rrset
.from_text(name
,
464 response
.answer
.append(rrset
)
466 # the counter is shared for UDP and TCP,
467 # so we need to do UDP then TCP to have a clean count
468 for _
in range(numberOfQueries
):
469 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
470 receivedQuery
.id = query
.id
471 self
.assertEquals(query
, receivedQuery
)
472 self
.assertEquals(response
, receivedResponse
)
474 for _
in range(numberOfQueries
):
475 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
476 receivedQuery
.id = query
.id
477 self
.assertEquals(query
, receivedQuery
)
478 self
.assertEquals(response
, receivedResponse
)
480 stats
= self
.sendConsoleCommand("dumpStats()").split()
483 # Map to a dict with every other element being the value to the previous one
484 for i
, x
in enumerate(stats
):
486 stats_dict
[x
] = stats
[i
+1]
488 # There should be no queries getting "no-policy" responses
489 self
.assertEquals(stats_dict
['no-policy'], '0')
491 # Each downstream should receive some queries, but it will be unbalanced
492 # because the sum of the weights is higher than INT_MAX.
493 # The first downstream will receive more than half the queries
494 self
.assertGreater(self
._responsesCounter
['UDP Responder'], numberOfQueries
/ 2)
495 self
.assertGreater(self
._responsesCounter
['TCP Responder'], numberOfQueries
/ 2)
497 # The second downstream will receive the remainder of the queries, but it might very well be 0
498 if 'UDP Responder 2' in self
._responsesCounter
:
499 self
.assertEquals(self
._responsesCounter
['UDP Responder 2'], numberOfQueries
- self
._responsesCounter
['UDP Responder'])
500 if 'TCP Responder 2' in self
._responsesCounter
:
501 self
.assertEquals(self
._responsesCounter
['TCP Responder 2'], numberOfQueries
- self
._responsesCounter
['TCP Responder'])
503 class TestRoutingBadWeightWRandom(DNSDistTest
):
505 _testServer2Port
= 5351
506 _consoleKey
= DNSDistTest
.generateConsoleKey()
507 _consoleKeyB64
= base64
.b64encode(_consoleKey
).decode('ascii')
508 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
509 _config_template
= """
511 controlSocket("127.0.0.1:%s")
512 setServerPolicy(wrandom)
513 s1 = newServer{address="127.0.0.1:%s", weight=-1}
514 s2 = newServer{address="127.0.0.1:%s", weight=2147483648}
516 _checkConfigExpectedOutput
= b
"""Error creating new server: downstream weight value must be greater than 0.
517 Error creating new server: downstream weight value must be between 1 and 2147483647
518 Configuration 'configs/dnsdist_TestRoutingBadWeightWRandom.conf' OK!
521 def testBadWeightWRandom(self
):
525 Test that downstreams cannot be added with invalid weights.
527 # There should be no downstreams
528 self
.assertTrue(self
.sendConsoleCommand("getServer(0)").startswith("Error"))