#!/usr/bin/env python3
"""
Run integration-e2e inside the shadow network simulator. This can be helpful
vs running it directly for several reasons.

* shadow simulates time, and can collapse idle time. This speeds up the
  network bootstrapping step in particular.
* shadow tries to be deterministic. There are some gaps, but in general
  there *should* be less nondeterministic flakiness under shadow
  than when running natively.
"""

import os
import pathlib
import shutil
import subprocess
import sys
import yaml

SHADOW_DATA_DIR='shadow.chutney.data'
SHADOW_CONFIG_FILE='shadow.chutney.yaml'
SHADOW_LOG_FILE='shadow.log'
# must be syncd with `TEST_DOMAIN` in `tests/chutney/test`.
TEST_DOMAIN='example.com'

def gen_shadow_config(**args):
    """
    Generate a shadow config file, as a string, for the given parameters.
    """

    return {
        'general': {
            'stop_time': '10m',
        },
        'network': {
            'graph': {
                'type': '1_gbit_switch'
            },
        },
        'hosts': {
            'host': {
                'network_node_id': 0,
                'processes': [{
                    'path': './tests/chutney/integration-e2e',
                    'environment': {
                        'RUNNING_IN_SHADOW': 'yes',
                        # unix sockets aren't supported in shadow
                        'CHUTNEY_ENABLE_CONTROLSOCKET': 'no',
                        # ipv6 isn't supported in shadow
                        'CHUTNEY_DISABLE_IPV6': 'yes',
                        # re-export PATH. The test scripts assume that usual shell utilities are on it.
                        'PATH': os.getenv('PATH'),
                        'CHUTNEY_PATH': args['chutney_path'],
                    },
                    # Give the web server below a little time to start.
                    'start_time': '5s',
                }],
            },
            TEST_DOMAIN: {
                'network_node_id': 0,
                'processes': [{
                    'path': 'python3',
                    'args': '-m http.server 80',
                    'start_time': '0',
                    'expected_final_state': 'running',
                }],
            }
        }
    }

def main():
    toplevel = pathlib.Path(os.fsdecode(subprocess.check_output("git rev-parse --show-toplevel", shell=True)).strip())
    os.chdir(toplevel)

    # Get CHUTNEY_PATH and ensure that it's set.
    # We don't want the script trying to clone or update chutney from inside the
    # shadow simulation, since it'll fail.
    chutney_path = os.getenv('CHUTNEY_PATH') or sys.exit("Missing environment variable CHUTNEY_PATH")

    # Write out shadow config. We could just pipe it directly to the shadow
    # process below, but writing it out is useful for debugging.
    shadow_config = gen_shadow_config(chutney_path=chutney_path)
    with open(SHADOW_CONFIG_FILE, 'w') as f:
        f.write(yaml.safe_dump(shadow_config))

    # Remove shadow's data dir. (It will bail if the directory already exists)
    if os.path.isdir(SHADOW_DATA_DIR):
        shutil.rmtree(SHADOW_DATA_DIR)

    shadow_args = [
        'shadow',
        '--data-directory=' + SHADOW_DATA_DIR,
        '--progress=true',
        '--model-unblocked-syscall-latency=true',
        SHADOW_CONFIG_FILE
        ]
    with open(SHADOW_LOG_FILE, 'w') as shadow_log_file:
        subprocess.run(shadow_args, check=True, stdout=shadow_log_file)

if __name__ == '__main__':
    main()
