#!/usr/bin/env python3

"""
Check of multi-signer DNSKEY synchronization.
"""

import random
import datetime
from dnstest.utils import *
from dnstest.keys import Keymgr
from dnstest.test import Test

SCENARIO = random.choice(range(108))
CDS = bool(SCENARIO % 2)
SIGNERS3 = bool((SCENARIO // 2) % 2)
SIGNER1ROLL = (SCENARIO // 4) % 3
SIGNER2ROLL = (SCENARIO // 12) % 3
DNSKEY_MASTER = (SCENARIO // 36) % 3
ROLL_LOG = [ "ZSK", "KSK", "CSK" ]
check_log("SCENARIO %d, SIGNERS3 enabled %s, SIGNER1ROLL %s, SIGNER2ROLL %s, CDS enabled %s, DNSKEY master %d" % \
          (SCENARIO, SIGNERS3, ROLL_LOG[SIGNER1ROLL], ROLL_LOG[SIGNER2ROLL], str(CDS), DNSKEY_MASTER))

def now_hms(shift):
    t = datetime.datetime.now()
    t = t + datetime.timedelta(seconds=shift)
    return t.strftime("%H:%M:%S")

def detect_ddns_deadlock(server):
    lastline=""
    with open(server.fout, "r") as fl:
        for line in fl:
            if "persistent" not in line:
                lastline = line

    #detect recent (any) activity in the logfile so that servers settle down before equivalence test
    if now_hms(0) in lastline:
        return True
    if now_hms(-1) in lastline:
        return True
    if now_hms(-2) in lastline:
        return True
    return False

def check_same_dnskey(server1, server2, server3, tst, tries=20):
    for i in range(tries):
        if not detect_ddns_deadlock(server1) and \
           not detect_ddns_deadlock(server2) and \
           not (SIGNERS3 and detect_ddns_deadlock(server3)):
            break
        if i < tries - 1:
            tst.sleep(6)
        else:
            set_err("DNSKEY sync has not converged yet")

    dnskey1 = server1.dig(zone[0].name, "DNSKEY", udp=False)
    dnskey2 = server2.dig(zone[0].name, "DNSKEY", udp=False)
    dnskey1.diff(dnskey2)
    if SIGNERS3:
        dnskey3 = server3.dig(zone[0].name, "DNSKEY", udp=False)
        dnskey2.diff(dnskey3)

    server1.flush(zone[0], wait=True)
    server1.zone_verify(zone)
    server2.flush(zone[0], wait=True)
    server2.zone_verify(zone)
    if SIGNERS3:
        server3.flush(zone[0], wait=True)
        server3.zone_verify(zone)

def configure_dnssec(server1, master, server2, server3, roll):
    t.link(zone, master, server1, ddns=True)
    server1.conf_srv().tcp_remote_io_timeout = 1500
    if DNSKEY_MASTER < 2:
        server1.conf_zone(zone).ddns_master = ""
    if DNSKEY_MASTER > 0:
        server1.conf_zone(list(server1.zones.keys())).ixfr_benevolent = True

    server_mod = int(server1.name[-1]) - 2

    server1.dnssec(zone).enable = True
    server1.dnssec(zone).single_type_signing = (roll == 2)
    server1.dnssec(zone).propagation_delay = 4
    server1.conf_ss("submission", zone).timeout = 4
    server1.dnssec(zone).dnskey_management = "incremental"
    server1.dnssec(zone).delete_delay = 4
    server1.dnssec(zone).cds_cdnskey_publish = ("always" if CDS else "none")
    server1.dnssec(zone).keytag_modulo = "%d/3" % server_mod
    if DNSKEY_MASTER == 1:
        server1.conf_ss("dnskey-sync", zone).remote = [ master ]
    else:
        server1.conf_ss("dnskey-sync", zone).remote = [ server2, server3 ] if SIGNERS3 else [ server2 ]

t = Test()

master = t.server("knot", xdp_enable=False) # DDNS over XDP not supported
signer1 = t.server("knot", xdp_enable=False)
signer2 = t.server("knot", xdp_enable=False)
signer3 = t.server("knot", xdp_enable=False)
zone = t.zone("catalog.") # has zero TTL => faster key rollovers

configure_dnssec(signer1, master, signer2, signer3, SIGNER1ROLL)
configure_dnssec(signer2, master, signer3 if SIGNERS3 else signer1, signer1, SIGNER2ROLL)
if SIGNERS3:
    configure_dnssec(signer3, master, signer1, signer2, SIGNER2ROLL)

t.start()
signer1.zone_wait(zone)
signer2.zone_wait(zone)
if SIGNERS3:
    signer3.zone_wait(zone)

t.sleep(4)
check_same_dnskey(signer1, signer2, signer3, t)

signer1.ctl("zone-key-rollover %s %s" % (zone[0].name, "zsk" if SIGNER1ROLL == 0 else "ksk"))
t.sleep(0.5)
signer2.ctl("zone-key-rollover %s %s" % (zone[0].name, "zsk" if SIGNER2ROLL == 0 else "ksk"))
if SIGNERS3:
    t.sleep(0.5)
    signer3.ctl("zone-key-rollover %s %s" % (zone[0].name, "zsk" if SIGNER2ROLL == 0 else "ksk"))

t.sleep(8)
check_same_dnskey(signer1, signer2, signer3, t)

t.sleep(8)
check_same_dnskey(signer1, signer2, signer3, t)

t.end()
