]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_Routing.py
Merge pull request #6448 from dmccombs/weight-fix
[thirdparty/pdns.git] / regression-tests.dnsdist / test_Routing.py
1 #!/usr/bin/env python
2 import base64
3 import threading
4 import time
5 import dns
6 from dnsdisttests import DNSDistTest
7
8 class TestRoutingPoolRouting(DNSDistTest):
9
10 _config_template = """
11 newServer{address="127.0.0.1:%s", pool="real"}
12 addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
13 """
14
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 """
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,
26 60,
27 dns.rdataclass.IN,
28 dns.rdatatype.A,
29 '192.0.2.1')
30 response.answer.append(rrset)
31
32 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
33 receivedQuery.id = query.id
34 self.assertEquals(query, receivedQuery)
35 self.assertEquals(response, receivedResponse)
36
37 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
38 receivedQuery.id = query.id
39 self.assertEquals(query, receivedQuery)
40 self.assertEquals(response, receivedResponse)
41
42 def testDefaultPool(self):
43 """
44 Routing: Set pool by qname canary
45
46 Send an A query to "notpool.routing.tests.powerdns.com.",
47 check that dnsdist sends no response (no servers
48 in the default pool).
49 """
50 name = 'notpool.routing.tests.powerdns.com.'
51 query = dns.message.make_query(name, 'A', 'IN')
52
53 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
54 self.assertEquals(receivedResponse, None)
55
56 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
57 self.assertEquals(receivedResponse, None)
58
59 class TestRoutingQPSPoolRouting(DNSDistTest):
60 _config_template = """
61 newServer{address="127.0.0.1:%s", pool="regular"}
62 addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
63 """
64
65 def testQPSPoolAction(self):
66 """
67 Routing: Set pool by QPS via action
68
69 Send queries to "qpspoolaction.routing.tests.powerdns.com."
70 check that dnsdist does not route the query to the "regular" pool
71 when the max QPS has been reached.
72 """
73 maxQPS = 10
74 name = 'qpspoolaction.routing.tests.powerdns.com.'
75 query = dns.message.make_query(name, 'A', 'IN')
76 response = dns.message.make_response(query)
77 rrset = dns.rrset.from_text(name,
78 60,
79 dns.rdataclass.IN,
80 dns.rdatatype.A,
81 '192.0.2.1')
82 response.answer.append(rrset)
83
84 for _ in range(maxQPS):
85 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
86 receivedQuery.id = query.id
87 self.assertEquals(query, receivedQuery)
88 self.assertEquals(response, receivedResponse)
89
90 # we should now be sent to the "abuse" pool which is empty,
91 # so the queries should be dropped
92 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
93 self.assertEquals(receivedResponse, None)
94
95 time.sleep(1)
96
97 # again, over TCP this time
98 for _ in range(maxQPS):
99 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
100 receivedQuery.id = query.id
101 self.assertEquals(query, receivedQuery)
102 self.assertEquals(response, receivedResponse)
103
104
105 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
106 self.assertEquals(receivedResponse, None)
107
108
109 class TestRoutingRoundRobinLB(DNSDistTest):
110
111 _testServer2Port = 5351
112 _config_params = ['_testServerPort', '_testServer2Port']
113 _config_template = """
114 setServerPolicy(roundrobin)
115 s1 = newServer{address="127.0.0.1:%s"}
116 s1:setUp()
117 s2 = newServer{address="127.0.0.1:%s"}
118 s2:setUp()
119 """
120
121 @classmethod
122 def startResponders(cls):
123 print("Launching responders..")
124 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
125 cls._UDPResponder.setDaemon(True)
126 cls._UDPResponder.start()
127 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
128 cls._UDPResponder2.setDaemon(True)
129 cls._UDPResponder2.start()
130
131 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
132 cls._TCPResponder.setDaemon(True)
133 cls._TCPResponder.start()
134
135 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
136 cls._TCPResponder2.setDaemon(True)
137 cls._TCPResponder2.start()
138
139 def testRR(self):
140 """
141 Routing: Round Robin
142
143 Send 10 A queries to "rr.routing.tests.powerdns.com.",
144 check that dnsdist routes half of it to each backend.
145 """
146 numberOfQueries = 10
147 name = 'rr.routing.tests.powerdns.com.'
148 query = dns.message.make_query(name, 'A', 'IN')
149 response = dns.message.make_response(query)
150 rrset = dns.rrset.from_text(name,
151 60,
152 dns.rdataclass.IN,
153 dns.rdatatype.A,
154 '192.0.2.1')
155 response.answer.append(rrset)
156
157 # the round robin counter is shared for UDP and TCP,
158 # so we need to do UDP then TCP to have a clean count
159 for _ in range(numberOfQueries):
160 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
161 receivedQuery.id = query.id
162 self.assertEquals(query, receivedQuery)
163 self.assertEquals(response, receivedResponse)
164
165 for _ in range(numberOfQueries):
166 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
167 receivedQuery.id = query.id
168 self.assertEquals(query, receivedQuery)
169 self.assertEquals(response, receivedResponse)
170
171 for key in self._responsesCounter:
172 value = self._responsesCounter[key]
173 self.assertEquals(value, numberOfQueries / 2)
174
175 class TestRoutingRoundRobinLBOneDown(DNSDistTest):
176
177 _testServer2Port = 5351
178 _config_params = ['_testServerPort', '_testServer2Port']
179 _config_template = """
180 setServerPolicy(roundrobin)
181 s1 = newServer{address="127.0.0.1:%s"}
182 s1:setUp()
183 s2 = newServer{address="127.0.0.1:%s"}
184 s2:setDown()
185 """
186
187 def testRRWithOneDown(self):
188 """
189 Routing: Round Robin with one server down
190
191 Send 100 A queries to "rr.routing.tests.powerdns.com.",
192 check that dnsdist routes all of it to the only backend up.
193 """
194 numberOfQueries = 10
195 name = 'rr.routing.tests.powerdns.com.'
196 query = dns.message.make_query(name, 'A', 'IN')
197 response = dns.message.make_response(query)
198 rrset = dns.rrset.from_text(name,
199 60,
200 dns.rdataclass.IN,
201 dns.rdatatype.A,
202 '192.0.2.1')
203 response.answer.append(rrset)
204
205 # the round robin counter is shared for UDP and TCP,
206 # so we need to do UDP then TCP to have a clean count
207 for _ in range(numberOfQueries):
208 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
209 receivedQuery.id = query.id
210 self.assertEquals(query, receivedQuery)
211 self.assertEquals(response, receivedResponse)
212
213 for _ in range(numberOfQueries):
214 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
215 receivedQuery.id = query.id
216 self.assertEquals(query, receivedQuery)
217 self.assertEquals(response, receivedResponse)
218
219 total = 0
220 for key in self._responsesCounter:
221 value = self._responsesCounter[key]
222 self.assertTrue(value == numberOfQueries or value == 0)
223 total += value
224
225 self.assertEquals(total, numberOfQueries * 2)
226
227 class TestRoutingOrder(DNSDistTest):
228
229 _testServer2Port = 5351
230 _config_params = ['_testServerPort', '_testServer2Port']
231 _config_template = """
232 setServerPolicy(firstAvailable)
233 s1 = newServer{address="127.0.0.1:%s", order=2}
234 s1:setUp()
235 s2 = newServer{address="127.0.0.1:%s", order=1}
236 s2:setUp()
237 """
238
239 @classmethod
240 def startResponders(cls):
241 print("Launching responders..")
242 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
243 cls._UDPResponder.setDaemon(True)
244 cls._UDPResponder.start()
245 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
246 cls._UDPResponder2.setDaemon(True)
247 cls._UDPResponder2.start()
248
249 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
250 cls._TCPResponder.setDaemon(True)
251 cls._TCPResponder.start()
252
253 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
254 cls._TCPResponder2.setDaemon(True)
255 cls._TCPResponder2.start()
256
257 def testOrder(self):
258 """
259 Routing: firstAvailable policy based on 'order'
260
261 Send 50 A queries to "order.routing.tests.powerdns.com.",
262 check that dnsdist routes all of it to the second backend
263 because it has the lower order value.
264 """
265 numberOfQueries = 50
266 name = 'order.routing.tests.powerdns.com.'
267 query = dns.message.make_query(name, 'A', 'IN')
268 response = dns.message.make_response(query)
269 rrset = dns.rrset.from_text(name,
270 60,
271 dns.rdataclass.IN,
272 dns.rdatatype.A,
273 '192.0.2.1')
274 response.answer.append(rrset)
275
276 for _ in range(numberOfQueries):
277 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
278 receivedQuery.id = query.id
279 self.assertEquals(query, receivedQuery)
280 self.assertEquals(response, receivedResponse)
281 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
282 receivedQuery.id = query.id
283 self.assertEquals(query, receivedQuery)
284 self.assertEquals(response, receivedResponse)
285
286 total = 0
287 if 'UDP Responder' in self._responsesCounter:
288 self.assertEquals(self._responsesCounter['UDP Responder'], 0)
289 self.assertEquals(self._responsesCounter['UDP Responder 2'], numberOfQueries)
290 if 'TCP Responder' in self._responsesCounter:
291 self.assertEquals(self._responsesCounter['TCP Responder'], 0)
292 self.assertEquals(self._responsesCounter['TCP Responder 2'], numberOfQueries)
293
294 class TestRoutingNoServer(DNSDistTest):
295
296 _config_template = """
297 newServer{address="127.0.0.1:%s", pool="real"}
298 setServFailWhenNoServer(true)
299 """
300
301 def testPolicyPoolNoServer(self):
302 """
303 Routing: No server should return ServFail
304 """
305 name = 'noserver.routing.tests.powerdns.com.'
306 query = dns.message.make_query(name, 'A', 'IN')
307 expectedResponse = dns.message.make_response(query)
308 expectedResponse.set_rcode(dns.rcode.SERVFAIL)
309
310 (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
311 self.assertEquals(receivedResponse, expectedResponse)
312
313 (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
314 self.assertEquals(receivedResponse, expectedResponse)
315
316
317 class TestRoutingWRandom(DNSDistTest):
318
319 _testServer2Port = 5351
320 _config_params = ['_testServerPort', '_testServer2Port']
321 _config_template = """
322 setServerPolicy(wrandom)
323 s1 = newServer{address="127.0.0.1:%s", weight=1}
324 s1:setUp()
325 s2 = newServer{address="127.0.0.1:%s", weight=2}
326 s2:setUp()
327 """
328
329 @classmethod
330 def startResponders(cls):
331 print("Launching responders..")
332 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
333 cls._UDPResponder.setDaemon(True)
334 cls._UDPResponder.start()
335 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
336 cls._UDPResponder2.setDaemon(True)
337 cls._UDPResponder2.start()
338
339 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
340 cls._TCPResponder.setDaemon(True)
341 cls._TCPResponder.start()
342
343 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
344 cls._TCPResponder2.setDaemon(True)
345 cls._TCPResponder2.start()
346
347 def testWRandom(self):
348 """
349 Routing: WRandom
350
351 Send 100 A queries to "rr.routing.tests.powerdns.com.",
352 check that dnsdist routes less than half to one, more to the other.
353 """
354 numberOfQueries = 100
355 name = 'rr.routing.tests.powerdns.com.'
356 query = dns.message.make_query(name, 'A', 'IN')
357 response = dns.message.make_response(query)
358 rrset = dns.rrset.from_text(name,
359 60,
360 dns.rdataclass.IN,
361 dns.rdatatype.A,
362 '192.0.2.1')
363 response.answer.append(rrset)
364
365 # the counter is shared for UDP and TCP,
366 # so we need to do UDP then TCP to have a clean count
367 for _ in range(numberOfQueries):
368 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
369 receivedQuery.id = query.id
370 self.assertEquals(query, receivedQuery)
371 self.assertEquals(response, receivedResponse)
372
373 for _ in range(numberOfQueries):
374 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
375 receivedQuery.id = query.id
376 self.assertEquals(query, receivedQuery)
377 self.assertEquals(response, receivedResponse)
378
379 # The lower weight downstream should receive less than half the queries
380 self.assertTrue(self._responsesCounter['UDP Responder'] < numberOfQueries * 0.50)
381 self.assertTrue(self._responsesCounter['TCP Responder'] < numberOfQueries * 0.50)
382
383 # The higher weight downstream should receive more than half the queries
384 self.assertTrue(self._responsesCounter['UDP Responder 2'] > numberOfQueries * 0.50)
385 self.assertTrue(self._responsesCounter['TCP Responder 2'] > numberOfQueries * 0.50)
386
387
388 class TestRoutingHighValueWRandom(DNSDistTest):
389
390 _testServer2Port = 5351
391 _consoleKey = DNSDistTest.generateConsoleKey()
392 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
393 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
394 _config_template = """
395 setKey("%s")
396 controlSocket("127.0.0.1:%s")
397 setServerPolicy(wrandom)
398 s1 = newServer{address="127.0.0.1:%s", weight=2000000000}
399 s1:setUp()
400 s2 = newServer{address="127.0.0.1:%s", weight=2000000000}
401 s2:setUp()
402 """
403
404 @classmethod
405 def startResponders(cls):
406 print("Launching responders..")
407 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
408 cls._UDPResponder.setDaemon(True)
409 cls._UDPResponder.start()
410 cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
411 cls._UDPResponder2.setDaemon(True)
412 cls._UDPResponder2.start()
413
414 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
415 cls._TCPResponder.setDaemon(True)
416 cls._TCPResponder.start()
417
418 cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
419 cls._TCPResponder2.setDaemon(True)
420 cls._TCPResponder2.start()
421
422 def testHighValueWRandom(self):
423 """
424 Routing: WRandom
425
426 Send 100 A queries to "rr.routing.tests.powerdns.com.",
427 check that dnsdist routes to each downstream, rather than failing with
428 no-policy.
429 """
430 numberOfQueries = 100
431 name = 'rr.routing.tests.powerdns.com.'
432 query = dns.message.make_query(name, 'A', 'IN')
433 response = dns.message.make_response(query)
434 rrset = dns.rrset.from_text(name,
435 60,
436 dns.rdataclass.IN,
437 dns.rdatatype.A,
438 '192.0.2.1')
439 response.answer.append(rrset)
440
441 # the counter is shared for UDP and TCP,
442 # so we need to do UDP then TCP to have a clean count
443 for _ in range(numberOfQueries):
444 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
445 receivedQuery.id = query.id
446 self.assertEquals(query, receivedQuery)
447 self.assertEquals(response, receivedResponse)
448
449 for _ in range(numberOfQueries):
450 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
451 receivedQuery.id = query.id
452 self.assertEquals(query, receivedQuery)
453 self.assertEquals(response, receivedResponse)
454
455 stats = self.sendConsoleCommand("dumpStats()").split()
456 stats_dict = {}
457
458 # Map to a dict with every other element being the value to the previous one
459 for i, x in enumerate(stats):
460 if not i % 2:
461 stats_dict[x] = stats[i+1]
462
463 # There should be no queries getting "no-policy" responses
464 self.assertEquals(stats_dict['no-policy'], '0')
465
466 # Each downstream should receive some queries, but it will be unbalanced
467 # The first downstream will receive more than half the queries
468 self.assertTrue(self._responsesCounter['UDP Responder'] > numberOfQueries / 2)
469 self.assertTrue(self._responsesCounter['TCP Responder'] > numberOfQueries / 2)
470
471 # The second downstream will receive the remainder of the queries, more than 0
472 self.assertTrue(self._responsesCounter['UDP Responder 2'] > 0)
473 self.assertTrue(self._responsesCounter['TCP Responder 2'] > 0)
474 self.assertEquals(self._responsesCounter['UDP Responder 2'], numberOfQueries - self._responsesCounter['UDP Responder'])
475 self.assertEquals(self._responsesCounter['TCP Responder 2'], numberOfQueries - self._responsesCounter['TCP Responder'])
476
477
478 class TestRoutingBadWeightWRandom(DNSDistTest):
479
480 _testServer2Port = 5351
481 _consoleKey = DNSDistTest.generateConsoleKey()
482 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
483 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
484 _config_template = """
485 setKey("%s")
486 controlSocket("127.0.0.1:%s")
487 setServerPolicy(wrandom)
488 s1 = newServer{address="127.0.0.1:%s", weight=-1}
489 s2 = newServer{address="127.0.0.1:%s", weight=2147483648}
490 """
491
492 def testBadWeightWRandom(self):
493 """
494 Routing: WRandom
495
496 Test that downstreams cannot be added with invalid weights.
497 """
498 # There should be no downstreams
499 self.assertTrue(self.sendConsoleCommand("getServer(0)").startswith("Error"))