From: Remi Gacogne Date: Wed, 9 Mar 2016 07:49:00 +0000 (+0100) Subject: dnsdist: Add API regression tests X-Git-Tag: dnsdist-1.0.0-beta1~129^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F3538%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Add API regression tests So we'll notice if we break the web server or the API. This commit also cleans up the responses counter usage. Because the counters are written to by the responder threads, they are updated at the class level. We do not want to alter them at the instance level, because that would create a new variable at this level, disconnected from the other one, but we can still read them from the instance level using 'self'. --- diff --git a/regression-tests.dnsdist/requirements.txt b/regression-tests.dnsdist/requirements.txt index 5521abf7c5..2063456d4f 100644 --- a/regression-tests.dnsdist/requirements.txt +++ b/regression-tests.dnsdist/requirements.txt @@ -1,3 +1,4 @@ dnspython>=1.11 nose>=1.3.7 libnacl>=1.4.3 +requests>=2.1.0 diff --git a/regression-tests.dnsdist/test_API.py b/regression-tests.dnsdist/test_API.py new file mode 100644 index 0000000000..58163a6046 --- /dev/null +++ b/regression-tests.dnsdist/test_API.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python + +import requests +from dnsdisttests import DNSDistTest + +class TestBasics(DNSDistTest): + + _webTimeout = 2.0 + _webServerPort = 8083 + _webServerBasicAuthPassword = 'secret' + _webServerAPIKey = 'apisecret' + # paths accessible using the API key + _apiPaths = ['/api/v1/servers/localhost', '/api/v1/servers/localhost/config', '/api/v1/servers/localhost/statistics', '/jsonstat?command=stats', '/jsonstat?command=dynblocklist'] + # paths accessible using basic auth only (list not exhaustive) + _basicOnlyPaths = ['/', '/index.html'] + _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] + _config_template = """ + newServer{address="127.0.0.1:%s"} + webserver("127.0.0.1:%s", "%s", "%s") + """ + + def testBasicAuth(self): + """ + API: Basic Authentication + """ + for path in self._basicOnlyPaths + self._apiPaths: + url = 'http://127.0.0.1:' + str(self._webServerPort) + path + r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + + def testXAPIKey(self): + """ + API: X-Api-Key + """ + headers = {'x-api-key': self._webServerAPIKey} + for path in self._apiPaths: + url = 'http://127.0.0.1:' + str(self._webServerPort) + path + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + + def testBasicAuthOnly(self): + """ + API: Basic Authentication Only + """ + headers = {'x-api-key': self._webServerAPIKey} + for path in self._basicOnlyPaths: + url = 'http://127.0.0.1:' + str(self._webServerPort) + path + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertEquals(r.status_code, 401) + + def testServersLocalhost(self): + """ + API: /api/v1/servers/localhost + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + + self.assertEquals(content['daemon_type'], 'dnsdist') + + for key in ['version', 'acl', 'local', 'rules', 'servers', 'frontends']: + self.assertIn(key, content) + + for rule in content['rules']: + for key in ['id', 'matches', 'rule', 'action']: + self.assertIn(key, rule) + for key in ['id', 'matches']: + self.assertTrue(rule[key] >= 0) + + for server in content['servers']: + for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit', + 'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order']: + self.assertIn(key, server) + + for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds', + 'qps', 'queries', 'order']: + self.assertTrue(server[key] >= 0) + + self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN']) + + for frontend in content['frontends']: + for key in ['id', 'address', 'udp', 'tcp', 'queries']: + self.assertIn(key, frontend) + + for key in ['id', 'queries']: + self.assertTrue(frontend[key] >= 0) + + def testServersLocalhostConfig(self): + """ + API: /api/v1/servers/localhost/config + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/config' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + values = {} + for entry in content: + for key in ['type', 'name', 'value']: + self.assertIn(key, entry) + + self.assertEquals(entry['type'], 'ConfigSetting') + values[entry['name']] = entry['value'] + + for key in ['acl', 'control-socket', 'ecs-override', 'ecs-source-prefix-v4', + 'ecs-source-prefix-v6', 'fixup-case', 'max-outstanding', 'server-policy', + 'stale-cache-entries-ttl', 'tcp-recv-timeout', 'tcp-send-timeout', + 'truncate-tc', 'verbose', 'verbose-health-checks']: + self.assertIn(key, values) + + for key in ['max-outstanding', 'stale-cache-entries-ttl', 'tcp-recv-timeout', + 'tcp-send-timeout']: + self.assertTrue(values[key] >= 0) + + self.assertTrue(values['ecs-source-prefix-v4'] >= 0 and values['ecs-source-prefix-v4'] <= 32) + self.assertTrue(values['ecs-source-prefix-v6'] >= 0 and values['ecs-source-prefix-v6'] <= 128) + + def testServersLocalhostStatistics(self): + """ + API: /api/v1/servers/localhost/statistics + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/statistics' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + values = {} + for entry in content: + self.assertIn('type', entry) + self.assertIn('name', entry) + self.assertIn('value', entry) + self.assertEquals(entry['type'], 'StatisticItem') + values[entry['name']] = entry['value'] + + expected = ['responses', 'servfail-responses', 'queries', 'acl-drops', 'block-filter', + 'rule-drop', 'rule-nxdomain', 'self-answered', 'downstream-timeouts', + 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1', + 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000', + 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000', + 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries', + 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits', + 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked', + 'dyn-block-nmg-size'] + + for key in expected: + self.assertIn(key, values) + self.assertTrue(values[key] >= 0) + + def testJsonstatStats(self): + """ + API: /jsonstat?command=stats + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=stats' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + + for key in ['packetcache-hits', 'packetcache-misses', 'over-capacity-drops', 'too-old-drops']: + self.assertIn(key, content) + self.assertTrue(content[key] >= 0) + + expected = ['responses', 'servfail-responses', 'queries', 'acl-drops', 'block-filter', + 'rule-drop', 'rule-nxdomain', 'self-answered', 'downstream-timeouts', + 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1', + 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000', + 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000', + 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries', + 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits', + 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked', + 'dyn-block-nmg-size'] + + for key in expected: + self.assertIn(key, content) + self.assertTrue(content[key] >= 0) + + def testJsonstatDynblocklist(self): + """ + API: /jsonstat?command=dynblocklist + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEquals(r.status_code, 200) + + content = r.json() + + if content: + for key in ['reason', 'seconds', 'blocks']: + self.assertIn(key, content) + + for key in ['blocks']: + self.assertTrue(content[key] >= 0) diff --git a/regression-tests.dnsdist/test_Caching.py b/regression-tests.dnsdist/test_Caching.py index 1e1cde1be3..be6d9ed999 100644 --- a/regression-tests.dnsdist/test_Caching.py +++ b/regression-tests.dnsdist/test_Caching.py @@ -50,8 +50,8 @@ class TestCaching(DNSDistTest): self.assertEquals(receivedResponse, response) total = 0 - for key in TestCaching._responsesCounter: - total += TestCaching._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] TestCaching._responsesCounter[key] = 0 self.assertEquals(total, 1) @@ -70,8 +70,8 @@ class TestCaching(DNSDistTest): self.assertEquals(receivedResponse, response) total = 0 - for key in TestCaching._responsesCounter: - total += TestCaching._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] TestCaching._responsesCounter[key] = 0 self.assertEquals(total, 1) @@ -109,8 +109,8 @@ class TestCaching(DNSDistTest): self.assertEquals(query, receivedQuery) self.assertEquals(receivedResponse, response) - for key in TestCaching._responsesCounter: - value = TestCaching._responsesCounter[key] + for key in self._responsesCounter: + value = self._responsesCounter[key] self.assertEquals(value, numberOfQueries) def testSkipCacheViaLua(self): @@ -146,8 +146,8 @@ class TestCaching(DNSDistTest): self.assertEquals(query, receivedQuery) self.assertEquals(receivedResponse, response) - for key in TestCaching._responsesCounter: - value = TestCaching._responsesCounter[key] + for key in self._responsesCounter: + value = self._responsesCounter[key] self.assertEquals(value, numberOfQueries) def testCacheExpiration(self): @@ -201,8 +201,8 @@ class TestCaching(DNSDistTest): self.assertEquals(receivedResponse, response) total = 0 - for key in TestCaching._responsesCounter: - total += TestCaching._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -264,8 +264,8 @@ class TestCaching(DNSDistTest): self.assertEquals(receivedResponse, response) total = 0 - for key in TestCaching._responsesCounter: - total += TestCaching._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -313,8 +313,8 @@ class TestCaching(DNSDistTest): self.assertTrue(an.ttl < ttl) total = 0 - for key in TestCaching._responsesCounter: - total += TestCaching._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -406,8 +406,8 @@ class TestCachingWithExistingEDNS(DNSDistTest): misses += 1 total = 0 - for key in TestCachingWithExistingEDNS._responsesCounter: - total += TestCachingWithExistingEDNS._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -477,8 +477,8 @@ class TestCachingCacheFull(DNSDistTest): misses += 1 total = 0 - for key in TestCachingCacheFull._responsesCounter: - total += TestCachingCacheFull._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -588,8 +588,8 @@ class TestCachingStale(DNSDistTest): self.assertEquals(an.ttl, self._staleCacheTTL) total = 0 - for key in TestCachingCacheFull._responsesCounter: - total += TestCachingCacheFull._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -652,8 +652,8 @@ class TestCacheManagement(DNSDistTest): self.assertEquals(receivedResponse, response) total = 0 - for key in TestCachingCacheFull._responsesCounter: - total += TestCachingCacheFull._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -734,8 +734,8 @@ class TestCacheManagement(DNSDistTest): self.assertEquals(receivedResponse, response2) total = 0 - for key in TestCachingCacheFull._responsesCounter: - total += TestCachingCacheFull._responsesCounter[key] + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) @@ -815,7 +815,6 @@ class TestCacheManagement(DNSDistTest): self.assertEquals(receivedResponse, response2) total = 0 - for key in TestCachingCacheFull._responsesCounter: - total += TestCachingCacheFull._responsesCounter[key] - + for key in self._responsesCounter: + total += self._responsesCounter[key] self.assertEquals(total, misses) diff --git a/regression-tests.dnsdist/test_Routing.py b/regression-tests.dnsdist/test_Routing.py index 2ccaa264bd..f51be3470c 100644 --- a/regression-tests.dnsdist/test_Routing.py +++ b/regression-tests.dnsdist/test_Routing.py @@ -241,8 +241,8 @@ class TestRoutingRoundRobinLB(DNSDistTest): self.assertEquals(query, receivedQuery) self.assertEquals(response, receivedResponse) - for key in TestRoutingRoundRobinLB._responsesCounter: - value = TestRoutingRoundRobinLB._responsesCounter[key] + for key in self._responsesCounter: + value = self._responsesCounter[key] self.assertEquals(value, numberOfQueries / 2) class TestRoutingRoundRobinLBOneDown(DNSDistTest): @@ -290,8 +290,8 @@ class TestRoutingRoundRobinLBOneDown(DNSDistTest): self.assertEquals(response, receivedResponse) total = 0 - for key in TestRoutingRoundRobinLBOneDown._responsesCounter: - value = TestRoutingRoundRobinLBOneDown._responsesCounter[key] + for key in self._responsesCounter: + value = self._responsesCounter[key] self.assertTrue(value == numberOfQueries or value == 0) total += value