"""
SPDX-License-Identifier: Apache-2.0
Copyright 2017 Massachusetts Institute of Technology.
"""


import base64
import configparser
import json
import os
import queue
import shutil
import signal
import subprocess
import tempfile
import threading
import time
import unittest
import uuid
from distutils.dir_util import copy_tree

import _io
import common
import crypto
import tenant
import tornado_requests

sentinel = None
cv_process = None
cn_process = None
cn_process_list = []

queue = queue.Queue()
num_threads = 5


config = configparser.RawConfigParser()
config.read(common.CONFIG_FILE)

cloudverifier_port = config.get("general", "cloudverifier_port")
cloudagent_port = config.get("general", "cloudagent_port")
registrar_port = config.get("general", "registrar_port")

cloudagent_ip = config.get("tenant", "cloudagent_ip")
cloudverifier_ip = config.get("tenant", "cloudverifier_ip")
registrar_ip = config.get("tenant", "cloudverifier_ip")
tpm_policy = json.loads(config.get("tenant", "tpm_policy"))
my_cert = config.get("tenant", "my_cert")
ca_cert = config.get("tenant", "ca_cert")
private_key = config.get("tenant", "private_key")
test_num_cloudagents = config.getint("general", "test_num_cloudagents")
test_duration = config.getint("general", "test_duration")
# cv_persistence_filename = config.get('cloud_verifier', 'persistence_filename')
# en_persistence_filename = config.get('registrar', 'persistence_filename')
cv_persistence_filename = None
en_persistence_filename = None


K = None
U = None
V = None


def readKUV():
    global K, U, V

    # read the keys in
    f = open("content_keys.txt")
    K = base64.b64decode(f.readline())
    U = base64.b64decode(f.readline())
    V = base64.b64decode(f.readline())
    f.close()


def text_callback(request, context):
    context.status_code = 402
    return "{}"


class Test(unittest.TestCase):

    cloudverifier_process = None

    @classmethod
    def setUpClass(cls):

        cls.test_table = {
            "test_cloudagent_tenant_get_nonce": {
                "prerun_function": {"name": "launch_cloudagent", "argument": None},
                "state_change_functions": [
                    {
                        "function_name": "test_cloudagent_tenant_get_nonce",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudagent_ip,
                        "http_request_port": cloudagent_port,
                        "http_request_query": {"nonce": "ThisIsThePasswordABC"},
                        "http_request_path": "/v1/quotes/tenant",
                        "http_result_status_expected": 200,
                        "check_function": {"name": "check_test_cloudagent_tenant_get_nonce"},
                    }
                ],
                "postrun_function": {"name": "kill_cloudagent", "argument": None},
            },
            "test_cloudagent_tenant_get_quote": {
                "prerun_function": {"name": "launch_cloudagent", "argument": None},
                "state_change_functions": [
                    {
                        "function_name": "test_cloudagent_tenant_get_quote",
                        "http_request_verb": "POST",
                        "http_request_ip": cloudagent_ip,
                        "http_request_port": cloudagent_port,
                        "http_request_path": "/v1/quotes/tenant",
                        "http_request_body": '{"encrypt_check": "K+oD4GfBMAdOFy94ZxTU2hB77tySSB75VVz2Zo4jN02txhNK2KiO5JhE1SRIUVASMZMW/VQUS9WgWdCUaJ+LOTWSuQ13alG4P4cLoamBr9c=","encrypted_key":"rBWIxK4i6zTl/M69Yyh2hmX+itDR9QCx4CIqmuRrEN3JAIUc2M+balr8gPD9r3Bs0OxYRC8/kcxBNo9Bsm93WZKwlmbZt2uVxhfaAqXwdGVpMBnM3bQnAEj1LIFoZZyQ48PVIdrEO4WW73Z2X3fplEFgOC3YT3lzluYgrn8iBkMRm+o2pJMdhynh6xLguszLX7qDOccPIIJch14ftWlsy6Ya9a6LHr9+hIfs4p2ATVVSl1wtUbf/ouNJdqUPAiFc4oXsg+kHQzWWiipjsAm871cA4wlvUb+/D4mFz1p3PRAK9hcICGwKoanWh8jbeuYnoqkch2EoHeLqayrisfNogg=="}',
                        "http_result_status_expected": 200,
                    }
                ],
                "postrun_function": {"name": "kill_cloudagent", "argument": None},
            },
            "test_cloudverifier_tenant_provide_v": {
                # "prerun_function"  : {"name":"launch_cloudverifier", "argument": None},
                "state_change_functions": [
                    {
                        "function_name": "test_cloudverifier_tenant_provide_v",
                        # "pre_function"  : {"name":"do_mock_for_test_cloudverifier_tenant_provide_v", "argument": None},
                        "http_request_verb": "POST",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_request_body": '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"06480EC4-6BF3-4F00-8323-FE6AE5868297","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',
                        "http_result_status_expected": 200,
                        # "concurrent_instances" : 10,
                        # "concurrent_new_thread_function" : "new_thread",
                        # "test_iterations" : 100,
                    },
                ],
            },
            "test_concurrent_access": {
                "prerun_function": {"name": "launch_cloudverifier", "argument": None},
                "state_change_functions": [
                    {
                        "function_name": "test_concurrent_access",
                        "http_request_verb": "POST",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_request_body": '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"06480EC4-6BF3-4F00-8323-FE6AE5868297","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',
                        "http_result_status_expected": 200,
                        "concurrency": {"instances": 5, "new_thread_function": "new_thread"},
                        "test_iterations": 100,
                    },
                ],
                "state_validation_functions": [
                    {
                        "function_name": "test_agent_id_list",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        # "http_request_body": '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"06480EC4-6BF3-4F00-8323-FE6AE5868297","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',
                        "http_result_status_expected": 200,
                        "check_function": {"name": "check_and_delete_all_entries", "argument": 500},
                    },
                ],
                "postrun_function": {"name": "kill_cloudverifier", "argument": None},
            },
            "test_concurrent_cloudnodiness": {
                # "prerun_function"  : {"name":"launch_cloudagents", "args": {'starting_port':9000, 'num_cloudagent_instances':250}},
                "prerun_function": {
                    "name": "launch_cloudagents",
                    "args": {"port_file": "cloudagent_port.txt", "num_cloudagent_instances": test_num_cloudagents},
                },
                "state_change_functions": [
                    {
                        "pre_function": {"name": "test_concurrent_cloudnodiness_modify_request", "argument": 500},
                        "function_name": "test_concurrent_cloudnodiness",
                        "http_request_verb": "POST",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_request_body": '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"C432FBB3-D2F1-4A97-9EF7-75BD81C00000","cloudagent_ip":"cloudagent_ip.txt","cloudagent_port":"cloudagent_port.txt","tpm_policy": {"22":"ffffffffffffffffffffffffffffffffffffffff","16":"0000000000000000000000000000000000000000"} }',
                        "http_result_status_expected": 200,
                        "test_iterations": test_num_cloudagents,
                        "post_function": {
                            "name": "test_concurrent_cloudnodiness_reset_request",
                            "args": {"ip_file": "cloudagent_ip.txt", "port_file": "cloudagent_port.txt"},
                        },
                    },
                ],
                "postrun_function": {
                    "name": "kill_cloudagents_after_delay",
                    "args": {
                        "sleep": test_duration,
                        "port_file": "cloudagent_port.txt",
                        "num_cloudagent_instances": test_num_cloudagents,
                    },
                },
            },
            "test_full_integration_happy_path": {
                # "prerun_function"  : {"name":"launch_required_servers", "argument": None},
                "state_change_functions": [
                    {
                        "function_name": "do_cloudagent_part",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudagent_ip,
                        "http_request_port": cloudagent_port,
                        "http_request_path": "/v1/quotes/tenant",
                        "http_request_query": {"nonce": "ThisIsThePasswordABC"},
                        "http_result_status_expected": 200,
                        "check_function": {"name": "provide_e"},
                        # "concurrent_new_thread_function" : "new_thread",
                        # "test_iterations" : 100,
                    },
                    {
                        "function_name": "do_cloudverifier_part",
                        "http_request_verb": "POST",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_request_body": '{"v": "XrNfEiODfu1fdXGtWbA+Wk02UhBxx1jTq7zhbC54ROA=","agent_id":"C432FBB3-D2F1-4A97-9EF7-75BD81C866E9","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',
                        "http_result_status_expected": 200,
                        "check_function": {"name": "check_test_sleep", "argument": 5},
                        # "concurrent_new_thread_function" : "new_thread",
                        # "test_iterations" : 100,
                    },
                ],
                # "postrun_function"  : {"name":"kill_required_servers", "argument": None},
            },
            "test_persistance_file_load": {
                "prerun_function": {
                    "name": "launch_cloudverifier",
                    "args": '{"06480EC4-6BF3-4F00-8323-FE6AE5868297": {"tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}, "ip": "127.0.0.1", "port": "8882", "v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU="}}',
                },
                "state_change_functions": [
                    {
                        "function_name": "test_persistance_file_load",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_result_status_expected": 200,
                        "check_function": {
                            "name": "check_test_persistance_file_load",
                            "argument": "06480EC4-6BF3-4F00-8323-FE6AE5868297",
                        },
                    },
                ],
                "postrun_function": {"name": "kill_cloudverifier", "argument": None},
            },
            "test_persistance_file_write": {
                "prerun_function": {"name": "launch_cloudverifier", "args": "{}"},
                "state_change_functions": [
                    {
                        "function_name": "test_persistance_file_write",
                        "http_request_verb": "POST",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_request_body": '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"06480EC4-6BF3-4F00-8323-FE6AE5868297","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',
                        "http_result_status_expected": 200,
                        "check_function": {
                            "name": "check_test_persistance_file_write",
                            "argument": "06480EC4-6BF3-4F00-8323-FE6AE5868297",
                        },
                    },
                ],
                "postrun_function": {"name": "kill_cloudverifier", "argument": None},
            },
            "test_persistance_file_bad": {
                "prerun_function": {"name": "launch_cloudverifier", "args": "{"},
            },
            "test_persistance_file_empty": {
                "prerun_function": {"name": "launch_cloudverifier", "args": ""},
                "state_change_functions": [
                    {
                        "function_name": "test_persistance_file_empty",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_result_status_expected": 200,
                        "check_function": {"name": "test_check_persistance_file_empty", "argument": None},
                    },
                ],
                "postrun_function": {"name": "kill_cloudverifier", "argument": None},
            },
            "test_persistance_file_nonexistent": {
                "prerun_function": {"name": "launch_cloudverifier", "args": None},
                "state_change_functions": [
                    {
                        "function_name": "test_persistance_file_nonexistent",
                        "http_request_verb": "GET",
                        "http_request_ip": cloudverifier_ip,
                        "http_request_port": cloudverifier_port,
                        "http_request_path": "/v1/instances",
                        "http_result_status_expected": 200,
                        "check_function": {"name": "test_check_persistance_file_empty", "argument": None},
                    },
                ],
                "postrun_function": {"name": "kill_cloudverifier", "argument": None},
            },
        }

    def test_concurrent_cloudnodiness(self):
        self.execute_test_definition()

    def test_cloudagent_tenant_get_nonce(self):
        self.execute_test_definition()

    def test_cloudagent_tenant_get_quote(self):
        self.execute_test_definition()

    def test_cloudverifier_tenant_provide_v(self):
        self.execute_test_definition()

    def test_concurrent_access(self):
        self.execute_test_definition()

    def test_full_integration_happy_path(self):
        self.execute_test_definition()

    def test_persistance_file_load(self):
        self.execute_test_definition()

    def test_persistance_file_write(self):
        self.execute_test_definition()

    def test_persistance_file_bad(self):
        self.execute_test_definition()

    def test_persistance_file_empty(self):
        self.execute_test_definition()

    def test_persistance_file_nonexistent(self):
        self.execute_test_definition()

    def test_cloudagent_cloud_verifier_get_quote(self):
        pass

    def check_test_sleep(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        time.sleep(argument)

    # '{"v": "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=","agent_id":"06480EC4-6BF3-4F00-8323-FE6AE5868297","cloudagent_ip":"127.0.0.1","cloudagent_port":"8882","tpm_policy": {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff"}}',

    def read_line_in_file(self, infile, line_number):
        with open(infile) as fp:
            for i, line in enumerate(fp):
                if i == line_number:
                    return line
        return ""

    def sleep_for_a_while(self, argument):
        time.sleep(float(argument))

    def test_concurrent_cloudnodiness_modify_request(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)
        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                request_body = test_functions.get("http_request_body")
                try:
                    json_request_body = json.loads(request_body)

                    tmpp_policy = json_request_body["tpm_policy"]

                    mask = 0
                    for key in list(tmpp_policy.keys()):
                        if key.isdigit():
                            mask = mask | (1 << int(key))

                    mask_str = "0x%X" % (mask)
                    tmpp_policy["mask"] = mask_str
                    json_request_body["tpm_policy"] = tmpp_policy

                    cloudagent_ip = json_request_body["cloudagent_ip"]
                    if cloudagent_ip.endswith(".txt"):
                        cloudagent_ip_file = cloudagent_ip
                        cloudagent_ip_read_from_file = self.read_line_in_file(cloudagent_ip_file, test_iteration)
                        json_request_body["cloudagent_ip"] = cloudagent_ip_read_from_file.strip()

                    cloudagent_port = json_request_body["cloudagent_port"]
                    if cloudagent_port.endswith(".txt"):
                        cloudagent_port_file = cloudagent_port
                        cloudagent_port_read_from_file = self.read_line_in_file(cloudagent_port_file, test_iteration)
                        json_request_body["cloudagent_port"] = cloudagent_port_read_from_file.strip()

                    #                     parser = ConfigParser.RawConfigParser()
                    #                     parser.read(common.CONFIG_FILE)
                    #                     test_agent_uuid = parser.get('general', 'agent_uuid')

                    test_agent_uuid = json_request_body["agent_id"]

                    port_string_length = len(str(json_request_body["cloudagent_port"]))
                    contrived_uuid = test_agent_uuid[:-port_string_length]
                    contrived_uuid = contrived_uuid + str(json_request_body["cloudagent_port"])
                    json_request_body["agent_id"] = contrived_uuid

                    test_functions["http_request_body"] = json.dumps(json_request_body)

                except Exception as e:
                    self.fail(
                        "Problem in test_concurrent_cloudnodiness_modify_request() replacing cloudagent_ip or cloudagent_port.  Error: %s"
                        % e
                    )

    def test_concurrent_cloudnodiness_reset_request(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):

        # time.sleep(2)

        test_record = self.test_table.get(test_method_name)
        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                request_body = test_functions.get("http_request_body")
                try:
                    json_request_body = json.loads(request_body)

                    # reset the request body to file arguments for next iteration
                    json_request_body["cloudagent_ip"] = argument["ip_file"]
                    json_request_body["cloudagent_port"] = argument["port_file"]

                    test_functions["http_request_body"] = json.dumps(json_request_body)

                except Exception as e:
                    self.fail(
                        "Problem in test_concurrent_cloudnodiness_modify_request() replacing cloudagent_ip or cloudagent_port.  Error: %s"
                        % e
                    )

    def test_check_persistance_file_empty(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)
        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                target_body = test_functions.get("http_result_body_actual")
                try:
                    jsondecoded = json.loads(target_body)
                    # test to make sure these two keys (and values) are in the return
                    if len(jsondecoded) != 0:
                        self.fail(
                            "Expected empty persistence file to replace non existent persistence file on startup."
                        )
                except Exception as e:
                    self.fail(
                        "Problem reading persistence file after replacement of empty persistence file.  Error: %s" % e
                    )

    def check_test_persistance_file_write(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)
        uuid_str = argument
        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                try:
                    with open(cv_persistence_filename) as persistance_file:
                        file_contents = persistance_file.read()

                        json_content = json.loads(file_contents)
                        if len(json_content) != 1 or json_content.get(uuid_str) is None:
                            self.fail("Unexpected persistence file contents.")
                except Exception as e:
                    self.fail("Problem reading persistence file after POST.  Error: %s" % e)
                try:
                    with open(cv_persistence_filename + ".bak") as backup_persistance_file:
                        backup_file_contents = backup_persistance_file.read()

                        json_backup_content = json.loads(backup_file_contents)
                        if len(json_backup_content) != 0:
                            self.fail("Unexpected backup persistence file contents.")
                except Exception as e:
                    self.fail("Problem reading backup persistence file after POST.  Error: %s" % e)

    def check_test_persistance_file_load(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)
        uuid_str = argument
        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                target_body = test_functions.get("http_result_body_actual")
                jsondecoded = json.loads(target_body)
                # test to make sure these two keys (and values) are in the return
                if len(jsondecoded) != 1 or jsondecoded.get(uuid_str) is None:
                    self.fail("Expected " + uuid_str + " to be in the list of active agent_ids")

    #     def do_mock_for_test_cloudverifier_tenant_provide_v(self, argument):
    #         global text_callback
    #         nonce = tpm_initialize.random_password(20)
    #         tpm_policy = {"00": "0000000000000000000000000000000000000000", "mask": "0x400801", "22": "ffffffffffffffffffffffffffffffffffffffff" }
    #         #theurl = 'http://' + cloudagent_ip + ':' + cloudagent_port + "/v1/quotes/cloudverifier" + "?nonce=" + nonce + "&mask=" + tpm_policy['mask']
    #         theurl = 'http://' + cloudagent_ip + ':' + cloudagent_port + "/v1/quotes/cloudverifier"
    #         with requests_mock.Mocker(real_http=True) as m:
    #             m.get(requests_mock.ANY, text=text_callback)

    def provide_e(self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument):
        test_record = self.test_table.get(test_method_name)

        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                response_body = test_functions.get("http_result_body_actual")
                jsondecoded = json.loads(response_body)

                public_key = jsondecoded.get("pubkey")
                quote = jsondecoded.get("quote")

                # test to make sure these two keys (and values) are in the return
                if public_key is None or quote is None:
                    self.fail("Expected both pubkey and quote arguments.")
                else:

                    mytenant = tenant.Tenant()

                    # command line options can overwrite config values
                    mytenant.cloudagent_ip = cloudagent_ip
                    mytenant.cloudverifier_ip = cloudverifier_ip
                    mytenant.agent_uuid = "C432FBB3-D2F1-4A97-9EF7-75BD81C866E9"

                    if mytenant.validate_tpm_quote(public_key, quote):
                        # encrypt U with the public key
                        global U, K

                        encrypted_U = crypto.rsa_encrypt(crypto.rsa_import_pubkey(public_key), str(U))
                        encrypt_check = crypto.do_hmac(K, mytenant.agent_uuid)
                        b64_encrypted_u = base64.b64encode(encrypted_U)
                        data = {"encrypted_key": b64_encrypted_u, "encrypt_check": encrypt_check}
                        u_json_message = json.dumps(data)

                        # post encrypted U back to Cloud Agent
                        response = tornado_requests.request(
                            "POST", f"http://{cloudagent_ip}:{cloudagent_port}/v1/quotes/tenant", data=u_json_message
                        )

                        if response.status_code != 200:
                            self.fail(
                                "Posting of Encrypted U to the Cloud Agent failed with response code %d"
                                % response.status_code
                            )
                    else:
                        self.fail("TPM Quote from cloud agent is invalid for nonce: %s" % self.nonce)

    def check_test_cloudagent_tenant_get_nonce(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)

        # perform each of the test functions and store the results
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                target_body = test_functions.get("http_result_body_actual")
                jsondecoded = json.loads(target_body)
                # test to make sure these two keys (and values) are in the return
                if jsondecoded.get("pubkey") is None or jsondecoded.get("quote") is None:
                    self.fail("Expected both pubkey and quote arguments.")

    def check_validate_test_cloudverifier_tenant_provide_v(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)

        # lookup test data and compare the results to canned values
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                target_body = test_functions.get("http_result_body_actual")
                jsondecoded = json.loads(target_body)

                v = jsondecoded.get("v")
                ip = jsondecoded.get("ip")
                port = jsondecoded.get("port")
                tpm_policy = jsondecoded.get("tpm_policy")

                if v is None or v != "nsRIy93UeeAi3GhAxpEcMH6R7OmaB7ArBdn2bEgyEwU=":
                    self.fail("Returned v from instance 06480EC4-6BF3-4F00-8323-FE6AE5868297 was not correct.")
                if ip is None or ip != "127.0.0.1":
                    self.fail("Returned ip from instance 06480EC4-6BF3-4F00-8323-FE6AE5868297 was not correct.")
                if port is None or port != "8882":
                    self.fail("Returned port from instance 06480EC4-6BF3-4F00-8323-FE6AE5868297 was not correct.")
                if tpm_policy is None or tpm_policy != {
                    "00": "0000000000000000000000000000000000000000",
                    "mask": "0x400801",
                    "22": "ffffffffffffffffffffffffffffffffffffffff",
                }:
                    self.fail("Returned tpm_policy from instance 06480EC4-6BF3-4F00-8323-FE6AE5868297 was not correct.")

    def check_and_delete_all_entries(
        self, test_method_name, test_function_name, state_change_or_validation, test_iteration, argument
    ):
        test_record = self.test_table.get(test_method_name)

        # lookup test data and compare the results to canned values
        for test_functions in test_record[state_change_or_validation]:
            if test_functions.get("function_name") == test_function_name:
                target_body = test_functions.get("http_result_body_actual")
                agent_id_list = json.loads(target_body)

                expected_len = argument
                actual_len = len(agent_id_list)
                if actual_len != expected_len:
                    self.fail("Expected " + str(expected_len) + " instance id's but received " + str(actual_len))

                for agent_id in agent_id_list:
                    params = {
                        "agent_id": agent_id,
                    }
                    try:
                        response = tornado_requests.request(
                            "DELETE",
                            "http://" + cloudverifier_ip + ":" + cloudverifier_port + "/v1/instances",
                            params=params,
                        )

                        if response.status_code != 200:
                            self.fail("Delete of agent_id " + agent_id + " failed.")

                    except Exception as e:
                        self.fail("Delete of agent_id " + agent_id + " failed with exception: %s" % e)

    def execute_the_test(self, setup_or_state_change_or_validation, test_functions, test_iteration):

        # call the pre_function
        pre_function = test_functions.get("pre_function")
        if pre_function is not None:
            pre_function_name = pre_function.get("name")
            pre_function_args = pre_function.get("args")
            # self._testMethodName, test_functions["function_name"], setup_or_state_change_or_validation, check_argument
            function_return = getattr(self, pre_function_name)(
                self._testMethodName,
                test_functions["function_name"],
                setup_or_state_change_or_validation,
                test_iteration,
                pre_function_args,
            )
            if not function_return:
                self.fail(
                    "Test "
                    + self._testMethodName
                    + ":"
                    + test_functions["function_name"]
                    + ":"
                    + pre_function_name
                    + " pre_function failure, test aborted."
                )

        full_url = (
            "http://"
            + test_functions.get("http_request_ip")
            + ":"
            + test_functions.get("http_request_port")
            + test_functions.get("http_request_path")
        )
        http_request_body_tag = test_functions.get("http_request_body")
        http_request_body_file_tag = test_functions.get("http_request_body_file")
        if http_request_body_tag is not None and http_request_body_file_tag is not None:
            self.fail(
                "Test "
                + self._testMethodName
                + ":"
                + test_functions["function_name"]
                + " contains both http_request_body and http_request_body_file tags."
            )

        thedata = ""
        if http_request_body_tag is None and http_request_body_file_tag is not None:
            thedata = open(http_request_body_file_tag).read()
        else:
            thedata = http_request_body_tag
        verb = test_functions.get("http_request_verb")
        query = test_functions.get("http_request_query", "")
        test_functions.get("http_request_header")
        req_header = test_functions.get("http_request_header")

        response = tornado_requests.request(verb, full_url, params=query, data=thedata, headers=req_header)

        temp = tempfile.TemporaryFile()
        for chunk in response.iter_content(1024):
            temp.write(chunk)

        temp.seek(0)
        # copy the results for future checking
        test_functions["http_result_status_actual"] = response.status_code
        test_functions["http_result_header_actual"] = response.headers
        test_functions["http_result_body_actual"] = temp.read()
        # validate response status
        if test_functions["http_result_status_actual"] != test_functions["http_result_status_expected"]:
            self.fail(
                "Test "
                + self._testMethodName
                + ":"
                + test_functions["function_name"]
                + " expected "
                + str(test_functions["http_result_status_expected"])
                + " but received "
                + str(test_functions["http_result_status_actual"])
            )  # reset the file marker for reading
        # validate response headers
        if test_functions.get("http_result_header_expected") is not None and not all(
            item in list(response.headers.items())
            for item in list(test_functions["http_result_header_expected"].items())
        ):
            self.fail(
                "Test "
                + self._testMethodName
                + ":"
                + test_functions["function_name"]
                + ", didn't receive expected headers."
            )
        # validate (shallow) response body
        if test_functions.get("http_result_body_expected") is not None and json.loads(
            test_functions.get("http_result_body_expected")
        ) != json.loads(test_functions.get("http_result_body_actual")):
            self.fail(
                "Test "
                + self._testMethodName
                + ":"
                + test_functions["function_name"]
                + ", didn't receive exact expected result body."
            )
        # validate (deep) response body
        check_function = test_functions.get("check_function")
        if check_function is not None:
            check_argument = check_function.get("argument")
            if getattr(self, check_function["name"])(
                self._testMethodName,
                test_functions["function_name"],
                setup_or_state_change_or_validation,
                test_iteration,
                check_argument,
            ):
                self.fail(
                    "Test "
                    + self._testMethodName
                    + ":"
                    + test_functions["function_name"]
                    + ", didn't receive exact expected result body."
                )

        # call the post_function
        post_function = test_functions.get("post_function")
        if post_function is not None:
            post_function_name = post_function.get("name")
            post_function_args = post_function.get("args")
            function_return = getattr(self, post_function_name)(
                self._testMethodName,
                test_functions["function_name"],
                setup_or_state_change_or_validation,
                test_iteration,
                post_function_args,
            )
            if not function_return:
                self.fail(
                    "Test "
                    + self._testMethodName
                    + ":"
                    + test_functions["function_name"]
                    + ":"
                    + post_function_name
                    + " post_function failure, test aborted."
                )

        temp.close()

    def request_task(self, queue, setup_or_state_change_or_validation, test_functions, test_iteration):
        try:
            # Table data does not provide ability to inject unique agent_id's for each concurrent instance.
            # The queue stores unique agent_id objects, injected by the new_thread function.
            # Get the agent_id from the Queue and modify the original table data to change the agent_id to something unique.
            http_request_body_tag = test_functions.get("http_request_body")
            http_request_body_file_tag = test_functions.get("http_request_body_file")
            if http_request_body_tag is not None and http_request_body_file_tag is not None:
                self.fail(
                    "Test "
                    + self._testMethodName
                    + ":"
                    + test_functions["function_name"]
                    + " contains both http_request_body and http_request_body_file tags."
                )

            thedata = ""
            if http_request_body_tag is None and http_request_body_file_tag is not None:
                thedata = open(http_request_body_file_tag).read()
            else:
                thedata = http_request_body_tag

            the_uid = queue.get()
            jsondata = json.loads(thedata)
            jsondata["agent_id"] = the_uid
            newdata = json.dumps(jsondata)

            # call the inline task passing the new data with the unique agent_id
            self.execute_the_test(setup_or_state_change_or_validation, test_functions, test_iteration)

        except Exception as e:
            self.fail(
                "Test "
                + self._testMethodName
                + ":"
                + test_functions["function_name"]
                + ", unexpected exception error: %s" % e
            )
        finally:
            queue.task_done()

    def modify_persistence_file(self, argument):
        string_to_write = None
        if isinstance(argument, dict):
            string_to_write = json.dumps(argument)
        elif isinstance(argument, str):
            string_to_write = argument
        elif isinstance(argument, _io.TextIOWrapper):
            string_to_write = argument.read()
            argument.close()
        elif argument is None:
            if os.path.isfile(cv_persistence_filename):
                os.remove(cv_persistence_filename)

        if string_to_write is not None:
            with open(cv_persistence_filename, "w") as persistance_file:
                persistance_file.write(string_to_write)

        backup_file_name = cv_persistence_filename + ".bak"
        if os.path.isfile(backup_file_name):
            os.remove(backup_file_name)

    def launch_cloudverifier(self, argument):

        readKUV()

        # modify the persistence file per the passed argument
        if argument is not None:
            self.modify_persistence_file(argument)

        global cv_process
        cv_process = subprocess.Popen("python cloud_verifier.py", shell=True)
        time.sleep(1)
        return True

    def overwrite_config_file(self, path, section, option, value):
        parser = configparser.RawConfigParser()
        parser.read(path)

        parser.set(section, option, value)

        # Writing our configuration file to 'example.ini'
        with open(path, "wb") as configfile:
            parser.write(configfile)

    def launch_cloudagents(self, argument):

        # self.launch_cloudverifier(None)
        port_file = argument.get("port_file")
        cloudagent_start_port = argument.get("starting_port")
        num_cloudagent_instances = argument["num_cloudagent_instances"]

        if cloudagent_start_port is not None:

            parser = configparser.RawConfigParser()
            parser.read(common.CONFIG_FILE)
            original_cloudagent_port = parser.get("general", "cloudagent_port")
            test_agent_uuid = parser.get("general", "agent_uuid")

            for cn in range(num_cloudagent_instances):
                new_dir = r"../cloudagent_on_port_" + str(cloudagent_start_port)
                config_file_path = new_dir + "/keylime.conf"
                copy_tree(".", new_dir)
                shutil.copyfile(common.CONFIG_FILE, config_file_path)
                if not os.path.isdir(new_dir):
                    os.mkdir(new_dir)
                # shutil.copyfile(r'../keylime.conf', new_dir + r'/keylime.conf')
                self.overwrite_config_file(config_file_path, "general", "cloudagent_port", str(cloudagent_start_port))
                port_string_length = len(str(cloudagent_start_port))
                contrived_uuid = test_agent_uuid[:-port_string_length]
                contrived_uuid = contrived_uuid + str(cloudagent_start_port)
                self.overwrite_config_file(config_file_path, "general", "agent_uuid", contrived_uuid)

                cn_process_list.append(
                    subprocess.Popen("python cloud_agent.py", shell=True, cwd=new_dir, preexec_fn=os.setsid).pid
                )
                cloudagent_start_port = cloudagent_start_port + 1
                # time.sleep(2)

            self.overwrite_config_file(common.CONFIG_FILE, "general", "cloudagent_port", str(original_cloudagent_port))

        elif port_file is not None:

            parser = configparser.RawConfigParser()
            parser.read(common.CONFIG_FILE)
            original_cloudagent_port = parser.get("general", "cloudagent_port")
            test_agent_uuid = parser.get("general", "agent_uuid")

            for cn in range(num_cloudagent_instances):
                cloudagent_port_read_from_file = self.read_line_in_file(port_file, cn).strip()

                new_dir = r"../cloudagent_on_port_" + cloudagent_port_read_from_file
                config_file_path = new_dir + "/keylime.conf"
                copy_tree(".", new_dir)
                shutil.copyfile(common.CONFIG_FILE, config_file_path)
                if not os.path.isdir(new_dir):
                    os.mkdir(new_dir)
                # shutil.copyfile(r'../keylime.conf', new_dir + r'/keylime.conf')
                self.overwrite_config_file(
                    config_file_path, "general", "cloudagent_port", cloudagent_port_read_from_file
                )
                port_string_length = len(cloudagent_port_read_from_file)
                contrived_uuid = test_agent_uuid[:-port_string_length]
                contrived_uuid = contrived_uuid + cloudagent_port_read_from_file
                self.overwrite_config_file(config_file_path, "general", "agent_uuid", contrived_uuid)

                cn_process_list.append(
                    subprocess.Popen("python cloud_agent.py", shell=True, cwd=new_dir, preexec_fn=os.setsid).pid
                )
                cloudagent_port = int(cloudagent_port_read_from_file) + 1
                # time.sleep(2)

            self.overwrite_config_file(common.CONFIG_FILE, "general", "cloudagent_port", str(original_cloudagent_port))
        print("done creating cloud agents, waiting for them to start...")
        time.sleep(10)
        print("starting test...")

    def kill_cloudagents_after_delay(self, argument):

        sleep_time = argument.get("sleep")
        time.sleep(sleep_time)

        # self.launch_cloudverifier(None)
        port_file = argument.get("port_file")
        cloudagent_start_port = argument.get("starting_port")
        num_cloudagent_instances = argument["num_cloudagent_instances"]

        if cloudagent_start_port is not None:

            parser = configparser.RawConfigParser()
            parser.read(common.CONFIG_FILE)

            for cn in range(num_cloudagent_instances):
                new_dir = r"../cloudagent_on_port_" + str(cloudagent_start_port)
                shutil.rmtree(new_dir)
                cloudagent_port = cloudagent_start_port + 1

        elif port_file is not None:

            parser = configparser.RawConfigParser()
            parser.read(common.CONFIG_FILE)
            test_agent_uuid = parser.get("general", "agent_uuid")

            for cn in range(num_cloudagent_instances):
                cloudagent_port_read_from_file = self.read_line_in_file(port_file, cn).strip()
                port_string_length = len(cloudagent_port_read_from_file)
                contrived_uuid = test_agent_uuid[:-port_string_length]
                contrived_uuid = contrived_uuid + cloudagent_port_read_from_file
                params = {
                    "agent_id": contrived_uuid,
                }
                try:
                    print("Sending #" + str(cn) + " DELETE request to CV for uuid: " + contrived_uuid)
                    response = tornado_requests.request(
                        "DELETE",
                        "http://" + cloudverifier_ip + ":" + cloudverifier_port + "/v1/instances",
                        params=params,
                    )

                    if response.status_code != 200:
                        self.fail("Delete of agent_id " + contrived_uuid + " failed.")

                except Exception as e:
                    self.fail("Delete of agent_id " + contrived_uuid + " failed with exception: %s" % e)

            for cn in range(num_cloudagent_instances):
                cloudagent_port_read_from_file = self.read_line_in_file(port_file, cn).strip()
                new_dir = r"../cloudagent_on_port_" + cloudagent_port_read_from_file
                shutil.rmtree(new_dir)

        for the_pid in cn_process_list:
            print("killing pid" + str(the_pid))
            os.killpg(the_pid, signal.SIGTERM)

    def kill_cloudverifier(self, argument):
        cv_process.kill()
        return True

    def launch_cloudagent(self, argument):

        readKUV()

        global cn_process
        cn_process = subprocess.Popen("python cloud_agent.py", shell=True)
        time.sleep(1)
        return True

    def kill_cloudagent(self, argument):
        cn_process.kill()
        return True

    def launch_required_servers(self, argument):
        self.launch_cloudagent(argument)
        self.launch_cloudverifier(argument)
        return True

    def kill_required_servers(self, argument):
        self.kill_cloudagent(argument)
        self.kill_cloudverifier(argument)
        return True

    def new_thread(self, args):
        # create a new uuid, and place it in the queue
        the_global_queue = args[0]
        new_uuid = str(uuid.uuid4())
        the_global_queue.put(new_uuid)

        return threading.Thread(target=self.request_task, args=args)

    def execute_test_function_set(self, setup_or_state_change_or_validation):
        # look up the test record
        test_record = self.test_table.get(self._testMethodName)
        # perform each of the test functions and store the results
        change_or_validation = test_record.get(setup_or_state_change_or_validation)
        if change_or_validation is not None:
            for test_functions in test_record[setup_or_state_change_or_validation]:

                #                 full_url = "http://" + test_functions.get("http_request_ip") + ":" + test_functions.get("http_request_port") + test_functions.get("http_request_path")
                #                 http_request_body_tag = test_functions.get("http_request_body")
                #                 http_request_body_file_tag = test_functions.get("http_request_body_file")
                #                 if http_request_body_tag != None and http_request_body_file_tag != None :
                #                     self.fail("Test " + self._testMethodName + ":" + test_functions["function_name"] + " contains both http_request_body and http_request_body_file tags." )
                #
                #                 thedata = ''
                #                 if http_request_body_tag == None and http_request_body_file_tag != None:
                #                     thedata = open(http_request_body_file_tag).read()
                #                 else:
                #                     thedata=http_request_body_tag
                #                 verb =  test_functions.get("http_request_verb")
                #                 query = test_functions.get("http_request_query","")
                #                 test_functions.get("http_request_header")
                #                 req_header = test_functions.get("http_request_header")

                concurrent_instances = None
                concurrent_new_thread_function = None

                concurrency_dict = test_functions.get("concurrency")
                if concurrency_dict is not None:
                    concurrent_instances = concurrency_dict.get("instances")
                    concurrent_new_thread_function = concurrency_dict.get("new_thread_function")

                    if concurrent_instances is None or concurrent_new_thread_function is None:
                        self.fail(
                            "Test "
                            + self._testMethodName
                            + ":"
                            + test_functions["function_name"]
                            + ' contains concurrency agent without mandatory \\"instances\\" or and \\"new_thread_function\\" specifiers'
                        )

                for test_iteration in range(int(test_functions.get("test_iterations", "1"))):

                    if concurrent_instances is None:

                        # do it inline on this thread
                        self.execute_the_test(setup_or_state_change_or_validation, test_functions, test_iteration)

                    else:

                        threads = []
                        for count in range(concurrent_instances):
                            args = (queue, setup_or_state_change_or_validation, test_functions, test_iteration)
                            # call the new_thread_function specified in the test table under concurrency tag.
                            # the new_thread_function is responsible for setting up the task, and creating the new thread.
                            # the task given to the thread must not block and call task_done() on completion regardless of success or failure
                            new_thread = getattr(self, concurrent_new_thread_function)(args)
                            threads.append(new_thread)

                        # start the threads
                        for t in threads:
                            t.start()

                        # blocks until all tasks have called task_done()
                        queue.join()

                        # blocks until all threads are complete
                        for t in threads:
                            t.join()

    def execute_test_definition(self):
        test_record = self.test_table.get(self._testMethodName)
        prerun_function_dict = test_record.get("prerun_function")
        if prerun_function_dict is not None:
            prerun_function_name = prerun_function_dict.get("name")
            prerun_function_args = prerun_function_dict.get("args")
            function_return = getattr(self, prerun_function_name)(prerun_function_args)

        self.execute_test_function_set("setup_functions")
        self.execute_test_function_set("state_change_functions")
        self.execute_test_function_set("state_validation_functions")
        postrun_function_dict = test_record.get("postrun_function")

        if postrun_function_dict is not None:
            postrun_function_name = postrun_function_dict.get("name")
            postrun_function_args = postrun_function_dict.get("args")
            function_return = getattr(self, postrun_function_name)(postrun_function_args)

    def setUp(self):
        pass

    def tearDown(self):
        # os.killpg(self.cloudverifier_process.pid, signal.SIGKILL)
        pass


if __name__ == "__main__":
    # import sys;sys.argv = ['', 'Test.testName']
    unittest.main()
