diff --git a/tests-extra/tests/modules/stats/test.py b/tests-extra/tests/modules/stats/test.py new file mode 100644 index 0000000000000000000000000000000000000000..23c0e6f987543094fdd94cad7ed109d330b07ff1 --- /dev/null +++ b/tests-extra/tests/modules/stats/test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 + +''' Check 'stats' query module functionality. ''' + +import os +import random + +from dnstest.libknot import libknot +from dnstest.module import ModStats +from dnstest.test import Test +from dnstest.utils import * + +def check_item(server, section, item, value, idx=None, zone=None): + try: + ctl = libknot.control.KnotCtl() + ctl.connect(os.path.join(server.dir, "knot.sock")) + + if zone: + ctl.send_block(cmd="zone-stats", section=section, item=item, zone=zone.name) + else: + ctl.send_block(cmd="stats", section=section, item=item) + + stats = ctl.receive_stats() + finally: + ctl.send(libknot.control.KnotCtlType.END) + ctl.close() + + if not stats and value == -1: + return + + if zone: + stats = stats.get("zone").get(zone.name.lower()) + + if idx: + if value == -1: + isset(idx not in stats.get(section).get(item), idx) + return + else: + data = int(stats.get(section).get(item).get(idx)) + else: + data = int(stats.get(section).get(item)) + + compare(data, value, "%s.%s" % (section, item)) + +ModStats.check() + +proto = random.choice([4, 6]) + +t = Test(stress=False, tsig=False, address=proto) + +knot = t.server("knot") +zones = t.zone_rnd(2) + +t.link(zones, knot) + +knot.add_module(None, ModStats()) +knot.add_module(zones[0], ModStats()) +knot.add_module(zones[1], ModStats()) + +t.start() +t.sleep(1) + +check_item(knot, "server", "zone-count", 2) + +resp = knot.dig(zones[0].name, "SOA", tries=1, udp=True) +query_size1 = resp.query_size() +reply_size1 = resp.response_size() + +resp = knot.dig(zones[0].name, "NS", tries=1, udp=False) +query_size2 = resp.query_size() +reply_size2 = resp.response_size() + +resp = knot.dig(zones[1].name, "TYPE11", tries=1, udp=True) +query_size3 = resp.query_size() +reply_size3 = resp.response_size() + +# Sucessfull transfer. +resp = knot.dig(zones[0].name, "AXFR", tries=1) +resp.check_xfr(rcode="NOERROR") +xfr_query_size = resp.query_size() +# Cannot get xfr_reply_size :-/ + +# Successfull update. +up = knot.update(zones[1]) +up.add(zones[1].name, "3600", "AAAA", "::1") +up.send("NOERROR") +ddns_query_size = up.query_size() +# Due to DDNS bulk processing, failed RCODE and response-bytes are not incremented! + +# Check request protocol metrics. +check_item(knot, "mod-stats", "request-protocol", 2, "udp%s" % proto) +check_item(knot, "mod-stats", "request-protocol", 1, "udp%s" % proto, zone=zones[0]) +check_item(knot, "mod-stats", "request-protocol", 1, "udp%s" % proto, zone=zones[1]) + +check_item(knot, "mod-stats", "request-protocol", 3, "tcp%s" % proto) +check_item(knot, "mod-stats", "request-protocol", 2, "tcp%s" % proto, zone=zones[0]) + +# Check request/response bytes metrics. +check_item(knot, "mod-stats", "request-bytes", query_size1 + query_size2 + query_size3, + "query") +check_item(knot, "mod-stats", "request-bytes", ddns_query_size, "update") +check_item(knot, "mod-stats", "request-bytes", xfr_query_size, "other") + +check_item(knot, "mod-stats", "response-bytes", reply_size1 + reply_size2 + reply_size3, + "reply") + +check_item(knot, "mod-stats", "request-bytes", query_size1 + query_size2, "query", + zone=zones[0]) +check_item(knot, "mod-stats", "response-bytes", reply_size1 + reply_size2, "reply", + zone=zones[0]) + +check_item(knot, "mod-stats", "request-bytes", query_size3, "query", zone=zones[1]) +check_item(knot, "mod-stats", "response-bytes", reply_size3, "reply", zone=zones[1]) + +# Check query size metrics (just for global module). +indices = dict() +for size in [query_size1, query_size2, query_size3]: + idx = "%i-%i" % (int(size / 16) * 16, int(size / 16) * 16 + 15) + if idx not in indices: + indices[idx] = 1 + else: + indices[idx] += 1; +for size in indices: + check_item(knot, "mod-stats", "query-size", indices[size], idx=size) + +# Check reply size metrics (just for global module). +indices = dict() +for size in [reply_size1, reply_size2, reply_size3]: + idx = "%i-%i" % (int(size / 16) * 16, int(size / 16) * 16 + 15) + if idx not in indices: + indices[idx] = 1 + else: + indices[idx] += 1; +for size in indices: + check_item(knot, "mod-stats", "reply-size", indices[size], idx=size) + +# Check query type metrics. +check_item(knot, "mod-stats", "query-type", 1, idx="SOA") +check_item(knot, "mod-stats", "query-type", 1, idx="NS") +check_item(knot, "mod-stats", "query-type", 1, idx="TYPE11") + +check_item(knot, "mod-stats", "query-type", 1, idx="SOA", zone=zones[0]) +check_item(knot, "mod-stats", "query-type", 1, idx="NS", zone=zones[0]) +check_item(knot, "mod-stats", "query-type", -1, idx="TYPE11", zone=zones[0]) + +check_item(knot, "mod-stats", "query-type", -1, idx="SOA", zone=zones[1]) +check_item(knot, "mod-stats", "query-type", -1, idx="NS", zone=zones[1]) +check_item(knot, "mod-stats", "query-type", 1, idx="TYPE11", zone=zones[1]) + +# Check server operation metrics. +check_item(knot, "mod-stats", "server-operation", 3, idx="query") +check_item(knot, "mod-stats", "server-operation", 1, idx="axfr") +check_item(knot, "mod-stats", "server-operation", 1, idx="update") + +# Check response code metrics. +check_item(knot, "mod-stats", "response-code", 4, idx="NOERROR") +check_item(knot, "mod-stats", "response-code", 3, idx="NOERROR", zone=zones[0]) +check_item(knot, "mod-stats", "response-code", 1, idx="NOERROR", zone=zones[1]) + +# Check nodata metrics. +check_item(knot, "mod-stats", "reply-nodata", 1, idx="other") +check_item(knot, "mod-stats", "reply-nodata", -1, idx="other", zone=zones[0]) +check_item(knot, "mod-stats", "reply-nodata", 1, idx="other", zone=zones[1]) + +t.end() diff --git a/tests-extra/tools/dnstest/module.py b/tests-extra/tools/dnstest/module.py index df40d466a7328edafe806b10be2f46793b542c46..744ed25f1fbf1b5052d9cbff251fedc37347fd42 100644 --- a/tests-extra/tools/dnstest/module.py +++ b/tests-extra/tools/dnstest/module.py @@ -181,3 +181,36 @@ class ModRosedb(KnotModule): set_err("ROSEDB_TOOL") detail_log("!Failed to add a record into rosedb '%s'" % self.dbdir) detail_log(SEP) + +class ModStats(KnotModule): + '''Stats module''' + + src_name = "stats_load" + conf_name = "mod-stats" + + def __init__(self): + super().__init__() + + def _bool(self, conf, name, value=True): + conf.item_str(name, "on" if value else "off") + + def get_conf(self, conf=None): + if not conf: + conf = dnstest.config.KnotConf() + + conf.begin(self.conf_name) + conf.id_item("id", self.conf_id) + self._bool(conf, "request-protocol", True) + self._bool(conf, "server-operation", True) + self._bool(conf, "request-bytes", True) + self._bool(conf, "response-bytes", True) + self._bool(conf, "edns-presence", True) + self._bool(conf, "flag-presence", True) + self._bool(conf, "response-code", True) + self._bool(conf, "reply-nodata", True) + self._bool(conf, "query-type", True) + self._bool(conf, "query-size", True) + self._bool(conf, "reply-size", True) + conf.end() + + return conf diff --git a/tests-extra/tools/dnstest/response.py b/tests-extra/tools/dnstest/response.py index 2df0effb14de0591f889eb2c12f49a252939a35c..f5ae00d3bba91215be137444173553f7b5c49ba7 100644 --- a/tests-extra/tools/dnstest/response.py +++ b/tests-extra/tools/dnstest/response.py @@ -392,3 +392,13 @@ class Response(object): for rr in nsec3_rrs: detail_log(" %s" % rr) detail_log(SEP) + + def query_size(self): + '''Return query size.''' + + return len(self.query.to_wire()) + + def response_size(self): + '''Return response size.''' + + return len(self.resp.to_wire()) diff --git a/tests-extra/tools/dnstest/update.py b/tests-extra/tools/dnstest/update.py index 6532be807dcad4b53f753831ad30757ee9ac2c79..6c9ef5412e304fbb901a3253fbfa245e2c783ee8 100644 --- a/tests-extra/tools/dnstest/update.py +++ b/tests-extra/tools/dnstest/update.py @@ -42,3 +42,8 @@ class Update(object): if self.upd.keyring and not resp.had_tsig: set_err("INVALID RESPONSE") check_log("ERROR: Expected TSIG signed response") + + def query_size(self): + '''Return update query size.''' + + return len(self.upd.to_wire())