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