176 lines
5.4 KiB
Python
Executable File
176 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
import io
|
|
import argparse
|
|
import subprocess
|
|
import signal
|
|
import time
|
|
from threading import Thread
|
|
|
|
if sys.version_info < (3, 0, 0):
|
|
print(__file__ + ' requires Python 3, while Python ' +
|
|
str(sys.version[0] + ' was detected. Terminating. '))
|
|
sys.exit(1)
|
|
|
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
veilid_server_exe_debug = os.path.join(script_dir, '..',
|
|
'target', 'debug', 'veilid-server')
|
|
veilid_server_exe_release = os.path.join(
|
|
script_dir, '..', 'target', 'release', 'veilid-server')
|
|
main_process = None
|
|
subindex_processes = []
|
|
|
|
try:
|
|
# Python 3, open as binary, then wrap in a TextIOWrapper with write-through.
|
|
sys.stdout = io.TextIOWrapper(
|
|
open(sys.stdout.fileno(), 'wb', 0), write_through=True)
|
|
sys.stderr = io.TextIOWrapper(
|
|
open(sys.stderr.fileno(), 'wb', 0), write_through=True)
|
|
except TypeError:
|
|
# Python 2
|
|
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
|
|
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
|
|
|
|
|
|
def tee(prefix, infile, *files):
|
|
"""Print `infile` to `files` in a separate thread."""
|
|
|
|
def fanout(prefix, infile, *files):
|
|
with infile:
|
|
for line in iter(infile.readline, b""):
|
|
for f in files:
|
|
f.write(prefix + line)
|
|
f.flush()
|
|
|
|
t = Thread(target=fanout, args=(prefix, infile,) + files)
|
|
t.daemon = True
|
|
t.start()
|
|
return t
|
|
|
|
|
|
def read_until_interface_dial_info(proc, proto):
|
|
|
|
interface_dial_info_str = b"Local Dial Info: "
|
|
for ln in iter(proc.stdout.readline, ""):
|
|
sys.stdout.buffer.write(ln)
|
|
sys.stdout.flush()
|
|
|
|
idx = ln.find(interface_dial_info_str)
|
|
if idx != -1:
|
|
idx += len(interface_dial_info_str)
|
|
di = ln[idx:]
|
|
if b"@"+bytes(proto)+b"|" in di:
|
|
return di.decode("utf-8").strip()
|
|
|
|
return None
|
|
|
|
|
|
class CleanChildProcesses:
|
|
def __enter__(self):
|
|
os.setpgrp() # create new process group, become its leader
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
try:
|
|
os.killpg(0, signal.SIGKILL) # kill all processes in my group
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
|
|
def main():
|
|
threads = []
|
|
|
|
# Parse arguments
|
|
parser = argparse.ArgumentParser(description='Run veilid servers locally')
|
|
parser.add_argument("count", type=int,
|
|
help='number of instances to run')
|
|
parser.add_argument("--release", action='store_true',
|
|
help='use release mode build')
|
|
parser.add_argument("--log_trace", action='store_true',
|
|
help='use trace logging')
|
|
parser.add_argument("--log_info", action='store_true',
|
|
help='use info logging')
|
|
parser.add_argument("-w", "--wait-for-debug", action='append',
|
|
help='specify subnode index to wait for the debugger')
|
|
parser.add_argument("--config-file", type=str,
|
|
help='configuration file to specify for the bootstrap node')
|
|
parser.add_argument("--protocol", type=str, default="udp",
|
|
help='default protocol to choose for dial info')
|
|
args = parser.parse_args()
|
|
|
|
if args.count < 1:
|
|
print("Must specify more than one instance")
|
|
sys.exit(1)
|
|
|
|
veilid_server_exe = None
|
|
if args.release:
|
|
veilid_server_exe = veilid_server_exe_release
|
|
else:
|
|
veilid_server_exe = veilid_server_exe_debug
|
|
|
|
base_args = [veilid_server_exe]
|
|
if args.log_info:
|
|
pass
|
|
elif args.log_trace:
|
|
base_args.append("--trace")
|
|
else:
|
|
base_args.append("--debug")
|
|
|
|
if args.config_file:
|
|
base_args.append("--config-file={}".format(args.config_file))
|
|
|
|
# Run primary node and get node id
|
|
main_args = base_args.copy()
|
|
if args.wait_for_debug and ("0" in args.wait_for_debug):
|
|
main_args.append("--wait-for-debug")
|
|
|
|
print("Running main node: {}".format(str(main_args)))
|
|
main_proc = subprocess.Popen(
|
|
main_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
print(">>> MAIN NODE PID={}".format(main_proc.pid))
|
|
|
|
main_di = read_until_interface_dial_info(
|
|
main_proc, bytes(args.protocol, 'utf-8'))
|
|
|
|
print(">>> MAIN DIAL INFO={}".format(main_di))
|
|
|
|
threads.append(
|
|
tee(b"Veilid-0: ", main_proc.stdout, open("/tmp/veilid-0-out", "wb"),
|
|
getattr(sys.stdout, "buffer", sys.stdout))
|
|
)
|
|
|
|
# Run all secondaries and add primary to bootstrap
|
|
for n in range(1, args.count):
|
|
|
|
time.sleep(1)
|
|
|
|
sub_args = base_args.copy()
|
|
sub_args.append("--subnode-index={}".format(n))
|
|
sub_args.append("--bootstrap-nodes={}".format(main_di))
|
|
if args.wait_for_debug and (str(n) in args.wait_for_debug):
|
|
sub_args.append("--wait-for-debug")
|
|
|
|
print("Running subnode {}: {}".format(n, str(sub_args)))
|
|
|
|
sub_proc = subprocess.Popen(
|
|
sub_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
print(">>> SUBNODE {} NODE PID={}".format(n, sub_proc.pid))
|
|
|
|
threads.append(
|
|
tee("Veilid-{}: ".format(n).encode("utf-8"), sub_proc.stdout, open("/tmp/veilid-{}-out".format(n), "wb"),
|
|
getattr(sys.stdout, "buffer", sys.stdout))
|
|
)
|
|
|
|
for t in threads:
|
|
t.join() # wait for IO completion
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
with CleanChildProcesses():
|
|
main()
|