]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.dnsdist/test_Routing.py
Merge pull request #8795 from omoerbeek/rec-lua-docs-policytag
[thirdparty/pdns.git] / regression-tests.dnsdist / test_Routing.py
CommitLineData
903853f4 1#!/usr/bin/env python
278403d3 2import base64
903853f4
RG
3import threading
4import time
5import dns
6from dnsdisttests import DNSDistTest
7
8class TestRoutingPoolRouting(DNSDistTest):
9
10 _config_template = """
11 newServer{address="127.0.0.1:%s", pool="real"}
903853f4 12 addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
903853f4
RG
13 """
14
903853f4
RG
15 def testPolicyPoolAction(self):
16 """
17 Routing: Set pool by qname via PoolAction
18
19 Send an A query to "poolaction.routing.tests.powerdns.com.",
20 check that dnsdist routes the query to the "real" pool.
21 """
6bb38cd6 22 name = 'poolaction.routing.tests.powerdns.com.'
903853f4
RG
23 query = dns.message.make_query(name, 'A', 'IN')
24 response = dns.message.make_response(query)
25 rrset = dns.rrset.from_text(name,
26 60,
27 dns.rdataclass.IN,
28 dns.rdatatype.A,
29 '192.0.2.1')
30 response.answer.append(rrset)
31
6ca2e796
RG
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)
903853f4
RG
38
39 def testDefaultPool(self):
40 """
41 Routing: Set pool by qname canary
42
43 Send an A query to "notpool.routing.tests.powerdns.com.",
44 check that dnsdist sends no response (no servers
45 in the default pool).
46 """
47 name = 'notpool.routing.tests.powerdns.com.'
48 query = dns.message.make_query(name, 'A', 'IN')
49
6ca2e796
RG
50 for method in ("sendUDPQuery", "sendTCPQuery"):
51 sender = getattr(self, method)
52 (_, receivedResponse) = sender(query, response=None, useQueue=False)
53 self.assertEquals(receivedResponse, None)
903853f4
RG
54
55class TestRoutingQPSPoolRouting(DNSDistTest):
56 _config_template = """
57 newServer{address="127.0.0.1:%s", pool="regular"}
903853f4
RG
58 addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
59 """
60
903853f4
RG
61 def testQPSPoolAction(self):
62 """
63 Routing: Set pool by QPS via action
64
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.
68 """
69 maxQPS = 10
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,
74 60,
75 dns.rdataclass.IN,
76 dns.rdatatype.A,
77 '192.0.2.1')
78 response.answer.append(rrset)
79
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)
85
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)
90
91 time.sleep(1)
92
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)
99
100
101 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
102 self.assertEquals(receivedResponse, None)
103
104
105class TestRoutingRoundRobinLB(DNSDistTest):
106
107 _testServer2Port = 5351
108 _config_params = ['_testServerPort', '_testServer2Port']
109 _config_template = """
110 setServerPolicy(roundrobin)
111 s1 = newServer{address="127.0.0.1:%s"}
112 s1:setUp()
113 s2 = newServer{address="127.0.0.1:%s"}
114 s2:setUp()
115 """
116
117 @classmethod
118 def startResponders(cls):
119 print("Launching responders..")
5df86a8a 120 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
903853f4
RG
121 cls._UDPResponder.setDaemon(True)
122 cls._UDPResponder.start()
5df86a8a 123 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
903853f4
RG
124 cls._UDPResponder2.setDaemon(True)
125 cls._UDPResponder2.start()
126
5df86a8a 127 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
903853f4
RG
128 cls._TCPResponder.setDaemon(True)
129 cls._TCPResponder.start()
130
5df86a8a 131 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
903853f4
RG
132 cls._TCPResponder2.setDaemon(True)
133 cls._TCPResponder2.start()
134
135 def testRR(self):
136 """
137 Routing: Round Robin
138
278403d3 139 Send 10 A queries to "rr.routing.tests.powerdns.com.",
903853f4
RG
140 check that dnsdist routes half of it to each backend.
141 """
142 numberOfQueries = 10
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,
147 60,
148 dns.rdataclass.IN,
149 dns.rdatatype.A,
150 '192.0.2.1')
151 response.answer.append(rrset)
152
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)
160
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)
166
02bbf9eb
RG
167 for key in self._responsesCounter:
168 value = self._responsesCounter[key]
903853f4
RG
169 self.assertEquals(value, numberOfQueries / 2)
170
171class TestRoutingRoundRobinLBOneDown(DNSDistTest):
172
173 _testServer2Port = 5351
174 _config_params = ['_testServerPort', '_testServer2Port']
175 _config_template = """
176 setServerPolicy(roundrobin)
177 s1 = newServer{address="127.0.0.1:%s"}
178 s1:setUp()
179 s2 = newServer{address="127.0.0.1:%s"}
180 s2:setDown()
181 """
182
183 def testRRWithOneDown(self):
184 """
185 Routing: Round Robin with one server down
186
187 Send 100 A queries to "rr.routing.tests.powerdns.com.",
188 check that dnsdist routes all of it to the only backend up.
189 """
190 numberOfQueries = 10
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,
195 60,
196 dns.rdataclass.IN,
197 dns.rdatatype.A,
198 '192.0.2.1')
199 response.answer.append(rrset)
200
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)
208
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)
214
215 total = 0
02bbf9eb
RG
216 for key in self._responsesCounter:
217 value = self._responsesCounter[key]
903853f4
RG
218 self.assertTrue(value == numberOfQueries or value == 0)
219 total += value
220
221 self.assertEquals(total, numberOfQueries * 2)
d12cd8e9 222
32b86928
RG
223class TestRoutingRoundRobinLBAllDown(DNSDistTest):
224
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"}
231 s1:setDown()
232 s2 = newServer{address="127.0.0.1:%s"}
233 s2:setDown()
234 """
235
236 def testRRWithAllDown(self):
237 """
238 Routing: Round Robin with all servers down
239 """
240 numberOfQueries = 10
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,
245 60,
246 dns.rdataclass.IN,
247 dns.rdatatype.A,
248 '192.0.2.1')
249 response.answer.append(rrset)
250
251 for method in ("sendUDPQuery", "sendTCPQuery"):
252 sender = getattr(self, method)
253 (_, receivedResponse) = sender(query, response=None, useQueue=False)
254 self.assertEquals(receivedResponse, None)
255
d12cd8e9
RG
256class TestRoutingOrder(DNSDistTest):
257
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}
263 s1:setUp()
264 s2 = newServer{address="127.0.0.1:%s", order=1}
265 s2:setUp()
266 """
267
268 @classmethod
269 def startResponders(cls):
270 print("Launching responders..")
5df86a8a 271 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
d12cd8e9
RG
272 cls._UDPResponder.setDaemon(True)
273 cls._UDPResponder.start()
5df86a8a 274 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
d12cd8e9
RG
275 cls._UDPResponder2.setDaemon(True)
276 cls._UDPResponder2.start()
277
5df86a8a 278 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
d12cd8e9
RG
279 cls._TCPResponder.setDaemon(True)
280 cls._TCPResponder.start()
281
5df86a8a 282 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
d12cd8e9
RG
283 cls._TCPResponder2.setDaemon(True)
284 cls._TCPResponder2.start()
285
286 def testOrder(self):
287 """
288 Routing: firstAvailable policy based on 'order'
289
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.
293 """
294 numberOfQueries = 50
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,
299 60,
300 dns.rdataclass.IN,
301 dns.rdatatype.A,
302 '192.0.2.1')
303 response.answer.append(rrset)
304
305 for _ in range(numberOfQueries):
6ca2e796
RG
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)
d12cd8e9
RG
312
313 total = 0
26a3cdb7
RG
314 if 'UDP Responder' in self._responsesCounter:
315 self.assertEquals(self._responsesCounter['UDP Responder'], 0)
d12cd8e9 316 self.assertEquals(self._responsesCounter['UDP Responder 2'], numberOfQueries)
26a3cdb7
RG
317 if 'TCP Responder' in self._responsesCounter:
318 self.assertEquals(self._responsesCounter['TCP Responder'], 0)
d12cd8e9 319 self.assertEquals(self._responsesCounter['TCP Responder 2'], numberOfQueries)
26a3cdb7
RG
320
321class TestRoutingNoServer(DNSDistTest):
322
323 _config_template = """
324 newServer{address="127.0.0.1:%s", pool="real"}
325 setServFailWhenNoServer(true)
326 """
327
328 def testPolicyPoolNoServer(self):
329 """
330 Routing: No server should return ServFail
331 """
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)
336
6ca2e796
RG
337 for method in ("sendUDPQuery", "sendTCPQuery"):
338 sender = getattr(self, method)
339 (_, receivedResponse) = sender(query, response=None, useQueue=False)
340 self.assertEquals(receivedResponse, expectedResponse)
278403d3
DM
341
342class TestRoutingWRandom(DNSDistTest):
343
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}
349 s1:setUp()
350 s2 = newServer{address="127.0.0.1:%s", weight=2}
351 s2:setUp()
352 """
353
354 @classmethod
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._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
361 cls._UDPResponder2.setDaemon(True)
362 cls._UDPResponder2.start()
363
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()
367
368 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
369 cls._TCPResponder2.setDaemon(True)
370 cls._TCPResponder2.start()
371
372 def testWRandom(self):
373 """
374 Routing: WRandom
375
6f9839e9 376 Send 100 A queries to "wrandom.routing.tests.powerdns.com.",
278403d3
DM
377 check that dnsdist routes less than half to one, more to the other.
378 """
379 numberOfQueries = 100
6f9839e9 380 name = 'wrandom.routing.tests.powerdns.com.'
278403d3
DM
381 query = dns.message.make_query(name, 'A', 'IN')
382 response = dns.message.make_response(query)
383 rrset = dns.rrset.from_text(name,
384 60,
385 dns.rdataclass.IN,
386 dns.rdatatype.A,
387 '192.0.2.1')
388 response.answer.append(rrset)
389
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)
397
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)
403
404 # The lower weight downstream should receive less than half the queries
6f9839e9
RG
405 self.assertLess(self._responsesCounter['UDP Responder'], numberOfQueries * 0.50)
406 self.assertLess(self._responsesCounter['TCP Responder'], numberOfQueries * 0.50)
278403d3
DM
407
408 # The higher weight downstream should receive more than half the queries
6f9839e9
RG
409 self.assertGreater(self._responsesCounter['UDP Responder 2'], numberOfQueries * 0.50)
410 self.assertGreater(self._responsesCounter['TCP Responder 2'], numberOfQueries * 0.50)
278403d3
DM
411
412
413class TestRoutingHighValueWRandom(DNSDistTest):
414
415 _testServer2Port = 5351
416 _consoleKey = DNSDistTest.generateConsoleKey()
417 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
418 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
419 _config_template = """
420 setKey("%s")
421 controlSocket("127.0.0.1:%s")
422 setServerPolicy(wrandom)
423 s1 = newServer{address="127.0.0.1:%s", weight=2000000000}
424 s1:setUp()
425 s2 = newServer{address="127.0.0.1:%s", weight=2000000000}
426 s2:setUp()
427 """
428
429 @classmethod
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._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
436 cls._UDPResponder2.setDaemon(True)
437 cls._UDPResponder2.start()
438
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()
442
443 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
444 cls._TCPResponder2.setDaemon(True)
445 cls._TCPResponder2.start()
446
447 def testHighValueWRandom(self):
448 """
6f9839e9 449 Routing: WRandom (overflow)
278403d3 450
6f9839e9 451 Send 100 A queries to "wrandom-overflow.routing.tests.powerdns.com.",
278403d3
DM
452 check that dnsdist routes to each downstream, rather than failing with
453 no-policy.
454 """
455 numberOfQueries = 100
6f9839e9 456 name = 'wrandom-overflow.routing.tests.powerdns.com.'
278403d3
DM
457 query = dns.message.make_query(name, 'A', 'IN')
458 response = dns.message.make_response(query)
459 rrset = dns.rrset.from_text(name,
460 60,
461 dns.rdataclass.IN,
462 dns.rdatatype.A,
463 '192.0.2.1')
464 response.answer.append(rrset)
465
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)
473
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)
479
480 stats = self.sendConsoleCommand("dumpStats()").split()
481 stats_dict = {}
482
483 # Map to a dict with every other element being the value to the previous one
484 for i, x in enumerate(stats):
485 if not i % 2:
486 stats_dict[x] = stats[i+1]
487
488 # There should be no queries getting "no-policy" responses
489 self.assertEquals(stats_dict['no-policy'], '0')
490
491 # Each downstream should receive some queries, but it will be unbalanced
6f9839e9 492 # because the sum of the weights is higher than INT_MAX.
278403d3 493 # The first downstream will receive more than half the queries
6f9839e9
RG
494 self.assertGreater(self._responsesCounter['UDP Responder'], numberOfQueries / 2)
495 self.assertGreater(self._responsesCounter['TCP Responder'], numberOfQueries / 2)
496
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'])
278403d3
DM
502
503class TestRoutingBadWeightWRandom(DNSDistTest):
504
505 _testServer2Port = 5351
506 _consoleKey = DNSDistTest.generateConsoleKey()
507 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
508 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
509 _config_template = """
510 setKey("%s")
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}
515 """
f73ce0e3
RG
516 _checkConfigExpectedOutput = b"""Error creating new server: downstream weight value must be greater than 0.
517Error creating new server: downstream weight value must be between 1 and 2147483647
518Configuration 'configs/dnsdist_TestRoutingBadWeightWRandom.conf' OK!
519"""
278403d3
DM
520
521 def testBadWeightWRandom(self):
522 """
523 Routing: WRandom
524
525 Test that downstreams cannot be added with invalid weights.
526 """
527 # There should be no downstreams
528 self.assertTrue(self.sendConsoleCommand("getServer(0)").startswith("Error"))