6 from dnsdisttests
import DNSDistTest
, pickAvailablePort
8 @unittest.skipIf('SKIP_PROMETHEUS_TESTS' in os
.environ
, 'Prometheus tests are disabled')
9 class TestPrometheus(DNSDistTest
):
12 _webServerPort
= pickAvailablePort()
13 _webServerBasicAuthPassword
= 'secret'
14 _webServerBasicAuthPasswordHashed
= '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
15 _webServerAPIKey
= 'apisecret'
16 _webServerAPIKeyHashed
= '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
17 _config_params
= ['_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
18 _config_template
= """
19 newServer{address="127.0.0.1:%s"}
20 webserver("127.0.0.1:%s")
21 setWebserverConfig({password="%s", apiKey="%s"})
22 pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
23 getPool(""):setCache(pc)
25 -- test custom metrics as well
26 declareMetric('custom-metric1', 'counter', 'Custom counter')
27 incMetric('custom-metric1')
28 declareMetric('custom-metric2', 'gauge', 'Custom gauge')
30 declareMetric('custom-metric3', 'counter', 'Custom counter', 'custom_prometheus_name')
33 def checkPrometheusContentBasic(self
, content
):
34 for line
in content
.splitlines():
35 if line
.startswith('# HELP'):
36 tokens
= line
.split(' ')
37 self
.assertGreaterEqual(len(tokens
), 4)
38 elif line
.startswith('# TYPE'):
39 tokens
= line
.split(' ')
40 self
.assertEqual(len(tokens
), 4)
41 self
.assertIn(tokens
[3], ['counter', 'gauge', 'histogram'])
42 elif not line
.startswith('#'):
43 tokens
= line
.split(' ')
44 self
.assertEqual(len(tokens
), 2)
45 if not line
.startswith('dnsdist_') and not line
.startswith('custom_prometheus_name'):
46 raise AssertionError('Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line
))
48 def checkMetric(self
, content
, name
, expectedType
, expectedValue
):
52 for line
in content
.splitlines():
54 tokens
= line
.split(' ')
55 if line
.startswith('# HELP'):
56 self
.assertGreaterEqual(len(tokens
), 4)
59 elif line
.startswith('# TYPE'):
60 self
.assertEqual(len(tokens
), 4)
63 self
.assertEqual(tokens
[3], expectedType
)
64 elif not line
.startswith('#'):
65 self
.assertEqual(len(tokens
), 2)
68 self
.assertEqual(int(tokens
[1]), expectedValue
)
70 self
.assertTrue(typeFound
)
71 self
.assertTrue(helpFound
)
72 self
.assertTrue(valueFound
)
74 def checkPrometheusContentPromtool(self
, content
):
77 testcmd
= ['promtool', 'check', 'metrics']
78 process
= subprocess
.Popen(testcmd
, stdout
=subprocess
.PIPE
, stdin
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, close_fds
=True)
79 output
= process
.communicate(input=content
)
80 except subprocess
.CalledProcessError
as exc
:
81 raise AssertionError('%s failed (%d): %s' % (testcmd
, process
.returncode
, process
.output
))
83 # commented out because promtool returns 3 because of the "_total" suffix warnings
84 #if process.returncode != 0:
85 # raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, output))
87 for line
in output
[0].splitlines():
88 if line
.endswith(b
"should have \"_total\" suffix"):
90 raise AssertionError('%s returned an unexpected output. Faulty line is "%s", complete content is "%s"' % (testcmd
, line
, output
))
92 def testMetrics(self
):
94 Prometheus: Retrieve metrics
96 url
= 'http://127.0.0.1:' + str(self
._webServerPort
) + '/metrics'
97 r
= requests
.get(url
, auth
=('whatever', self
._webServerBasicAuthPassword
), timeout
=self
._webTimeout
)
99 self
.assertEqual(r
.status_code
, 200)
100 self
.checkPrometheusContentBasic(r
.text
)
101 self
.checkPrometheusContentPromtool(r
.content
)
102 self
.checkMetric(r
.text
, 'dnsdist_custom_metric1', 'counter', 1)
103 self
.checkMetric(r
.text
, 'dnsdist_custom_metric2', 'gauge', 0)
104 self
.checkMetric(r
.text
, 'custom_prometheus_name', 'counter', 0)