blob: 31562414d4ed33232580d11168520cd510dcc773 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import multiprocessing
import os
import pwd
import subprocess
from twisted.internet import reactor
from packet_queue import monitoring
from packet_queue import nfqueue
from packet_queue import simulation
ENGINE_PORT = 25467
MTU = 2048
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
'--target', type=str, required=True,
help='path of the test binary to run')
parser.add_argument(
'--gtest_filter', type=str, default='EngineBrowserTest.LoadUrl',
help='which specific test to run')
parser.add_argument(
'-w', '--bandwidth', type=int, default=-1,
help='bandwidth cap in bytes/second, unlimited by default')
parser.add_argument(
'-b', '--buffer', type=int, default=-1,
help='buffer size in bytes, unlimited by default')
parser.add_argument(
'-d', '--delay', type=float, default=0.0,
help='one-way packet delay in seconds')
parser.add_argument(
'-l', '--loss', type=float, default=0.0,
help='probability of random packet loss')
return parser.parse_args()
def run_packet_queue(params, ready_event):
"""Runs the packet queue with parameters from command-line args.
This function runs the Twisted reactor, which blocks until the process is
interrupted.
"""
event_log = monitoring.EventLog()
pipes = simulation.PipePair(params, event_log)
nfqueue.configure('tcp', ENGINE_PORT, pipes, 'lo')
reactor.callWhenRunning(ready_event.set)
reactor.addSystemEventTrigger(
'before', 'shutdown', report_stats, event_log)
reactor.run()
def report_stats(event_log):
"""Prints accumulated network stats to stdout."""
dropped = 0
delivered = 0
for event in event_log.get_pending():
if event['type'] == 'drop':
dropped += event['value']
elif event['type'] == 'deliver':
delivered += event['value']
print '%i bytes delivered' % delivered
print '%i bytes dropped' % dropped
def set_chrome_test_user():
"""If the SUDO_USER environment variable exists, sets the user to that.
This is required because Chrome browser tests won't run as root.
If SUDO_USER isn't present, we might be running as a non-root user with
the CAP_NET_ADMIN capability, so the user won't change.
"""
sudo_user = os.getenv('SUDO_USER')
if sudo_user:
uid = pwd.getpwnam(sudo_user).pw_uid
os.setuid(uid)
def main():
"""Runs a Blimp test target with network impairment.
This works by forking to run the packet queue, and then running the test
binary in a subprocess when the packet queue is ready.
"""
args = parse_args()
simulation_params = {
'bandwidth': args.bandwidth,
'buffer': args.buffer,
'delay': args.delay,
'loss': args.loss,
}
ready_event = multiprocessing.Event()
packet_queue = multiprocessing.Process(
target=run_packet_queue, args=(simulation_params, ready_event))
if subprocess.call(['ip', 'link', 'set', 'dev', 'lo', 'mtu', str(MTU)]):
raise OSError('Failed to set MTU')
packet_queue.start()
ready_event.wait(timeout=3)
subprocess.call([
args.target,
'--disable-setuid-sandbox',
'--single_process',
'--engine-port=%i' % ENGINE_PORT,
'--gtest_filter=%s' % args.gtest_filter,
], preexec_fn=set_chrome_test_user)
packet_queue.terminate()
if __name__ == '__main__':
main()