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