6 from dnsdisttests
import DNSDistTest
8 class TestAPIBasics(DNSDistTest
):
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")
27 def testBasicAuth(self
):
29 API: Basic Authentication
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
)
35 self
.assertEquals(r
.status_code
, 200)
37 def testXAPIKey(self
):
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
)
46 self
.assertEquals(r
.status_code
, 200)
48 def testBasicAuthOnly(self
):
50 API: Basic Authentication Only
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)
58 def testAPIKeyOnly(self
):
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)
67 def testServersLocalhost(self
):
69 API: /api/v1/servers/localhost
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
)
75 self
.assertEquals(r
.status_code
, 200)
76 self
.assertTrue(r
.json())
79 self
.assertEquals(content
['daemon_type'], 'dnsdist')
81 rule_groups
= ['response-rules', 'cache-hit-response-rules', 'self-answered-response-rules']
82 for key
in ['version', 'acl', 'local', 'rules', 'servers', 'frontends', 'pools'] + rule_groups
:
83 self
.assertIn(key
, content
)
85 for rule
in content
['rules']:
86 for key
in ['id', 'matches', 'rule', 'action', 'uuid']:
87 self
.assertIn(key
, rule
)
88 for key
in ['id', 'matches']:
89 self
.assertTrue(rule
[key
] >= 0)
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)
98 for server
in content
['servers']:
99 for key
in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit',
100 'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors',
102 self
.assertIn(key
, server
)
104 for key
in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
105 'qps', 'queries', 'order']:
106 self
.assertTrue(server
[key
] >= 0)
108 self
.assertTrue(server
['state'] in ['up', 'down', 'UP', 'DOWN'])
110 for frontend
in content
['frontends']:
111 for key
in ['id', 'address', 'udp', 'tcp', 'queries']:
112 self
.assertIn(key
, frontend
)
114 for key
in ['id', 'queries']:
115 self
.assertTrue(frontend
[key
] >= 0)
117 for pool
in content
['pools']:
118 for key
in ['id', 'name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
119 self
.assertIn(key
, pool
)
121 for key
in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']:
122 self
.assertTrue(pool
[key
] >= 0)
124 def testServersIDontExist(self
):
126 API: /api/v1/servers/idontexist (should be 404)
128 headers
= {'x-api-key': self
._webServerAPIKey
}
129 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/idontexist'
130 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
131 self
.assertEquals(r
.status_code
, 404)
133 def testServersLocalhostConfig(self
):
135 API: /api/v1/servers/localhost/config
137 headers
= {'x-api-key': self
._webServerAPIKey
}
138 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost/config'
139 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
141 self
.assertEquals(r
.status_code
, 200)
142 self
.assertTrue(r
.json())
145 for entry
in content
:
146 for key
in ['type', 'name', 'value']:
147 self
.assertIn(key
, entry
)
149 self
.assertEquals(entry
['type'], 'ConfigSetting')
150 values
[entry
['name']] = entry
['value']
152 for key
in ['acl', 'control-socket', 'ecs-override', 'ecs-source-prefix-v4',
153 'ecs-source-prefix-v6', 'fixup-case', 'max-outstanding', 'server-policy',
154 'stale-cache-entries-ttl', 'tcp-recv-timeout', 'tcp-send-timeout',
155 'truncate-tc', 'verbose', 'verbose-health-checks']:
156 self
.assertIn(key
, values
)
158 for key
in ['max-outstanding', 'stale-cache-entries-ttl', 'tcp-recv-timeout',
160 self
.assertTrue(values
[key
] >= 0)
162 self
.assertTrue(values
['ecs-source-prefix-v4'] >= 0 and values
['ecs-source-prefix-v4'] <= 32)
163 self
.assertTrue(values
['ecs-source-prefix-v6'] >= 0 and values
['ecs-source-prefix-v6'] <= 128)
165 def testServersLocalhostConfigAllowFrom(self
):
167 API: /api/v1/servers/localhost/config/allow-from
169 headers
= {'x-api-key': self
._webServerAPIKey
}
170 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost/config/allow-from'
171 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
173 self
.assertEquals(r
.status_code
, 200)
174 self
.assertTrue(r
.json())
176 for key
in ['type', 'name', 'value']:
177 self
.assertIn(key
, content
)
179 self
.assertEquals(content
['name'], 'allow-from')
180 self
.assertEquals(content
['type'], 'ConfigSetting')
181 acl
= content
['value']
182 expectedACL
= ["127.0.0.1/32", "::1/128"]
185 self
.assertEquals(acl
, expectedACL
)
187 def testServersLocalhostConfigAllowFromPut(self
):
189 API: PUT /api/v1/servers/localhost/config/allow-from (should be refused)
191 The API is read-only by default, so this should be refused
193 newACL
= ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
194 payload
= json
.dumps({"name": "allow-from",
195 "type": "ConfigSetting",
197 headers
= {'x-api-key': self
._webServerAPIKey
}
198 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost/config/allow-from'
199 r
= requests
.put(url
, headers
=headers
, timeout
=self
._webTimeout
, data
=payload
)
201 self
.assertEquals(r
.status_code
, 405)
203 def testServersLocalhostStatistics(self
):
205 API: /api/v1/servers/localhost/statistics
207 headers
= {'x-api-key': self
._webServerAPIKey
}
208 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost/statistics'
209 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
211 self
.assertEquals(r
.status_code
, 200)
212 self
.assertTrue(r
.json())
215 for entry
in content
:
216 self
.assertIn('type', entry
)
217 self
.assertIn('name', entry
)
218 self
.assertIn('value', entry
)
219 self
.assertEquals(entry
['type'], 'StatisticItem')
220 values
[entry
['name']] = entry
['value']
222 expected
= ['responses', 'servfail-responses', 'queries', 'acl-drops',
223 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
224 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
225 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
226 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
227 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
228 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
229 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
230 'dyn-block-nmg-size', 'rule-servfail']
233 self
.assertIn(key
, values
)
234 self
.assertTrue(values
[key
] >= 0)
237 self
.assertIn(key
, expected
)
239 def testJsonstatStats(self
):
241 API: /jsonstat?command=stats
243 headers
= {'x-api-key': self
._webServerAPIKey
}
244 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/jsonstat?command=stats'
245 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
247 self
.assertEquals(r
.status_code
, 200)
248 self
.assertTrue(r
.json())
251 expected
= ['responses', 'servfail-responses', 'queries', 'acl-drops',
252 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts',
253 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1',
254 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000',
255 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000',
256 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
257 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits',
258 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked',
259 'dyn-block-nmg-size', 'packetcache-hits', 'packetcache-misses', 'over-capacity-drops',
263 self
.assertIn(key
, content
)
264 self
.assertTrue(content
[key
] >= 0)
266 def testJsonstatDynblocklist(self
):
268 API: /jsonstat?command=dynblocklist
270 headers
= {'x-api-key': self
._webServerAPIKey
}
271 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/jsonstat?command=dynblocklist'
272 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
274 self
.assertEquals(r
.status_code
, 200)
279 for key
in ['reason', 'seconds', 'blocks', 'action']:
280 self
.assertIn(key
, content
)
282 for key
in ['blocks']:
283 self
.assertTrue(content
[key
] >= 0)
285 class TestAPIServerDown(DNSDistTest
):
288 _webServerPort
= 8083
289 _webServerBasicAuthPassword
= 'secret'
290 _webServerAPIKey
= 'apisecret'
291 # paths accessible using the API key
292 _config_params
= ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
293 _config_template
= """
294 setACL({"127.0.0.1/32", "::1/128"})
295 newServer{address="127.0.0.1:%s"}
296 getServer(0):setDown()
297 webserver("127.0.0.1:%s", "%s", "%s")
300 def testServerDownNoLatencyLocalhost(self
):
302 API: /api/v1/servers/localhost, no latency for a down server
304 headers
= {'x-api-key': self
._webServerAPIKey
}
305 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost'
306 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
308 self
.assertEquals(r
.status_code
, 200)
309 self
.assertTrue(r
.json())
312 self
.assertEquals(content
['servers'][0]['latency'], None)
314 class TestAPIWritable(DNSDistTest
):
317 _webServerPort
= 8083
318 _webServerBasicAuthPassword
= 'secret'
319 _webServerAPIKey
= 'apisecret'
320 _APIWriteDir
= '/tmp'
321 _config_params
= ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey', '_APIWriteDir']
322 _config_template
= """
323 setACL({"127.0.0.1/32", "::1/128"})
324 newServer{address="127.0.0.1:%s"}
325 webserver("127.0.0.1:%s", "%s", "%s")
326 setAPIWritable(true, "%s")
329 def testSetACL(self
):
333 headers
= {'x-api-key': self
._webServerAPIKey
}
334 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/api/v1/servers/localhost/config/allow-from'
335 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
337 self
.assertEquals(r
.status_code
, 200)
338 self
.assertTrue(r
.json())
340 acl
= content
['value']
341 expectedACL
= ["127.0.0.1/32", "::1/128"]
344 self
.assertEquals(acl
, expectedACL
)
346 newACL
= ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
347 payload
= json
.dumps({"name": "allow-from",
348 "type": "ConfigSetting",
350 r
= requests
.put(url
, headers
=headers
, timeout
=self
._webTimeout
, data
=payload
)
352 self
.assertEquals(r
.status_code
, 200)
353 self
.assertTrue(r
.json())
355 self
.assertEquals(content
['value'], newACL
)
357 r
= requests
.get(url
, headers
=headers
, timeout
=self
._webTimeout
)
359 self
.assertEquals(r
.status_code
, 200)
360 self
.assertTrue(r
.json())
362 self
.assertEquals(content
['value'], newACL
)
364 configFile
= self
._APIWriteDir
+ '/' + 'acl.conf'
365 self
.assertTrue(os
.path
.isfile(configFile
))
367 with
open(configFile
, 'rt') as f
:
368 fileContent
= f
.read()
370 self
.assertEquals(fileContent
, """-- Generated by the REST API, DO NOT EDIT
371 setACL({"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"})