#!/usr/bin/env python3

import os
import sys
import telnetlib
import time
from datetime import datetime

import requests

#########################################################################
#
#  parameters
#
#########################################################################

#  echo -e "usage: $0 <my-topology.virl> <number of concurrent simulations> <username> <password> <node-timeout> <global-timeout> [debug level], e.g.,\n"
#  echo "$0 /home/virl/git-virl-hs-fulda/GIT-VIRL-HS-Fulda/advcompnet-lab2-dcn-fabricpath.virl 6 guest password 0.5 500"

BASE_API_URL = "http://192.168.76.209:13080/v2"

TELNET_HOST = "192.168.76.209"
USERNAME = sys.argv[3]
PORT = 13080

# PASSWORD = getpass.getpass()
PASSWORD = sys.argv[4]

TIMEOUT = int(sys.argv[5])
GLOBAL_TIMEOUT = 1000

PROJECT_NAME = sys.argv[1]
# PROJECT_ID = "69e293ac-a339-4d1d-b2e3-047dc2da8566"

NUMBER_OF_CONCURRENT_BENCHMARK_PROJECTS = int(sys.argv[2])
NUMBER_OF_BENCHMARK_RUNS = 1

#########################################################################
#
#  start of script
#
#########################################################################

# delete all stale benchmark projects left over from previous runs
r = requests.get(BASE_API_URL + '/projects', auth=(USERNAME, PASSWORD))
if r.status_code == 200:
    for entry in r.json():
        if str(entry['name']).startswith("benchmark"):
            r = requests.delete(BASE_API_URL + '/projects/' + entry["project_id"], auth=(USERNAME, PASSWORD))
            if r.status_code == 204:
                print("Deleted stale project %s (%s)..." % (entry["project_id"], entry["name"]))
            else:
                sys.exit("Could not delete project")
else:
    sys.exit("Could not get stale benchmark projects")

for run in range(1, NUMBER_OF_BENCHMARK_RUNS + 1):

    print("Starting run %i..." % run)

    # array containing the results of each run
    results = []

    # create timestamp on script start
    time_start = time.time()
    date_start = datetime.fromtimestamp(time_start).ctime()
    print("Start at: %s " % date_start)

    r = requests.get(BASE_API_URL + '/projects', auth=(USERNAME, PASSWORD))
    if r.status_code == 200:
        for entry in r.json():
            if entry['name'] == PROJECT_NAME:
                # print(entry['project_id'])
                PROJECT_ID = entry['project_id']
    else:
        sys.exit("Could not get projects")
    # array containing all created temporary benchmark projects
    benchmarks = []

    # duplicate project to create multiple temporary benchmark projects
    for benchNumber in range(1, NUMBER_OF_CONCURRENT_BENCHMARK_PROJECTS + 1):
        print("Adding benchmark %i" % benchNumber)
        data = "{ \"name\": \"benchmark" + str(benchNumber) + "\"}"
        r = requests.post(BASE_API_URL + '/projects/' + PROJECT_ID + '/duplicate', data, auth=(USERNAME, PASSWORD))
        if r.status_code == 201:
            entry = r.json()
            print("Created benchmark project %s (%s) from %s (%s)" % (
                entry['project_id'], entry['name'], PROJECT_ID, PROJECT_NAME))
            benchmarks.append(entry)
            data = "{}"
            r = requests.post(BASE_API_URL + '/projects/' + entry['project_id'] + '/open', data,
                              auth=(USERNAME, PASSWORD))
            if r.status_code == 201:
                print("Opened benchmark project %s (%s)" % (entry['project_id'], entry['name']))
            else:
                sys.exit("Could not open benchmark project")
        else:
            sys.exit("Could not duplicate project")

    # time.sleep(5)

    # start all nodes in all projects
    for benchmark in benchmarks:
        data = "{}"
        r = requests.post(BASE_API_URL + '/projects/' + benchmark["project_id"] + '/nodes/start', data,
                          auth=(USERNAME, PASSWORD))
        if r.status_code == 204:
            print("Started all nodes in project %s (%s) ..." % (benchmark["project_id"], benchmark["name"]))
        else:
            sys.exit("Could not start nodes")

    # create timestamp when nodes are started
    time_started = time.time()
    date_started = datetime.fromtimestamp(time_started).ctime()
    print("Started at: %s " % date_started)

    # collect all active nodes in all projects to the nodes[] array
    nodes = []
    for benchmark in benchmarks:
        print("Getting nodes from " + benchmark["name"] + "...")
        r = requests.get(BASE_API_URL + '/projects/' + benchmark["project_id"] + '/nodes', auth=(USERNAME, PASSWORD))
        if r.status_code == 200:
            for entry in r.json():
                print("Found node: " + entry["name"] + " state: " + entry["status"] + " console: " + str(
                    entry["console"]))
                if entry["status"] == "started":
                    nodes.append(entry)
                    print("Added node...")
                else:
                    sys.exit("Not all nodes active")
        else:
            sys.exit("Could get state to count nodes")

    nodeCount = len(nodes)
    print("Found %i nodes in all benchmarks..." % (len(nodes)))

    # create timestamp when nodes are active
    time_active = time.time()
    date_active = datetime.fromtimestamp(time_active).ctime()
    print("Active at: %s " % datetime.fromtimestamp(time_active).ctime())

    # check if nodes are usable
    usable_nodes = []
    while len(nodes) > 0:

        # perf output
        # print(os.popen("top -b -n 1 | head -20 >> gns3bench-perf.log"))

        time_globalTimeout = time.time()
        print("(check duration: {:d} sec, timeout: {:d} sec)".format(int((time_globalTimeout - time_active)),
                                                                     int(GLOBAL_TIMEOUT)))

        # if usability of nodes is checked longer than global timeout, exit
        # this can happen if nodes get stuck, e.g., due to high load, missing resources etc.
        if time_globalTimeout - time_active > GLOBAL_TIMEOUT:
            date_usable = 0
            durationUsable = 0
            avgConsoleDelay = 0
            durationStart = time_started - time_start
            durationActive = time_active - time_start
            results.append("%s;%i;%i;%s;%s;%s;%s;%i;%i;%i;%f" % (
                (PROJECT_NAME + ": TIMEOUT"), NUMBER_OF_CONCURRENT_BENCHMARK_PROJECTS, nodeCount, date_start,
                date_started, date_active, date_usable, durationStart, durationActive, durationUsable, avgConsoleDelay))
            results.append("  Occurred while still checking nodes: " + str(nodes))

            f = open("gns3bench-error.log", "a")
            f.writelines(results)
            f.write("\r\n")
            f.close()
            sys.exit("global timeout")

        for entry in nodes:
            print("Checking node %s in %s..." % (entry["name"], entry["project_id"]))
            try:
                tn = telnetlib.Telnet(TELNET_HOST, entry["console"])
                tn.write(b"\n" + b"\n" + b"\n")
                response = tn.read_until(b" login:", timeout=TIMEOUT)
                # print(response)
                if response.find("login:".encode()) != -1:
                    print("Found usable node %s" % entry["name"])
                    usable_nodes.append(entry)
                    nodes.remove(entry)
            except TimeoutError:
                print("Connection to %s in project %s timed out" % (entry["name"], entry["project_id"]))

    print(str(len(usable_nodes)) + " usable nodes...")

    # create timestamp when all nodes are usable
    time_usable = time.time()
    date_usable = datetime.fromtimestamp(time_usable).ctime()
    print("Usable at: %s " % datetime.fromtimestamp(time_usable).ctime())

    durationStart = time_started - time_start
    durationActive = time_active - time_start
    durationUsable = time_usable - time_start
    # print results
    print("\n\n\nResult:")
    print("Start: %f " % durationStart)
    print("Active: %f " % durationActive)
    print("Usable: %f " % durationUsable)

    # file output
    # Topology;ConcurrentSims;Nodes;Script-Start;Started;Active;Usable;Start Time (sec);Active Time (sec);Usable Time (sec);Average Console Delay (sec)
    # advcompnet-lab-1-dcn-scenario1.virl;10;50;Mon Jun 12 12:48:51 GMT 2017;Mon Jun 12 12:51:49 GMT 2017;Mon Jun 12 12:55:33 GMT 2017;Mon Jun 12 12:56:23 GMT 2017;178;402;452;0,3453
    avgConsoleDelay = 0
    results.append("%s;%i;%i;%s;%s;%s;%s;%i;%i;%i;%f" % (
        PROJECT_NAME, NUMBER_OF_CONCURRENT_BENCHMARK_PROJECTS, nodeCount, date_start, date_started, date_active,
        date_usable, durationStart, durationActive, durationUsable, avgConsoleDelay))

    f = open("gns3bench.log", "a")
    f.writelines(results)
    f.write("\r\n")
    f.close()
    print("Appended output to %s" % os.path.join(os.getcwd(), str(f.name)))

    # stop here, if we don't won't to stop and delete benchmark projects
    # sys.exit(0)

    # print("waiting...")
    # time.sleep(1)

    # stop all nodes in all projects
    # for benchmark in benchmarks:
    #    data = "{}"
    #    r = requests.post(BASE_API_URL + '/projects/' + benchmark["project_id"] + '/nodes/stop', data,
    #                      auth=(USERNAME, PASSWORD))
    #    if r.status_code == 204:
    #        print("Stopped all nodes in project " + benchmark["project_id"] + "...")
    #    else:
    #        sys.exit("Could not stop nodes")

    # delete the created benchmark projects
    for benchmark in benchmarks:
        r = requests.delete(BASE_API_URL + '/projects/' + benchmark["project_id"], auth=(USERNAME, PASSWORD))
        if r.status_code == 204:
            print("Deleted project " + benchmark["name"] + "...")
        else:
            sys.exit("Could not delete project")

print("%i runs finished" % NUMBER_OF_BENCHMARK_RUNS)

sys.exit(0)