]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_API.py
Merge pull request #5068 from pieterlexis/issue-1010-calculateSOASerial-uint32_t
[thirdparty/pdns.git] / regression-tests.dnsdist / test_API.py
1 #!/usr/bin/env python
2 import os.path
3
4 import json
5 import requests
6 from dnsdisttests import DNSDistTest
7
8 class TestAPIBasics(DNSDistTest):
9
10 _webTimeout = 2.0
11 _webServerPort = 8083
12 _webServerBasicAuthPassword = 'secret'
13 _webServerAPIKey = 'apisecret'
14 # paths accessible using the API key only
15 _apiOnlyPaths = ['/api/v1/servers/localhost/config', '/api/v1/servers/localhost/config/allow-from', '/api/v1/servers/localhost/statistics']
16 # paths accessible using an API key or basic auth
17 _statsPaths = [ '/jsonstat?command=stats', '/jsonstat?command=dynblocklist', '/api/v1/servers/localhost']
18 # paths accessible using basic auth only (list not exhaustive)
19 _basicOnlyPaths = ['/', '/index.html']
20 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
21 _config_template = """
22 setACL({"127.0.0.1/32", "::1/128"})
23 newServer{address="127.0.0.1:%s"}
24 webserver("127.0.0.1:%s", "%s", "%s")
25 """
26
27 def testBasicAuth(self):
28 """
29 API: Basic Authentication
30 """
31 for path in self._basicOnlyPaths + self._statsPaths:
32 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
33 r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
34 self.assertTrue(r)
35 self.assertEquals(r.status_code, 200)
36
37 def testXAPIKey(self):
38 """
39 API: X-Api-Key
40 """
41 headers = {'x-api-key': self._webServerAPIKey}
42 for path in self._apiOnlyPaths + self._statsPaths:
43 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
44 r = requests.get(url, headers=headers, timeout=self._webTimeout)
45 self.assertTrue(r)
46 self.assertEquals(r.status_code, 200)
47
48 def testBasicAuthOnly(self):
49 """
50 API: Basic Authentication Only
51 """
52 headers = {'x-api-key': self._webServerAPIKey}
53 for path in self._basicOnlyPaths:
54 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
55 r = requests.get(url, headers=headers, timeout=self._webTimeout)
56 self.assertEquals(r.status_code, 401)
57
58 def testAPIKeyOnly(self):
59 """
60 API: API Key Only
61 """
62 for path in self._apiOnlyPaths:
63 url = 'http://127.0.0.1:' + str(self._webServerPort) + path
64 r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
65 self.assertEquals(r.status_code, 401)
66
67 def testServersLocalhost(self):
68 """
69 API: /api/v1/servers/localhost
70 """
71 headers = {'x-api-key': self._webServerAPIKey}
72 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
73 r = requests.get(url, headers=headers, timeout=self._webTimeout)
74 self.assertTrue(r)
75 self.assertEquals(r.status_code, 200)
76 self.assertTrue(r.json())
77 content = r.json()
78
79 self.assertEquals(content['daemon_type'], 'dnsdist')
80
81 for key in ['version', 'acl', 'local', 'rules', 'response-rules', 'cache-hit-response-rules', 'servers', 'frontends', 'pools']:
82 self.assertIn(key, content)
83
84 for rule in content['rules']:
85 for key in ['id', 'matches', 'rule', 'action', 'uuid']:
86 self.assertIn(key, rule)
87 for key in ['id', 'matches']:
88 self.assertTrue(rule[key] >= 0)
89
90 for rule in content['response-rules']:
91 for key in ['id', 'matches', 'rule', 'action', 'uuid']:
92 self.assertIn(key, rule)
93 for key in ['id', 'matches']:
94 self.assertTrue(rule[key] >= 0)
95
96 for rule in content['cache-hit-response-rules']:
97 for key in ['id', 'matches', 'rule', 'action']:
98 self.assertIn(key, rule)
99 for key in ['id', 'matches']:
100 self.assertTrue(rule[key] >= 0)
101
102 for server in content['servers']:
103 for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
104 'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors']:
105 self.assertIn(key, server)
106
107 for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
108 'qps', 'queries', 'order']:
109 self.assertTrue(server[key] >= 0)
110
111 self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN'])
112
113 for frontend in content['frontends']:
114 for key in ['id', 'address', 'udp', 'tcp', 'queries']:
115 self.assertIn(key, frontend)
116
117 for key in ['id', 'queries']:
118 self.assertTrue(frontend[key] >= 0)
119
120 for pool in content['pools']:
121 for key in ['id', 'name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
122 self.assertIn(key, pool)
123
124 for key in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
125 self.assertTrue(pool[key] >= 0)
126
127 def testServersIDontExist(self):
128 """
129 API: /api/v1/servers/idontexist (should be 404)
130 """
131 headers = {'x-api-key': self._webServerAPIKey}
132 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/idontexist'
133 r = requests.get(url, headers=headers, timeout=self._webTimeout)
134 self.assertEquals(r.status_code, 404)
135
136 def testServersLocalhostConfig(self):
137 """
138 API: /api/v1/servers/localhost/config
139 """
140 headers = {'x-api-key': self._webServerAPIKey}
141 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config'
142 r = requests.get(url, headers=headers, timeout=self._webTimeout)
143 self.assertTrue(r)
144 self.assertEquals(r.status_code, 200)
145 self.assertTrue(r.json())
146 content = r.json()
147 values = {}
148 for entry in content:
149 for key in ['type', 'name', 'value']:
150 self.assertIn(key, entry)
151
152 self.assertEquals(entry['type'], 'ConfigSetting')
153 values[entry['name']] = entry['value']
154
155 for key in ['acl', 'control-socket', 'ecs-override', 'ecs-source-prefix-v4',
156 'ecs-source-prefix-v6', 'fixup-case', 'max-outstanding', 'server-policy',
157 'stale-cache-entries-ttl', 'tcp-recv-timeout', 'tcp-send-timeout',
158 'truncate-tc', 'verbose', 'verbose-health-checks']:
159 self.assertIn(key, values)
160
161 for key in ['max-outstanding', 'stale-cache-entries-ttl', 'tcp-recv-timeout',
162 'tcp-send-timeout']:
163 self.assertTrue(values[key] >= 0)
164
165 self.assertTrue(values['ecs-source-prefix-v4'] >= 0 and values['ecs-source-prefix-v4'] <= 32)
166 self.assertTrue(values['ecs-source-prefix-v6'] >= 0 and values['ecs-source-prefix-v6'] <= 128)
167
168 def testServersLocalhostConfigAllowFrom(self):
169 """
170 API: /api/v1/servers/localhost/config/allow-from
171 """
172 headers = {'x-api-key': self._webServerAPIKey}
173 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
174 r = requests.get(url, headers=headers, timeout=self._webTimeout)
175 self.assertTrue(r)
176 self.assertEquals(r.status_code, 200)
177 self.assertTrue(r.json())
178 content = r.json()
179 for key in ['type', 'name', 'value']:
180 self.assertIn(key, content)
181
182 self.assertEquals(content['name'], 'allow-from')
183 self.assertEquals(content['type'], 'ConfigSetting')
184 self.assertEquals(content['value'], ["127.0.0.1/32", "::1/128"])
185
186 def testServersLocalhostConfigAllowFromPut(self):
187 """
188 API: PUT /api/v1/servers/localhost/config/allow-from (should be refused)
189
190 The API is read-only by default, so this should be refused
191 """
192 newACL = ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
193 payload = json.dumps({"name": "allow-from",
194 "type": "ConfigSetting",
195 "value": newACL})
196 headers = {'x-api-key': self._webServerAPIKey}
197 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
198 r = requests.put(url, headers=headers, timeout=self._webTimeout, data=payload)
199 self.assertFalse(r)
200 self.assertEquals(r.status_code, 405)
201
202 def testServersLocalhostStatistics(self):
203 """
204 API: /api/v1/servers/localhost/statistics
205 """
206 headers = {'x-api-key': self._webServerAPIKey}
207 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/statistics'
208 r = requests.get(url, headers=headers, timeout=self._webTimeout)
209 self.assertTrue(r)
210 self.assertEquals(r.status_code, 200)
211 self.assertTrue(r.json())
212 content = r.json()
213 values = {}
214 for entry in content:
215 self.assertIn('type', entry)
216 self.assertIn('name', entry)
217 self.assertIn('value', entry)
218 self.assertEquals(entry['type'], 'StatisticItem')
219 values[entry['name']] = entry['value']
220
221 expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
222 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
223 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
224 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
225 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
226 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
227 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
228 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
229 'dyn-block-nmg-size']
230
231 for key in expected:
232 self.assertIn(key, values)
233 self.assertTrue(values[key] >= 0)
234
235 for key in values:
236 self.assertIn(key, expected)
237
238 def testJsonstatStats(self):
239 """
240 API: /jsonstat?command=stats
241 """
242 headers = {'x-api-key': self._webServerAPIKey}
243 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=stats'
244 r = requests.get(url, headers=headers, timeout=self._webTimeout)
245 self.assertTrue(r)
246 self.assertEquals(r.status_code, 200)
247 self.assertTrue(r.json())
248 content = r.json()
249
250 expected = ['responses', 'servfail-responses', 'queries', 'acl-drops',
251 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
252 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
253 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
254 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
255 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
256 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
257 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
258 'dyn-block-nmg-size', 'packetcache-hits', 'packetcache-misses', 'over-capacity-drops',
259 'too-old-drops']
260
261 for key in expected:
262 self.assertIn(key, content)
263 self.assertTrue(content[key] >= 0)
264
265 def testJsonstatDynblocklist(self):
266 """
267 API: /jsonstat?command=dynblocklist
268 """
269 headers = {'x-api-key': self._webServerAPIKey}
270 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
271 r = requests.get(url, headers=headers, timeout=self._webTimeout)
272 self.assertTrue(r)
273 self.assertEquals(r.status_code, 200)
274
275 content = r.json()
276
277 if content:
278 for key in ['reason', 'seconds', 'blocks']:
279 self.assertIn(key, content)
280
281 for key in ['blocks']:
282 self.assertTrue(content[key] >= 0)
283
284 class TestAPIServerDown(DNSDistTest):
285
286 _webTimeout = 2.0
287 _webServerPort = 8083
288 _webServerBasicAuthPassword = 'secret'
289 _webServerAPIKey = 'apisecret'
290 # paths accessible using the API key
291 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
292 _config_template = """
293 setACL({"127.0.0.1/32", "::1/128"})
294 newServer{address="127.0.0.1:%s"}
295 getServer(0):setDown()
296 webserver("127.0.0.1:%s", "%s", "%s")
297 """
298
299 def testServerDownNoLatencyLocalhost(self):
300 """
301 API: /api/v1/servers/localhost, no latency for a down server
302 """
303 headers = {'x-api-key': self._webServerAPIKey}
304 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
305 r = requests.get(url, headers=headers, timeout=self._webTimeout)
306 self.assertTrue(r)
307 self.assertEquals(r.status_code, 200)
308 self.assertTrue(r.json())
309 content = r.json()
310
311 self.assertEquals(content['servers'][0]['latency'], None)
312
313 class TestAPIWritable(DNSDistTest):
314
315 _webTimeout = 2.0
316 _webServerPort = 8083
317 _webServerBasicAuthPassword = 'secret'
318 _webServerAPIKey = 'apisecret'
319 _APIWriteDir = '/tmp'
320 _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey', '_APIWriteDir']
321 _config_template = """
322 setACL({"127.0.0.1/32", "::1/128"})
323 newServer{address="127.0.0.1:%s"}
324 webserver("127.0.0.1:%s", "%s", "%s")
325 setAPIWritable(true, "%s")
326 """
327
328 def testSetACL(self):
329 """
330 API: Set ACL
331 """
332 headers = {'x-api-key': self._webServerAPIKey}
333 url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config/allow-from'
334 r = requests.get(url, headers=headers, timeout=self._webTimeout)
335 self.assertTrue(r)
336 self.assertEquals(r.status_code, 200)
337 self.assertTrue(r.json())
338 content = r.json()
339 self.assertEquals(content['value'], ["127.0.0.1/32", "::1/128"])
340
341 newACL = ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
342 payload = json.dumps({"name": "allow-from",
343 "type": "ConfigSetting",
344 "value": newACL})
345 r = requests.put(url, headers=headers, timeout=self._webTimeout, data=payload)
346 self.assertTrue(r)
347 self.assertEquals(r.status_code, 200)
348 self.assertTrue(r.json())
349 content = r.json()
350 self.assertEquals(content['value'], newACL)
351
352 r = requests.get(url, headers=headers, timeout=self._webTimeout)
353 self.assertTrue(r)
354 self.assertEquals(r.status_code, 200)
355 self.assertTrue(r.json())
356 content = r.json()
357 self.assertEquals(content['value'], newACL)
358
359 configFile = self._APIWriteDir + '/' + 'acl.conf'
360 self.assertTrue(os.path.isfile(configFile))
361 fileContent = None
362 with file(configFile) as f:
363 fileContent = f.read()
364
365 self.assertEquals(fileContent, """-- Generated by the REST API, DO NOT EDIT
366 setACL({"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"})
367 """)