jsonschema
psutil
pyyaml
+setuptools
--- /dev/null
+#!/usr/bin/env python3
+
+'''Test master-slave-like replication using Redis database.'''
+
+from dnstest.test import Test
+
+t = Test(redis=True)
+
+master = t.server("knot")
+slave = t.server("knot")
+
+zones = t.zone("example.com.")
+
+t.link(zones, master)
+t.link(zones, slave)
+
+master.zonefile_sync = "0"
+
+for z in zones:
+ master.zones[z.name].redis_out = "1"
+ slave.zones[z.name].redis_in = "1"
+ slave.zones[z.name].zfile.remove()
+
+t.start()
+
+master.zones_wait(zones)
+
+master.ctl("zone-flush", wait=True)
+#slave.ctl("zone-reload")
+
+serials = slave.zones_wait(zones)
+t.xfr_diff(master, slave, zones)
+
+for z in zones:
+ up = master.update(z)
+ up.add("suppnot1", 3600, "A", "1.2.3.4")
+ up.send()
+
+t.sleep(2)
+master.ctl("zone-flush", wait=True)
+#slave.ctl("zone-reload")
+
+slave.zones_wait(zones, serials)
+t.xfr_diff(master, slave, zones)
+
+# SOA serial logic rotation
+serials6 = serials5
+
+for i in range(5):
+ if i == 3:
+ slave.ctl("zone-freeze", wait=True)
+
+ for z in zones:
+ if i == 4:
+ serials6[z.name] += 1
+ else:
+ serials6[z.name] += (1 << 30)
+ serials6[z.name] %= (1 << 32)
+ up = master.update(z)
+ up.add(z.name, 3600, "SOA", "dns1 hostmaster %d 10800 3600 1209600 7200" % (serials6[z.name]))
+ up.add("loop", 3600, "AAAA", "1::%d" % i)
+ up.send()
+ master.zones_wait(zones, serials6, equal=True)
+
+slave.ctl("zone-thaw")
+slave.zones_wait(zones, serials6, equal=True)
+t.xfr_diff(master, slave, zones)
+resp = slave.dig("loop." + zones[0].name, "AAAA")
+resp.check(rcode="NOERROR", rdata="1::1")
+resp.check(rcode="NOERROR", rdata="1::4")
+resp.check_count(5, "AAAA")
+
+t.end()
gdb_bin = get_binary("KNOT_TEST_GDB", "gdb")
# KNOT_TEST_VGDB - vgdb binary.
vgdb_bin = get_binary("KNOT_TEST_VGDB", "vgdb")
+# KNOT_TEST_REDIS - Redis database server binary.
+redis_bin = get_binary("KNOT_TEST_REDIS", "redis-server")
+# KNOT_TEST_REDIS_CLI - Redis database command line client.
+redis_cli = get_binary("KNOT_TEST_REDIS_CLI", "redis-cli")
# KNOT_TEST_LIBTOOL - libtool script.
libtool_bin = get_binary("KNOT_TEST_LIBTOOL", repo_binary("libtool"))
# KNOT_TEST_LIBKNOT - libknot library.
--- /dev/null
+from dnstest.utils import *
+import dnstest.params as params
+import os
+import shutil
+import subprocess
+import time
+
+class Redis(object):
+ def __init__(self, addr, wrk_dir, redis_bin, redis_cli, knotso):
+ self.addr = addr
+ self.port = None
+ self.tls_port = None
+ self.pin = None
+ self.wrk_dir = wrk_dir
+ self.redis_bin = redis_bin
+ self.redis_cli = redis_cli
+ self.knotso = knotso
+ self.proc = None
+ self.monitor = None
+ self.monitor_log = None
+
+ if not os.path.exists(wrk_dir):
+ os.makedirs(wrk_dir)
+
+ def wrk_file(self, filename):
+ return os.path.join(self.wrk_dir, filename)
+
+ def conf_file(self):
+ return self.wrk_file("redis.conf")
+
+ def gen_confile(self):
+ with open(self.conf_file(), "w") as cf:
+ cf.write("dir " + self.wrk_dir + os.linesep)
+ cf.write("logfile " + self.wrk_file("redis.log") + os.linesep)
+ cf.write("loadmodule " + self.knotso + os.linesep)
+ cf.write("bind " + self.addr + os.linesep)
+ cf.write("port " + str(self.port) + os.linesep)
+ cf.write("tls-port " + str(self.tls_port) + os.linesep)
+ cf.write("tls-protocols \"TLSv1.3\"" + os.linesep)
+ cf.write("tls-auth-clients no" + os.linesep)
+ cf.write("tls-key-file key.pem" + os.linesep)
+ cf.write("tls-cert-file cert.pem" + os.linesep)
+ if self.addr != "127.0.0.1" and self.addr != "::1":
+ cf.write("protected-mode no " + os.linesep)
+
+ shutil.copy(os.path.join(params.common_data_dir, "cert", "cert.pem"), self.wrk_dir)
+ shutil.copy(os.path.join(params.common_data_dir, "cert", "key.pem"), self.wrk_dir)
+ keyfile = os.path.join(self.wrk_dir, "key.pem")
+ out = subprocess.check_output(["certtool", "--infile=" + keyfile, "-k"]).rstrip().decode('ascii')
+ self.pin = ssearch(out, r'pin-sha256:([^\n]*)')
+
+ def start(self):
+ self.proc = subprocess.Popen([ self.redis_bin, self.conf_file() ])
+ time.sleep(0.3)
+ monitor_cmd = [ self.redis_cli, "-h", self.addr, "-p", str(self.port), "monitor" ]
+ self.monitor_log = open(os.path.join(self.wrk_dir, "monitor.log"), "a")
+ self.monitor = subprocess.Popen(monitor_cmd, stdout=self.monitor_log, stderr=self.monitor_log)
+
+ def stop(self):
+ if self.monitor:
+ self.monitor.terminate()
+ if self.monitor_log:
+ self.monitor_log.close()
+ if self.proc:
+ self.proc.terminate()
+
+ def cli(self, *params):
+ cmd = [ self.redis_cli, "-h", self.addr, "-p", str(self.port) ] + list(params)
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, _ = p.communicate()
+ return out.decode().strip()
self.zfile = zone_file
self.masters = set()
self.slaves = set()
+ self.redis_in = None
+ self.redis_out = None
self.serial_modulo = None
self.ddns = ddns
self.ixfr = ixfr
self.session_log = None
self.confile = None
+ self.redis = None
+
self.binding_errors = 0
def _check_socket(self, proto, port):
s.item_str("journal-db-max-size", self.journal_db_size)
s.item_str("timer-db-max-size", self.timer_db_size)
s.item_str("catalog-db-max-size", self.catalog_db_size)
+ if self.redis is not None:
+ tls = random.choice([True, False])
+ port = self.redis.tls_port if tls else self.redis.port
+ s.item_str("zone-db-listen", self.redis.addr + "@" + str(port))
+ if tls:
+ s.item_str("zone-db-cert-key", self.redis.pin)
+ s.item_str("zone-db-tls", "on")
s.end()
s.begin("template")
self.config_xfr(z, s)
+ self._str(s, "zone-db-input", z.redis_in)
+ self._str(s, "zone-db-output", z.redis_out)
+
self._str(s, "serial-policy", self.serial_policy)
self._str(s, "serial-modulo", z.serial_modulo)
self._str(s, "ddns-master", self.ddns_master)
from dnstest.context import Context
import dnstest.params as params
import dnstest.server
+import dnstest.redis
import dnstest.keys
import dnstest.zonefile
+def repo_file(*path):
+ module_path = os.path.dirname(os.path.realpath(__file__))
+ repo_path = os.path.realpath(os.path.join(module_path, "..", "..", ".."))
+ file_path = os.path.join(repo_path, *path)
+ return file_path if os.path.isfile(file_path) else None
+
class Test(object):
'''Specification of DNS test topology'''
rel_time = time.time()
start_time = 0
- def __init__(self, address=None, tsig=None, stress=True, quic=False, tls=False):
+ def __init__(self, address=None, tsig=None, stress=True, quic=False, tls=False, redis=False):
if not os.path.exists(Context().out_dir):
raise Exception("Output directory doesn't exist")
else:
self.addr = Test.LOCAL_ADDR_MULTI[random.choice([4, 6])]
+ self.redis = None
+ redis_knotso = repo_file("src", "redis", ".libs", "knot.so")
+ if redis:
+ if params.redis_bin == "":
+ raise Skip("Redis server not available")
+ if redis_knotso is None:
+ raise Skip("Redis knot module not available")
+ self.redis = dnstest.redis.Redis(self.addr, os.path.join(self.out_dir, "redis"),
+ params.redis_bin, params.redis_cli, redis_knotso)
+
self.tsig = None
if tsig != None:
if type(tsig) is dnstest.keys.Tsig:
if os.path.isfile(suppressions_file):
srv.valgrind.append("--suppressions=%s" % suppressions_file)
+ srv.redis = self.redis
+
self.servers.add(srv)
return srv
server.xdp_cover_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
server.xdp_cover_sock.bind((server.addr, server.xdp_port))
+ if self.redis is not None:
+ self.redis.port = self._gen_port()
+ self.redis.tls_port = self._gen_port()
+ self.redis.gen_confile()
+
for server in self.servers:
server.gen_confile()
self.generate_conf()
+ if self.redis:
+ self.redis.start()
+
def srv_sort(server):
masters = 0
for z in server.zones:
else:
server.stop(check=check)
+ if self.redis:
+ self.redis.stop()
+
def end(self):
'''Finish testing'''