#!/usr/bin/env python3

'''Test concurrent access to control socket.'''

from dnstest.utils import *
from dnstest.test import Test
import random
import threading
import time

zone_backup_running = dict()

def check_shutdown(server):
    if not server.log_search("shutting down"):
        set_err("server %s not shut down" % server.name)

def random_sleep():
    time.sleep(random.choice([0, 0, 0, 0, 0, 0.1, 0.2, 1, 2, 3 ]))

def random_ctl(server, zone_name):
    cmd = random.choice(["status", "reload", "stats", "zone-check", "zone-status", "zone-reload",
                         "zone-refresh", "zone-retransfer", "zone-notify", "zone-flush",
                         "zone-sign", "zone-keys-load", "zone-ksk-submitted", "zone-freeze",
                         "zone-thaw", "zone-xfr-freeze", "zone-xfr-thaw", "zone-read", "zone-get",
                         "zone-stats", "conf-init", "conf-list", "conf-read", "conf-diff",
                         "conf-get", "zone-serial-set"])
    if cmd[0:5] == "zone-" and random.choice([False, True, True]):
        cmd += " " + zone_name
    if cmd == "zone-serial-set" and random.choice([False, True, True]):
        cmd += " " + random.choice(["+", "="]) + str(random.randint(0, 4000000000))
    if random.choice([False, True]):
        cmd = "-b " + cmd
    try:
        server.ctl(cmd, availability=False)
    except:
        pass

def random_ctls(server, zone_name):
    for i in range(random.choice([1, 1, 2, 3])):
        random_ctl(server, zone_name)
        random_sleep()

def ctl_txn_generic(server, txn_start, txn_modify, txn_commit, txn_abort, abort_failed_start):
    txnsock = server.ctl_sock_rnd()
    try:
        server.ctl("zone-status", availability=False, custom_parm=txnsock)
    except:
        pass
    try:
        server.ctl(txn_start, availability=False, custom_parm=txnsock)
    except:
        try:
            if abort_failed_start:
                server.ctl(txn_abort, availability=False, custom_parm=txnsock)
        except:
            pass
        return
    random_sleep()
    try:
        server.ctl(txn_modify, availability=False, custom_parm=txnsock)
        random_sleep()
        server.ctl(txn_commit, availability=False, custom_parm=txnsock)
    except:
        attempts = 9
        while attempts > 0:
            time.sleep(2)
            attempts -= 1
            try:
                server.ctl(txn_abort, availability=False, custom_parm=txnsock)
                attempts = 0
            except:
                pass

def bck_purge_rest(server, zone_name):
    global zone_backup_running
    if random.choice([False, True]) and not True in zone_backup_running.values():
        zone_name = " "
    if zone_name in zone_backup_running and zone_backup_running[zone_name]:
        return
    if " " in zone_backup_running and zone_backup_running[" "]:
        return
    zone_backup_running[zone_name] = True

    bckdir = "%s/backup" % server.dir
    bckdir += str(int(time.time()))
    bckdir = " +backupdir " + bckdir
    cmd_bck = "-t 14 -b zone-backup " + zone_name + bckdir
    cmd_pur = "-b -f zone-purge " + ("--" if zone_name == " " else zone_name)
    cmd_res = "-b zone-restore " + zone_name + bckdir
    ctl_txn_generic(server, cmd_bck, cmd_pur, cmd_res, cmd_res, False)
    try:
        server.ctl("zone-refresh " + zone_name, availability=False)
    except:
        pass
    try:
        server.ctl("zone-reload " + zone_name, availability=False)
    except:
        pass
    for i in range(10):
        try:
            resp = server.dig(zone_name, "SOA")
            if resp.count("SOA") > 0:
                break
        except:
            pass
        time.sleep(1)
    zone_backup_running[zone_name] = False

def ctl_update(server, zone_name):
    ctl_txn_generic(server, "-b zone-begin " + zone_name,
                    random.choice(["zone-set " + zone_name + " abc 3600 A 1.2.3." + str(random.randint(1, 254)),
                                   "zone-serial-set " + zone_name + " " + random.choice(["+", "="]) + str(random.randint(0, 4000000000))]),
                    "zone-commit " + zone_name, "zone-abort " + zone_name, True)

def conf_update(server, zone_name):
    ctl_txn_generic(server, "conf-begin",
                    "conf-set zone[" + zone_name + "].zonemd-generate " + random.choice(["none", "zonemd-sha384", "zonemd-sha512", "remove"]),
                    "conf-commit", "conf-abort", True)

def random_thing(server, zone_name):
    scn = random.choice([random_ctls, random_ctls, random_ctls, random_ctls, bck_purge_rest, ctl_update, conf_update])
    scn(server, zone_name)

def run_thr(fun, server, zone_name):
    threading.Thread(target=fun, args=[server, zone_name]).start()

t = Test()

master = t.server("knot")
slave = t.server("knot")
zones = t.zone_rnd(2, dnssec=False, records=40)
t.link(zones, master, slave)

for z in zones:
    master.dnssec(z).enable = True
    master.dnssec(z).nsec3 = True

for s in [ master, slave ]:
    s.conf_srv().background_workers = 3
    s.conf_srv().udp_workers = 1
    s.conf_srv().tcp_workers = 1

t.start()
slave.zones_wait(zones)

for i in range(60):
    s = random.choice([master, slave])
    z = random.choice(zones)
    run_thr(random_thing, s, z.name)
    random_sleep()

for s in [ master, slave ]:
    for z in zones:
        try:
            s.ctl("zone-xfr-thaw " + z.name, availability=False)
            s.ctl("zone-thaw " + z.name, availability=False)
            s.ctl("zone-notify " + z.name, availability=False)
        except:
            pass

for s in [ master, slave ]:
    try:
        s.ctl("conf-abort", availability=False)
    except:
        pass
    for z in zones:
        try:
            s.ctl("zone-abort " + z.name, availability=False)
        except:
            pass


t.sleep(10)
master.zones_wait(zones) # check that server is still operable
slave.zones_wait(zones) # check that server is still operable

t.end()

time.sleep(4)
for s in [ master, slave ]:
    check_shutdown(s)
