diff --git a/src/dns_exporter/collector.py b/src/dns_exporter/collector.py index 39a9cc6..54ae7d2 100644 --- a/src/dns_exporter/collector.py +++ b/src/dns_exporter/collector.py @@ -128,12 +128,13 @@ def collect_dns(self) -> Iterator[CounterMetricFamily | GaugeMetricFamily]: except dns.exception.Timeout: # configured timeout was reached before a response arrived reason = "timeout" - yield from self.yield_failure_reason_metric(failure_reason=reason) + yield from self.yield_failure_reason_metric(failure_reason=reason, config=self.config) except ConnectionRefusedError: # server actively refused the connection reason = "connection_error" yield from self.yield_failure_reason_metric( failure_reason=reason, + config=self.config, ) except OSError as e: # raised by multiple protocols on ICMP unreach @@ -141,6 +142,7 @@ def collect_dns(self) -> Iterator[CounterMetricFamily | GaugeMetricFamily]: reason = "connection_error" yield from self.yield_failure_reason_metric( failure_reason=reason, + config=self.config, ) except ProtocolSpecificError as e: # a protocol specific exception was raised, log and re-raise @@ -149,7 +151,7 @@ def collect_dns(self) -> Iterator[CounterMetricFamily | GaugeMetricFamily]: exc_info=True, ) reason = str(e) - yield from self.yield_failure_reason_metric(failure_reason=reason) + yield from self.yield_failure_reason_metric(failure_reason=reason, config=self.config) except Exception: # noqa: BLE001 logger.warning( f"""Caught an unknown exception while looking up qname {self.config.query_name} using server @@ -158,14 +160,16 @@ def collect_dns(self) -> Iterator[CounterMetricFamily | GaugeMetricFamily]: exc_info=True, ) reason = "other_failure" - yield from self.yield_failure_reason_metric(failure_reason=reason) + yield from self.yield_failure_reason_metric(failure_reason=reason, config=self.config) # clock it qtime = time.time() - start # did we get a response? if r is None: - logger.info(f"No DNS response received from server {self.config.server.geturl()} :( returning metrics, failure reason is '{reason}'...") + logger.warning( + f"No DNS response received from server {self.config.server.geturl()} - failure reason is '{reason}'..." + ) yield from (get_dns_qtime_metric(), get_dns_ttl_metric(), get_dns_success_metric(value=0)) return None @@ -215,7 +219,7 @@ def handle_response( yield get_dns_success_metric(1) except ValidationError as E: logger.exception(f"Validation failed: {E.args[1]}") - yield from self.yield_failure_reason_metric(failure_reason=E.args[1]) + yield from self.yield_failure_reason_metric(failure_reason=E.args[1], config=self.config) yield get_dns_success_metric(0) def handle_response_options(self, response: Message) -> None: @@ -614,17 +618,28 @@ def validate_response(self, response: Message) -> None: @staticmethod def yield_failure_reason_metric( failure_reason: str, + config: Config | None = None, ) -> Iterator[CounterMetricFamily]: """This method is used to maintain failure metrics. If an empty string is passed as failure_reason (meaning success) the failure counters will not be incremented. """ + # get server and proxy (if any) + if config: + server = config.server.geturl() if config.server else "none" + proxy = config.proxy.geturl() if config.proxy else "none" + else: + server = "none" + proxy = "none" + + # was there a failure? if failure_reason: + # is it a valid failure reason? if failure_reason not in FAILURE_REASONS: # unknown failure_reason, this is a bug raise UnknownFailureReasonError(failure_reason) # increase the global failure counter - dnsexp_scrape_failures_total.labels(reason=failure_reason).inc() + dnsexp_scrape_failures_total.labels(reason=failure_reason, server=server, proxy=proxy).inc() # get the failure metric fail = get_dns_failure_metric() # initialise all labels in the per-scrape metric, @@ -632,9 +647,9 @@ def yield_failure_reason_metric( for reason in FAILURE_REASONS: # set counter to 1 on match (custom collector - the metrics only exist during the scrape) if reason == failure_reason: - fail.add_metric([reason], 1) + fail.add_metric([reason, server, proxy], 1) else: - fail.add_metric([reason], 0) + fail.add_metric([reason, server, proxy], 0) yield fail diff --git a/src/dns_exporter/config.py b/src/dns_exporter/config.py index 6d6e302..c690920 100644 --- a/src/dns_exporter/config.py +++ b/src/dns_exporter/config.py @@ -28,6 +28,16 @@ # get logger logger = logging.getLogger(f"dns_exporter.{__name__}") +# the currently supported protocols in dns_exporter +valid_protocols = [ + "udp", + "tcp", + "udptcp", + "dot", + "doh", + "doq", +] + @dataclass class RRValidator: @@ -283,14 +293,6 @@ def validate_integers(self) -> None: def validate_protocol(self) -> None: """Validate protocol.""" - valid_protocols = [ - "udp", - "tcp", - "udptcp", - "dot", - "doh", - "doq", - ] if self.protocol not in valid_protocols: raise ConfigError( "invalid_request_protocol", diff --git a/src/dns_exporter/entrypoint.py b/src/dns_exporter/entrypoint.py index 83c85e2..6f4ce3a 100644 --- a/src/dns_exporter/entrypoint.py +++ b/src/dns_exporter/entrypoint.py @@ -122,8 +122,9 @@ def main(mockargs: list[str] | None = None) -> None: rootlogger = logging.getLogger("") rootlogger.setLevel(level) # httpx is noisy at INFO - if logger.isEnabledFor(logging.INFO): - logging.getLogger('httpx').setLevel(logging.WARNING) + if level == logging.INFO: + # httpx is noisy at level info, cap to WARNING + logging.getLogger("httpx").setLevel(logging.WARNING) logger.info( f"dns_exporter v{DNSExporter.__version__} starting up - logging at level {level}", ) diff --git a/src/dns_exporter/exporter.py b/src/dns_exporter/exporter.py index 35e3ecb..5a009f8 100644 --- a/src/dns_exporter/exporter.py +++ b/src/dns_exporter/exporter.py @@ -467,16 +467,16 @@ def parse_server(server: str, protocol: str) -> urllib.parse.SplitResult: Parse it with urllib.parse.urlsplit, add explicit port if needed, and return the result. """ + # make sure we always have a scheme to help the parser if "://" not in server: server = f"{protocol}://{server}" + # parse the string splitresult = urllib.parse.urlsplit(server) + # make sure scheme is the dns_exporter internal protocol identifier (not https://) + splitresult = splitresult._replace(scheme=protocol) if protocol == "doh" and not splitresult.path: # use the default DoH path - splitresult = urllib.parse.urlsplit( - urllib.parse.urlunsplit( - splitresult._replace(path="/dns-query", scheme="https"), - ), - ) + splitresult = splitresult._replace(path="/dns-query") # is there an explicit port in the configured server url? use default if not. if splitresult.port is None: if protocol in ["udp", "tcp", "udptcp"]: diff --git a/src/dns_exporter/metrics.py b/src/dns_exporter/metrics.py index ad85d6f..a74b208 100644 --- a/src/dns_exporter/metrics.py +++ b/src/dns_exporter/metrics.py @@ -164,13 +164,16 @@ def get_dns_failure_metric() -> CounterMetricFamily: A scrape (or the resulting DNS query) can fail for many reasons, including configuration issues, server issues, timeout, network issues, bad response, or failed response validation. - This metric has just one label: + This metric has three labels: - ``reason``: The reason for the failure. + - ``server`` is set to the server URL. + - ``proxy`` is set to the proxy URL (or ``none``). + """ return CounterMetricFamily( name="dnsexp_failures_total", documentation="The total number of scrape failures by failure reason. This counter is increased every time a scrape is initiated and a valid response (considering validation rules) is not received.", # noqa: E501 - labels=["reason"], + labels=["reason", "server", "proxy"], ) @@ -283,10 +286,13 @@ def get_dns_failure_metric() -> CounterMetricFamily: dnsexp_scrape_failures_total = Counter( name="dnsexp_scrape_failures_total", documentation="The total number of scrapes failed by failure reason. This counter is increased every time the dns_exporter receives a scrape request which fails for some reason, including response validation logic.", # noqa: E501 - labelnames=["reason"], + labelnames=["reason", "server", "proxy"], ) """``dnsexp_scrape_failures_total`` is the Counter keeping track of how many scrape requests failed for some reason. -This metric has one label: +This metric has three labels: - ``reason`` is set to the failure reason. + - ``server`` is set to the server URL. + - ``proxy`` is set to the proxy URL (or ``none``). + """ diff --git a/src/tests/test_certificate.py b/src/tests/test_certificate.py index 94a8864..3077380 100644 --- a/src/tests/test_certificate.py +++ b/src/tests/test_certificate.py @@ -19,7 +19,10 @@ def test_cert_verify_fail_doh(dns_exporter_example_config, caplog): "family": "ipv4", }, ) - assert 'dnsexp_failures_total{reason="certificate_error"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="certificate_error",server="doh://91.239.100.100:443/dns-query"} 1.0' + in r.text + ) def test_cert_verify_fail_dot(dns_exporter_example_config, caplog): @@ -35,7 +38,9 @@ def test_cert_verify_fail_dot(dns_exporter_example_config, caplog): "family": "ipv4", }, ) - assert 'dnsexp_failures_total{reason="certificate_error"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="certificate_error",server="dot://91.239.100.100:853"} 1.0' in r.text + ) # this fails because the adguard servers have IP:.... SAN entries in the certificates @@ -54,7 +59,9 @@ def test_cert_verify_fail_doq(dns_exporter_example_config, caplog): "family": "ipv4", }, ) - assert 'dnsexp_failures_total{reason="certificate_error"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="certificate_error",server="doq://94.140.14.140:853"} 1.0' in r.text + ) ################################################################################### @@ -75,7 +82,10 @@ def test_cert_verify_fail_custom_ca_doh(dns_exporter_example_config, caplog): "verify_certificate_path": "tests/certificates/test.crt", }, ) - assert 'dnsexp_failures_total{reason="certificate_error"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="certificate_error",server="doh://91.239.100.100:443/dns-query"} 1.0' + in r.text + ) def test_cert_verify_fail_custom_ca_dot(dns_exporter_example_config, caplog): @@ -92,7 +102,9 @@ def test_cert_verify_fail_custom_ca_dot(dns_exporter_example_config, caplog): "verify_certificate_path": "tests/certificates/test.crt", }, ) - assert 'dnsexp_failures_total{reason="certificate_error"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="certificate_error",server="dot://91.239.100.100:853"} 1.0' in r.text + ) assert "Protocol dot raised ssl.SSLCertVerificationError, returning certificate_error" in caplog.text @@ -112,7 +124,7 @@ def test_cert_verify_fail_custom_ca_doq(dns_exporter_example_config, caplog): }, ) assert "Custom CA path for DoQ is disabled pending https://github.com/tykling/dns_exporter/issues/95" in caplog.text - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="none"} 1.0' in r.text ################################################################################### @@ -188,7 +200,10 @@ def test_cert_verify_invalid_path_doh(dns_exporter_example_config, caplog): "verify_certificate_path": "/nonexistant", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="doh://91.239.100.100:443/dns-query"} 1.0' + in r.text + ) assert "Protocol doh raised exception, returning failure reason invalid_request_config" in caplog.text @@ -206,7 +221,10 @@ def test_cert_verify_invalid_path_dot(dns_exporter_example_config, caplog): "verify_certificate_path": "/nonexistant", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="dot://91.239.100.100:853"} 1.0' + in r.text + ) assert "Protocol dot raised ValueError, is verify_certificate_path wrong" in caplog.text @@ -225,5 +243,5 @@ def test_cert_verify_invalid_path_doq(dns_exporter_example_config, caplog): "verify_certificate_path": "/nonexistant", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="none"} 1.0' in r.text assert "Custom CA path for DoQ is disabled pending https://github.com/tykling/dns_exporter/issues/95" in caplog.text diff --git a/src/tests/test_exporter.py b/src/tests/test_exporter.py index 338c511..3f986b4 100644 --- a/src/tests/test_exporter.py +++ b/src/tests/test_exporter.py @@ -74,7 +74,7 @@ def test_config_endpoint_2(dns_exporter_example_config): ) config = r.json() assert config["protocol"] == "doh" - assert config["server"] == "https://1dot1dot1dot1.cloudflare-dns.com:443/dns-query" + assert config["server"] == "doh://1dot1dot1dot1.cloudflare-dns.com:443/dns-query" assert config["query_name"] == "bornhack.dk" assert config["query_type"] == "NS" assert config["validate_response_flags"]["fail_if_any_absent"] == ["AD"] @@ -90,7 +90,7 @@ def test_invalid_qs_ip(dns_exporter_example_config): "ip": "notanip", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_ip"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_ip",server="none"} 1.0' in r.text def test_invalid_configfile_ip(caplog, exporter): @@ -108,7 +108,7 @@ def test_missing_query_name(dns_exporter_example_config): "server": "dns.google", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_query_name"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_query_name",server="none"} 1.0' in r.text def test_missing_server(dns_exporter_example_config): @@ -119,7 +119,7 @@ def test_missing_server(dns_exporter_example_config): "query_name": "example.com", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_server"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_server",server="none"} 1.0' in r.text def test_undefined_module(dns_exporter_example_config, caplog): @@ -130,7 +130,7 @@ def test_undefined_module(dns_exporter_example_config, caplog): "module": "notamodule", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_module"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_module",server="none"} 1.0' in r.text def test_unknown_config_key(dns_exporter_example_config, caplog): @@ -142,7 +142,7 @@ def test_unknown_config_key(dns_exporter_example_config, caplog): "foo": "bar", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="none"} 1.0' in r.text def test_ip_family_conflict(dns_exporter_example_config, caplog): @@ -156,7 +156,7 @@ def test_ip_family_conflict(dns_exporter_example_config, caplog): "ip": "192.0.2.53", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_ip"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_ip",server="none"} 1.0' in r.text def test_ip_conflict(dns_exporter_example_config, caplog): @@ -170,7 +170,7 @@ def test_ip_conflict(dns_exporter_example_config, caplog): "family": "ipv4", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_ip"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_ip",server="none"} 1.0' in r.text def test_ip_and_hostname(dns_exporter_example_config, caplog): @@ -200,7 +200,7 @@ def test_unresolvable_server(dns_exporter_example_config, caplog): "query_name": "example.com", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_server"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_server",server="none"} 1.0' in r.text def test_ipv6_family(dns_exporter_example_config, caplog): @@ -231,7 +231,7 @@ def test_ipv7_family(dns_exporter_example_config, caplog): "family": "ipv7", }, ) - assert 'dnsexp_failures_total{reason="invalid_request_family"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_family",server="none"} 1.0' in r.text # run this test last @@ -258,19 +258,26 @@ def test_internal_metrics(dns_exporter_example_config, caplog): dnsexp_http_responses_total{path="/",response_code="200"} 1.0 dnsexp_dns_queries_total 59.0 dnsexp_dns_responsetime_seconds_bucket{additional="0",answer="1",authority="0",family="ipv4",flags="QR RA RD",ip="8.8.4.4",le="2.0",nsid="no_nsid",opcode="QUERY",port="53",protocol="udp",proxy="none",query_name="example.com",query_type="A",rcode="NOERROR",server="udp://dns.google:53",transport="UDP"} -dnsexp_scrape_failures_total{reason="certificate_error"} 4.0 -dnsexp_scrape_failures_total{reason="invalid_request_config"} 7.0 -dnsexp_scrape_failures_total{reason="invalid_request_proxy"} 2.0 -dnsexp_scrape_failures_total{reason="invalid_response_answer_rrs"} 4.0 -dnsexp_scrape_failures_total{reason="invalid_request_ip"} 3.0 -dnsexp_scrape_failures_total{reason="invalid_response_rcode"} 1.0 -dnsexp_scrape_failures_total{reason="invalid_request_server"} 2.0 -dnsexp_scrape_failures_total{reason="invalid_response_flags"} 6.0 -dnsexp_scrape_failures_total{reason="invalid_request_query_name"} 1.0 -dnsexp_scrape_failures_total{reason="other_failure"} 1.0 -dnsexp_scrape_failures_total{reason="invalid_response_additional_rrs"} 1.0 -dnsexp_scrape_failures_total{reason="invalid_request_family"} 1.0 -dnsexp_scrape_failures_total{reason="invalid_request_module"} 1.0""".split("\n"): +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_server",server="none"} 2.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://l.root-servers.net:53"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 5.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_query_name",server="none"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_ip",server="none"} 3.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://dns.google:53"} 3.0 +dnsexp_scrape_failures_total{proxy="none",reason="other_failure",server="doh://dns.google:443/dns-query"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_additional_rrs",server="udp://k.root-servers.net:53"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.quad9.net:53"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_module",server="none"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_config",server="none"} 4.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_response_rcode",server="udp://dns.google:53"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_family",server="none"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_proxy",server="none"} 2.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_config",server="doh://91.239.100.100:443/dns-query"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="certificate_error",server="doh://91.239.100.100:443/dns-query"} 2.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_config",server="dot://91.239.100.100:853"} 1.0 +dnsexp_scrape_failures_total{proxy="none",reason="certificate_error",server="dot://91.239.100.100:853"} 2.0 +dnsexp_scrape_failures_total{proxy="none",reason="invalid_request_config",server="doq://94.140.14.140:853"} 1.0 +""".split("\n"): assert metric in r.text, f"expected metrics not found: {r.text}" @@ -395,7 +402,9 @@ def test_validate_rcode(dns_exporter_example_config, caplog): "family": "ipv4", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_rcode"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_rcode",server="udp://dns.google:53"} 1.0' in r.text + ) assert 'rcode="NXDOMAIN"' in r.text @@ -410,7 +419,9 @@ def test_validate_flags_fail_if_any_absent(dns_exporter_example_config, caplog): "module": "has_ad", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 1.0' in r.text + ) def test_validate_flags_fail_if_any_present(dns_exporter_example_config, caplog): @@ -424,7 +435,9 @@ def test_validate_flags_fail_if_any_present(dns_exporter_example_config, caplog) "module": "has_no_ad", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 1.0' in r.text + ) def test_validate_flags_fail_if_all_present(dns_exporter_example_config, caplog): @@ -438,7 +451,9 @@ def test_validate_flags_fail_if_all_present(dns_exporter_example_config, caplog) "module": "fail_not_auth", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 1.0' in r.text + ) def test_validate_flags_fail_if_all_absent(dns_exporter_example_config, caplog): @@ -452,7 +467,9 @@ def test_validate_flags_fail_if_all_absent(dns_exporter_example_config, caplog): "module": "fail_recursive", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 1.0' in r.text + ) def test_validate_flags_fail_if_all_present_2(dns_exporter_example_config, caplog): @@ -466,7 +483,10 @@ def test_validate_flags_fail_if_all_present_2(dns_exporter_example_config, caplo "module": "fail_not_auth", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.quad9.net:53"} 1.0' + in r.text + ) def test_validate_flags_fail_if_all_absent_2(dns_exporter_example_config, caplog): @@ -480,7 +500,9 @@ def test_validate_flags_fail_if_all_absent_2(dns_exporter_example_config, caplog "module": "fail_recursive", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_flags"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_flags",server="udp://dns.google:53"} 1.0' in r.text + ) def test_validate_flags_fail_if_all_present_3(dns_exporter_example_config, caplog): @@ -524,7 +546,10 @@ def test_validate_rr_fail_if_matches_regexp(dns_exporter_example_config, caplog) "module": "fail_auth_k_root", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_answer_rrs"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://l.root-servers.net:53"} 1.0' + in r.text + ) def test_validate_rrs_fail_if_all_match_regexp(dns_exporter_example_config, caplog): @@ -539,7 +564,10 @@ def test_validate_rrs_fail_if_all_match_regexp(dns_exporter_example_config, capl "module": "fail_additional_root", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_additional_rrs"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_additional_rrs",server="udp://k.root-servers.net:53"} 1.0' + in r.text + ) def test_validate_rrs_fail_if_all_match_regexp_2(dns_exporter_example_config, caplog): @@ -569,7 +597,10 @@ def test_validate_rrs_fail_if_not_matches_regexp(dns_exporter_example_config, ca "module": "fail_answer_root", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_answer_rrs"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://dns.google:53"} 1.0' + in r.text + ) def test_validate_rrs_fail_if_none_matches_regexp(dns_exporter_example_config, caplog): @@ -584,7 +615,10 @@ def test_validate_rrs_fail_if_none_matches_regexp(dns_exporter_example_config, c "module": "fail_answer_root_none", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_answer_rrs"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://dns.google:53"} 1.0' + in r.text + ) def test_validate_rrs_fail_if_none_matches_regexp_2( @@ -602,7 +636,10 @@ def test_validate_rrs_fail_if_none_matches_regexp_2( "module": "fail_answer_root_none", }, ) - assert 'dnsexp_failures_total{reason="invalid_response_answer_rrs"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="invalid_response_answer_rrs",server="udp://dns.google:53"} 1.0' + in r.text + ) def test_edns_pad(dns_exporter_example_config, caplog): @@ -692,7 +729,7 @@ def test_invalid_integer(dns_exporter_example_config, caplog): ) assert "Unable to validate integer for key edns_bufsize" in caplog.text assert "ValueError: invalid literal for int() with base 10: 'foo'" in caplog.text - assert 'dnsexp_failures_total{reason="invalid_request_config"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_config",server="none"} 1.0' in r.text def test_configure_rrvalidator(caplog, exporter): @@ -751,7 +788,10 @@ def test_catch_unknown_exception( "protocol": "doh", }, ) - assert 'dnsexp_failures_total{reason="other_failure"} 1.0' in r.text + assert ( + 'dnsexp_failures_total{proxy="none",reason="other_failure",server="doh://dns.google:443/dns-query"} 1.0' + in r.text + ) ### ttl tests @@ -797,15 +837,19 @@ def test_connection_error_server(dns_exporter_example_config, caplog, protocol): r = requests.get( "http://127.0.0.1:25353/query", params={ - "server": "192.0.2.0:420", + "server": "192.0.2.42:420", "protocol": protocol, "query_name": "example.org", + "timeout": 1, }, ) # this is handled a bit differently depending on the ICMP error (if any) received from the network + server = f"{protocol}://192.0.2.42:420" + if protocol == "doh": + server = f"{server}/dns-query" if ( - 'dnsexp_failures_total{reason="connection_error"} 1.0' not in r.text - and 'dnsexp_failures_total{reason="timeout"} 1.0' not in r.text + 'dnsexp_failures_total{proxy="none",reason="connection_error",server="%s"} 1.0' % server not in r.text + and 'dnsexp_failures_total{proxy="none",reason="timeout",server="%s"} 1.0' % server not in r.text ): raise AssertionError(protocol) @@ -824,11 +868,14 @@ def test_timeout_server(dns_exporter_example_config, caplog, protocol): "timeout": 0.00001, }, ) + server = f"{protocol}://192.0.2.42:420" + if protocol == "doh": + server = f"{server}/dns-query" if protocol == "doh": # doh raises httpx.ConnectError - assert 'dnsexp_failures_total{reason="connection_error"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="connection_error",server="%s"} 1.0' % server in r.text else: - assert 'dnsexp_failures_total{reason="timeout"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="timeout",server="%s"} 1.0' % server in r.text def test_collect_ttl_value_length(dns_exporter_example_config, caplog): diff --git a/src/tests/test_proxy.py b/src/tests/test_proxy.py index 641e90b..71ee07d 100644 --- a/src/tests/test_proxy.py +++ b/src/tests/test_proxy.py @@ -52,7 +52,7 @@ def test_proxy_doh(dns_exporter_example_config, proxy_server): }, ) assert 'proxy="socks5://127.0.0.1:1080"' in r.text - assert 'server="https://dns.google:443/dns-query"' in r.text + assert 'server="doh://dns.google:443/dns-query"' in r.text ################################################################################### @@ -71,7 +71,11 @@ def test_proxy_fail(dns_exporter_example_config, proxy_server, protocol): "proxy": "socks5://127.0.0.1:1081", }, ) - assert 'dnsexp_failures_total{reason="connection_error"} 1.0' in r.text + server = "doh://dns.google:443/dns-query" if protocol == "doh" else f"{protocol}://dns.google:53" + assert ( + 'dnsexp_failures_total{proxy="socks5://127.0.0.1:1081",reason="connection_error",server="%s"} 1.0' % server + in r.text + ) ################################################################################### @@ -88,7 +92,7 @@ def test_proxy_without_scheme(dns_exporter_example_config): }, ) assert r.status_code == 200, "non-200 returncode" - assert 'dnsexp_failures_total{reason="invalid_request_proxy"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_proxy",server="none"} 1.0' in r.text def test_proxy_unknown_scheme(dns_exporter_example_config): @@ -102,7 +106,7 @@ def test_proxy_unknown_scheme(dns_exporter_example_config): }, ) assert r.status_code == 200, "non-200 returncode" - assert 'dnsexp_failures_total{reason="invalid_request_proxy"} 1.0' in r.text + assert 'dnsexp_failures_total{proxy="none",reason="invalid_request_proxy",server="none"} 1.0' in r.text def test_exporter_modules_none(caplog, exporter):