コード例 #1
0
ファイル: gce.py プロジェクト: zirrtu/observatory
def main():
    config = utils.parse_vantage_points()

    parser = argparse.ArgumentParser()
    parser.add_argument('action', choices=['start', 'stop', 'status'])
    parser.add_argument('server_type', choices=['gce_servers', 'emu_servers'])
    args = parser.parse_args()

    procs = []
    for server, server_cfg in config[args.server_type].iteritems():
        instance_name = server_cfg['name']
        zone = server_cfg['zone']

        if args.action == 'start':
            procs.append(Popen(
                ['gcloud', 'compute', 'instances', 'start',
                 instance_name, '--zone', zone]))
        elif args.action == 'stop':
            procs.append(Popen(
                ['gcloud', 'compute', 'instances', 'stop',
                 instance_name, '--zone', zone]))
        elif args.action == 'status':
            procs.append(Popen(
                ['gcloud', 'compute', 'instances', 'describe',
                 instance_name, '--zone', zone],
                stdout=PIPE))

    for proc in procs:
        if args.action == 'status':
            status, _ = proc.communicate()
            print(yaml.load(status)['status'])
        else:
            proc.wait()
コード例 #2
0
ファイル: test.py プロジェクト: JerryLX/pantheon
    def run_without_tunnel(self):
        port = utils.get_open_port()

        # run the side specified by self.run_first
        cmd = ['python', self.cc_src, self.run_first, port]
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_first))
        self.proc_first = Popen(cmd, preexec_fn=os.setsid)

        # sleep just in case the process isn't quite listening yet
        # the cleaner approach might be to try to verify the socket is open
        time.sleep(self.run_first_setup_time)

        self.test_start_time = utils.utc_time()
        # run the other side specified by self.run_second
        sh_cmd = 'python %s %s $MAHIMAHI_BASE %s' % (self.cc_src,
                                                     self.run_second, port)
        sh_cmd = ' '.join(self.mm_cmd) + " -- sh -c '%s'" % sh_cmd
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_second))
        self.proc_second = Popen(sh_cmd, shell=True, preexec_fn=os.setsid)

        signal.signal(signal.SIGALRM, utils.timeout_handler)
        signal.alarm(self.runtime)

        try:
            self.proc_first.wait()
            self.proc_second.wait()
        except utils.TimeoutError:
            pass
        else:
            signal.alarm(0)
            sys.stderr.write('Warning: test exited before time limit\n')
        finally:
            self.test_end_time = utils.utc_time()

        return True
コード例 #3
0
    def run_tunnel_managers(self):
        # run tunnel server manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                ts_manager_cmd = ['python', self.tunnel_manager]
            else:
                ts_manager_cmd = self.r['ssh_cmd'] + [
                    '"python', self.r['tunnel_manager'], '"'
                ]
        else:
            ts_manager_cmd = ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel server manager (tsm)] ')
        self.ts_manager = Popen(ts_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid,
                                shell=True)

        ts_manager = self.ts_manager

        while True:
            running = ts_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        ts_manager.stdin.write('prompt [tsm]\n')
        ts_manager.stdin.flush()

        # run tunnel client manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                tc_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']
                ]
            else:
                tc_manager_cmd = ['python', self.tunnel_manager]
        else:
            tc_manager_cmd = self.mm_cmd + ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel client manager (tcm)] ')
        self.tc_manager = Popen(tc_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid,
                                shell=True)
        tc_manager = self.tc_manager

        while True:
            running = tc_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        tc_manager.stdin.write('prompt [tcm]\n')
        tc_manager.stdin.flush()

        os.system("sudo sysctl -w net.ipv4.ip_forward=1")
        return ts_manager, tc_manager
コード例 #4
0
ファイル: test.py プロジェクト: StanfordLPNG/pantheon
    def run_without_tunnel(self):
        port = utils.get_open_port()

        # run the side specified by self.run_first
        cmd = ['python', self.cc_src, self.run_first, port]
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_first))
        self.proc_first = Popen(cmd, preexec_fn=os.setsid)

        # sleep just in case the process isn't quite listening yet
        # the cleaner approach might be to try to verify the socket is open
        time.sleep(self.run_first_setup_time)

        self.test_start_time = utils.utc_time()
        # run the other side specified by self.run_second
        sh_cmd = 'python %s %s $MAHIMAHI_BASE %s' % (
            self.cc_src, self.run_second, port)
        sh_cmd = ' '.join(self.mm_cmd) + " -- sh -c '%s'" % sh_cmd
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_second))
        self.proc_second = Popen(sh_cmd, shell=True, preexec_fn=os.setsid)

        signal.signal(signal.SIGALRM, utils.timeout_handler)
        signal.alarm(self.runtime)

        try:
            self.proc_first.wait()
            self.proc_second.wait()
        except utils.TimeoutError:
            pass
        else:
            signal.alarm(0)
            sys.stderr.write('Warning: test exited before time limit\n')
        finally:
            self.test_end_time = utils.utc_time()

        return True
コード例 #5
0
def test_schemes(args):
    wrappers_dir = path.join(context.src_dir, 'wrappers')

    if args.all:
        schemes = utils.parse_config()['schemes'].keys()
    elif args.schemes is not None:
        schemes = args.schemes.split()

    for scheme in schemes:
        sys.stderr.write('Testing %s...\n' % scheme)
        src = path.join(wrappers_dir, scheme + '.py')

        run_first = check_output([src, 'run_first']).strip()
        run_second = 'receiver' if run_first == 'sender' else 'sender'

        port = utils.get_open_port()

        # run first to run
        cmd = [src, run_first, port]
        first_proc = Popen(cmd, preexec_fn=os.setsid)

        # wait for 'run_first' to be ready
        time.sleep(3)

        # run second to run
        cmd = [src, run_second, '127.0.0.1', port]
        second_proc = Popen(cmd, preexec_fn=os.setsid)

        # test lasts for 3 seconds
        signal.signal(signal.SIGALRM, utils.timeout_handler)
        signal.alarm(3)

        try:
            for proc in [first_proc, second_proc]:
                proc.wait()
                if proc.returncode != 0:
                    sys.exit('%s failed in tests' % scheme)
        except utils.TimeoutError:
            pass
        except Exception as exception:
            sys.exit('test_schemes.py: %s\n' % exception)
        else:
            signal.alarm(0)
            sys.exit('test exited before time limit')
        finally:
            # cleanup
            utils.kill_proc_group(first_proc)
            utils.kill_proc_group(second_proc)
コード例 #6
0
def stop_hosts():
    if expt_type == 'node':
        # run aws.py
        aws_py = path.join(context.scripts_dir, 'aws.py')
        with open(os.devnull, 'w') as DEVNULL:
            Popen([aws_py, 'stop'], stdout=DEVNULL, stderr=DEVNULL).wait()

    else:
        if expt_type == 'cloud':
            gce_server_type = 'gce_servers'
        elif expt_type == 'emu':
            gce_server_type = 'emu_servers'

        # run gce.py
        gce_py = path.join(context.scripts_dir, 'gce.py')
        with open(os.devnull, 'w') as DEVNULL:
            Popen([gce_py, 'stop', gce_server_type],
                  stdout=DEVNULL,
                  stderr=DEVNULL).wait()
コード例 #7
0
ファイル: utils.py プロジェクト: zirrtu/observatory
def execute(host_cmd):
    """ Given 'host_cmd' with form: {host: command_to_run_over_SSH, ...},
        return {host: boolean (indicating success or failure), ... }
    """

    host_proc = {}
    for host, cmd in host_cmd.iteritems():
        host_proc[host] = Popen(ssh_cmd(host) + [cmd])

    for host in host_proc:
        host_proc[host] = True if host_proc[host].wait() == 0 else False

    return host_proc
コード例 #8
0
ファイル: test.py プロジェクト: JerryLX/pantheon
class Test(object):
    def __init__(self, args, run_id, cc):
        self.mode = args.mode
        self.run_id = run_id
        self.cc = cc
        self.data_dir = path.abspath(args.data_dir)
        self.extra_sender_args = args.extra_sender_args

        # shared arguments between local and remote modes
        self.flows = args.flows
        self.runtime = args.runtime
        self.interval = args.interval
        self.run_times = args.run_times

        # used for cleanup
        self.proc_first = None
        self.proc_second = None
        self.ts_manager = None
        self.tc_manager = None

        self.test_start_time = None
        self.test_end_time = None

        # local mode
        if self.mode == 'local':
            self.datalink_trace = args.uplink_trace
            self.acklink_trace = args.downlink_trace
            self.prepend_mm_cmds = args.prepend_mm_cmds
            self.append_mm_cmds = args.append_mm_cmds
            self.extra_mm_link_args = args.extra_mm_link_args

            # for convenience
            self.sender_side = 'remote'
            self.server_side = 'local'

        # remote mode
        if self.mode == 'remote':
            self.sender_side = args.sender_side
            self.server_side = args.server_side
            self.local_addr = args.local_addr
            self.local_if = args.local_if
            self.remote_if = args.remote_if
            self.local_desc = args.local_desc
            self.remote_desc = args.remote_desc

            self.ntp_addr = args.ntp_addr
            self.local_ofst = None
            self.remote_ofst = None

            self.r = utils.parse_remote_path(args.remote_path, self.cc)

        # arguments when there's a config
        self.test_config = None
        if hasattr(args, 'test_config'):
            self.test_config = args.test_config

        if self.test_config is not None:
            self.cc = self.test_config['test-name']
            self.flow_objs = {}
            cc_src_remote_dir = ''
            if self.mode == 'remote':
                cc_src_remote_dir = r['base_dir']

            tun_id = 1
            for flow in args.test_config['flows']:
                cc = flow['scheme']
                run_first, run_second = utils.who_runs_first(cc)

                local_p = path.join(context.src_dir, 'wrappers', cc + '.py')
                remote_p = path.join(cc_src_remote_dir, 'wrappers', cc + '.py')

                self.flow_objs[tun_id] = Flow(cc=cc,
                                              cc_src_local=local_p,
                                              cc_src_remote=remote_p,
                                              run_first=run_first,
                                              run_second=run_second)
                tun_id += 1

    def setup_mm_cmd(self):
        mm_datalink_log = self.cc + '_mm_datalink_run%d.log' % self.run_id
        mm_acklink_log = self.cc + '_mm_acklink_run%d.log' % self.run_id
        self.mm_datalink_log = path.join(self.data_dir, mm_datalink_log)
        self.mm_acklink_log = path.join(self.data_dir, mm_acklink_log)

        if self.run_first == 'receiver' or self.flows > 0:
            # if receiver runs first OR if test inside pantheon tunnel
            uplink_log = self.mm_datalink_log
            downlink_log = self.mm_acklink_log
            uplink_trace = self.datalink_trace
            downlink_trace = self.acklink_trace
        else:
            # if sender runs first AND test without pantheon tunnel
            uplink_log = self.mm_acklink_log
            downlink_log = self.mm_datalink_log
            uplink_trace = self.acklink_trace
            downlink_trace = self.datalink_trace

        self.mm_cmd = []

        if self.prepend_mm_cmds:
            self.mm_cmd += self.prepend_mm_cmds.split()

        self.mm_cmd += [
            'mm-link', uplink_trace, downlink_trace,
            '--uplink-log=' + uplink_log, '--downlink-log=' + downlink_log
        ]

        if self.extra_mm_link_args:
            self.mm_cmd += self.extra_mm_link_args.split()

        if self.append_mm_cmds:
            self.mm_cmd += self.append_mm_cmds.split()

    def prepare_tunnel_log_paths(self):
        # boring work making sure logs have correct paths on local and remote
        self.datalink_ingress_logs = {}
        self.datalink_egress_logs = {}
        self.acklink_ingress_logs = {}
        self.acklink_egress_logs = {}

        local_tmp = utils.tmp_dir

        if self.mode == 'remote':
            remote_tmp = self.r['tmp_dir']

        for tun_id in xrange(1, self.flows + 1):
            uid = uuid.uuid4()

            datalink_ingress_logname = ('%s_flow%s_uid%s.log.ingress' %
                                        (self.datalink_name, tun_id, uid))
            self.datalink_ingress_logs[tun_id] = path.join(
                local_tmp, datalink_ingress_logname)

            datalink_egress_logname = ('%s_flow%s_uid%s.log.egress' %
                                       (self.datalink_name, tun_id, uid))
            self.datalink_egress_logs[tun_id] = path.join(
                local_tmp, datalink_egress_logname)

            acklink_ingress_logname = ('%s_flow%s_uid%s.log.ingress' %
                                       (self.acklink_name, tun_id, uid))
            self.acklink_ingress_logs[tun_id] = path.join(
                local_tmp, acklink_ingress_logname)

            acklink_egress_logname = ('%s_flow%s_uid%s.log.egress' %
                                      (self.acklink_name, tun_id, uid))
            self.acklink_egress_logs[tun_id] = path.join(
                local_tmp, acklink_egress_logname)

            if self.mode == 'remote':
                if self.sender_side == 'local':
                    self.datalink_ingress_logs[tun_id] = path.join(
                        remote_tmp, datalink_ingress_logname)
                    self.acklink_egress_logs[tun_id] = path.join(
                        remote_tmp, acklink_egress_logname)
                else:
                    self.datalink_egress_logs[tun_id] = path.join(
                        remote_tmp, datalink_egress_logname)
                    self.acklink_ingress_logs[tun_id] = path.join(
                        remote_tmp, acklink_ingress_logname)

    def setup(self):
        # setup commonly used paths
        self.cc_src = path.join(context.src_dir, 'wrappers', self.cc + '.py')
        self.tunnel_manager = path.join(context.src_dir, 'experiments',
                                        'tunnel_manager.py')

        # record who runs first
        if self.test_config is None:
            self.run_first, self.run_second = utils.who_runs_first(self.cc)
        else:
            self.run_first = None
            self.run_second = None

        # wait for 3 seconds until run_first is ready
        self.run_first_setup_time = 3

        # setup output logs
        self.datalink_name = self.cc + '_datalink_run%d' % self.run_id
        self.acklink_name = self.cc + '_acklink_run%d' % self.run_id

        self.datalink_log = path.join(self.data_dir,
                                      self.datalink_name + '.log')
        self.acklink_log = path.join(self.data_dir, self.acklink_name + '.log')

        if self.flows > 0:
            self.prepare_tunnel_log_paths()

        if self.mode == 'local':
            self.setup_mm_cmd()
        else:
            # record local and remote clock offset
            if self.ntp_addr is not None:
                self.local_ofst, self.remote_ofst = utils.query_clock_offset(
                    self.ntp_addr, self.r['ssh_cmd'])

    # test congestion control without running pantheon tunnel
    def run_without_tunnel(self):
        port = utils.get_open_port()

        # run the side specified by self.run_first
        cmd = ['python', self.cc_src, self.run_first, port]
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_first))
        self.proc_first = Popen(cmd, preexec_fn=os.setsid)

        # sleep just in case the process isn't quite listening yet
        # the cleaner approach might be to try to verify the socket is open
        time.sleep(self.run_first_setup_time)

        self.test_start_time = utils.utc_time()
        # run the other side specified by self.run_second
        sh_cmd = 'python %s %s $MAHIMAHI_BASE %s' % (self.cc_src,
                                                     self.run_second, port)
        sh_cmd = ' '.join(self.mm_cmd) + " -- sh -c '%s'" % sh_cmd
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_second))
        self.proc_second = Popen(sh_cmd, shell=True, preexec_fn=os.setsid)

        signal.signal(signal.SIGALRM, utils.timeout_handler)
        signal.alarm(self.runtime)

        try:
            self.proc_first.wait()
            self.proc_second.wait()
        except utils.TimeoutError:
            pass
        else:
            signal.alarm(0)
            sys.stderr.write('Warning: test exited before time limit\n')
        finally:
            self.test_end_time = utils.utc_time()

        return True

    def run_tunnel_managers(self):
        # run tunnel server manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                ts_manager_cmd = ['python', self.tunnel_manager]
            else:
                ts_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']
                ]
        else:
            ts_manager_cmd = ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel server manager (tsm)] ')
        self.ts_manager = Popen(ts_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid)
        ts_manager = self.ts_manager

        while True:
            running = ts_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        ts_manager.stdin.write('prompt [tsm]\n')
        ts_manager.stdin.flush()

        # run tunnel client manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                tc_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']
                ]
            else:
                tc_manager_cmd = ['python', self.tunnel_manager]
        else:
            tc_manager_cmd = self.mm_cmd + ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel client manager (tcm)] ')
        self.tc_manager = Popen(tc_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid)

        tc_manager = self.tc_manager

        while True:
            running = tc_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        tc_manager.stdin.write('prompt [tcm]\n')
        tc_manager.stdin.flush()

        return ts_manager, tc_manager

    def run_tunnel_server(self, tun_id, ts_manager):
        if self.server_side == self.sender_side:
            ts_cmd = 'mm-tunnelserver --ingress-log=%s --egress-log=%s' % (
                self.acklink_ingress_logs[tun_id],
                self.datalink_egress_logs[tun_id])
        else:
            ts_cmd = 'mm-tunnelserver --ingress-log=%s --egress-log=%s' % (
                self.datalink_ingress_logs[tun_id],
                self.acklink_egress_logs[tun_id])

        if self.mode == 'remote':
            if self.server_side == 'remote':
                if self.remote_if is not None:
                    ts_cmd += ' --interface=' + self.remote_if
            else:
                if self.local_if is not None:
                    ts_cmd += ' --interface=' + self.local_if

        ts_cmd = 'tunnel %s %s\n' % (tun_id, ts_cmd)
        ts_manager.stdin.write(ts_cmd)
        ts_manager.stdin.flush()

        # read the command to run tunnel client
        readline_cmd = 'tunnel %s readline\n' % tun_id
        ts_manager.stdin.write(readline_cmd)
        ts_manager.stdin.flush()

        cmd_to_run_tc = ts_manager.stdout.readline().split()
        return cmd_to_run_tc

    def run_tunnel_client(self, tun_id, tc_manager, cmd_to_run_tc):
        if self.mode == 'local':
            cmd_to_run_tc[1] = '$MAHIMAHI_BASE'
        else:
            if self.server_side == 'remote':
                cmd_to_run_tc[1] = self.r['ip']
            else:
                cmd_to_run_tc[1] = self.local_addr

        cmd_to_run_tc_str = ' '.join(cmd_to_run_tc)

        if self.server_side == self.sender_side:
            tc_cmd = '%s --ingress-log=%s --egress-log=%s' % (
                cmd_to_run_tc_str, self.datalink_ingress_logs[tun_id],
                self.acklink_egress_logs[tun_id])
        else:
            tc_cmd = '%s --ingress-log=%s --egress-log=%s' % (
                cmd_to_run_tc_str, self.acklink_ingress_logs[tun_id],
                self.datalink_egress_logs[tun_id])

        if self.mode == 'remote':
            if self.server_side == 'remote':
                if self.local_if is not None:
                    tc_cmd += ' --interface=' + self.local_if
            else:
                if self.remote_if is not None:
                    tc_cmd += ' --interface=' + self.remote_if

        tc_cmd = 'tunnel %s %s\n' % (tun_id, tc_cmd)
        readline_cmd = 'tunnel %s readline\n' % tun_id

        # re-run tunnel client after 20s timeout for at most 3 times
        max_run = 3
        curr_run = 0
        got_connection = ''
        while 'got connection' not in got_connection:
            curr_run += 1
            if curr_run > max_run:
                sys.stderr.write('Unable to establish tunnel\n')
                return False

            tc_manager.stdin.write(tc_cmd)
            tc_manager.stdin.flush()
            while True:
                tc_manager.stdin.write(readline_cmd)
                tc_manager.stdin.flush()

                signal.signal(signal.SIGALRM, utils.timeout_handler)
                signal.alarm(20)

                try:
                    got_connection = tc_manager.stdout.readline()
                    sys.stderr.write('Tunnel is connected\n')
                except utils.TimeoutError:
                    sys.stderr.write('Tunnel connection timeout\n')
                    break
                except IOError:
                    sys.stderr.write('Tunnel client failed to connect to '
                                     'tunnel server\n')
                    return False
                else:
                    signal.alarm(0)
                    if 'got connection' in got_connection:
                        break

        return True

    def run_first_side(self, tun_id, send_manager, recv_manager, send_pri_ip,
                       recv_pri_ip):

        first_src = self.cc_src
        second_src = self.cc_src

        if self.run_first == 'receiver':
            if self.mode == 'remote':
                if self.sender_side == 'local':
                    first_src = self.r['cc_src']
                else:
                    second_src = self.r['cc_src']

            port = utils.get_open_port()

            first_cmd = 'tunnel %s python %s receiver %s\n' % (tun_id,
                                                               first_src, port)
            second_cmd = 'tunnel %s python %s sender %s %s\n' % (
                tun_id, second_src, recv_pri_ip, port)

            recv_manager.stdin.write(first_cmd)
            recv_manager.stdin.flush()
        elif self.run_first == 'sender':  # self.run_first == 'sender'
            if self.mode == 'remote':
                if self.sender_side == 'local':
                    second_src = self.r['cc_src']
                else:
                    first_src = self.r['cc_src']

            port = utils.get_open_port()

            first_cmd = 'tunnel %s python %s sender %s\n' % (tun_id, first_src,
                                                             port)
            second_cmd = 'tunnel %s python %s receiver %s %s\n' % (
                tun_id, second_src, send_pri_ip, port)

            send_manager.stdin.write(first_cmd)
            send_manager.stdin.flush()

        # get run_first and run_second from the flow object
        else:
            assert (hasattr(self, 'flow_objs'))
            flow = self.flow_objs[tun_id]

            first_src = flow.cc_src_local
            second_src = flow.cc_src_local

            if flow.run_first == 'receiver':
                if self.mode == 'remote':
                    if self.sender_side == 'local':
                        first_src = flow.cc_src_remote
                    else:
                        second_src = flow.cc_src_remote

                port = utils.get_open_port()

                first_cmd = 'tunnel %s python %s receiver %s\n' % (
                    tun_id, first_src, port)
                second_cmd = 'tunnel %s python %s sender %s %s --extra-args=%s\n' % (
                    tun_id, second_src, recv_pri_ip, port,
                    self.extra_sender_args)

                recv_manager.stdin.write(first_cmd)
                recv_manager.stdin.flush()
            else:  # flow.run_first == 'sender'
                if self.mode == 'remote':
                    if self.sender_side == 'local':
                        second_src = flow.cc_src_remote
                    else:
                        first_src = flow.cc_src_remote

                port = utils.get_open_port()

                first_cmd = 'tunnel %s python %s sender %s --extra-args=%s\n' % (
                    tun_id, first_src, port, self.extra_sender_args)
                second_cmd = 'tunnel %s python %s receiver %s %s\n' % (
                    tun_id, second_src, send_pri_ip, port)

                send_manager.stdin.write(first_cmd)
                send_manager.stdin.flush()

        return second_cmd

    def run_second_side(self, send_manager, recv_manager, second_cmds):
        time.sleep(self.run_first_setup_time)

        start_time = time.time()
        self.test_start_time = utils.utc_time()

        # start each flow self.interval seconds after the previous one
        for i in xrange(len(second_cmds)):
            if i != 0:
                time.sleep(self.interval)
            second_cmd = second_cmds[i]

            if self.run_first == 'receiver':
                send_manager.stdin.write(second_cmd)
                send_manager.stdin.flush()
            elif self.run_first == 'sender':
                recv_manager.stdin.write(second_cmd)
                recv_manager.stdin.flush()
            else:
                assert (hasattr(self, 'flow_objs'))
                flow = self.flow_objs[i]
                if flow.run_first == 'receiver':
                    send_manager.stdin.write(second_cmd)
                    send_manager.stdin.flush()
                elif flow.run_first == 'sender':
                    recv_manager.stdin.write(second_cmd)
                    recv_manager.stdin.flush()

        elapsed_time = time.time() - start_time
        if elapsed_time > self.runtime:
            sys.stderr.write('Interval time between flows is too long')
            return False

        time.sleep(self.runtime - elapsed_time)
        self.test_end_time = utils.utc_time()

        return True

    # test congestion control using tunnel client and tunnel server
    def run_with_tunnel(self):
        # run pantheon tunnel server and client managers
        ts_manager, tc_manager = self.run_tunnel_managers()

        # create alias for ts_manager and tc_manager using sender or receiver
        if self.sender_side == self.server_side:
            send_manager = ts_manager
            recv_manager = tc_manager
        else:
            send_manager = tc_manager
            recv_manager = ts_manager

        # run every flow
        second_cmds = []
        for tun_id in xrange(1, self.flows + 1):
            # run tunnel server for tunnel tun_id
            cmd_to_run_tc = self.run_tunnel_server(tun_id, ts_manager)

            # run tunnel client for tunnel tun_id
            if not self.run_tunnel_client(tun_id, tc_manager, cmd_to_run_tc):
                return False

            tc_pri_ip = cmd_to_run_tc[3]  # tunnel client private IP
            ts_pri_ip = cmd_to_run_tc[4]  # tunnel server private IP

            if self.sender_side == self.server_side:
                send_pri_ip = ts_pri_ip
                recv_pri_ip = tc_pri_ip
            else:
                send_pri_ip = tc_pri_ip
                recv_pri_ip = ts_pri_ip

            # run the side that runs first and get cmd to run the other side
            second_cmd = self.run_first_side(tun_id, send_manager,
                                             recv_manager, send_pri_ip,
                                             recv_pri_ip)
            second_cmds.append(second_cmd)

        # run the side that runs second
        if not self.run_second_side(send_manager, recv_manager, second_cmds):
            return False

        # stop all the running flows and quit tunnel managers
        ts_manager.stdin.write('halt\n')
        ts_manager.stdin.flush()
        tc_manager.stdin.write('halt\n')
        tc_manager.stdin.flush()

        # process tunnel logs
        self.process_tunnel_logs()

        return True

    def download_tunnel_logs(self, tun_id):
        assert (self.mode == 'remote')

        # download logs from remote side
        cmd = 'scp -C %s:' % self.r['host_addr']
        cmd += '%(remote_log)s %(local_log)s'

        # function to get a corresponding local path from a remote path
        f = lambda p: path.join(utils.tmp_dir, path.basename(p))

        if self.sender_side == 'remote':
            local_log = f(self.datalink_egress_logs[tun_id])
            call(cmd % {
                'remote_log': self.datalink_egress_logs[tun_id],
                'local_log': local_log
            },
                 shell=True)
            self.datalink_egress_logs[tun_id] = local_log

            local_log = f(self.acklink_ingress_logs[tun_id])
            call(cmd % {
                'remote_log': self.acklink_ingress_logs[tun_id],
                'local_log': local_log
            },
                 shell=True)
            self.acklink_ingress_logs[tun_id] = local_log
        else:
            local_log = f(self.datalink_ingress_logs[tun_id])
            call(cmd % {
                'remote_log': self.datalink_ingress_logs[tun_id],
                'local_log': local_log
            },
                 shell=True)
            self.datalink_ingress_logs[tun_id] = local_log

            local_log = f(self.acklink_egress_logs[tun_id])
            call(cmd % {
                'remote_log': self.acklink_egress_logs[tun_id],
                'local_log': local_log
            },
                 shell=True)
            self.acklink_egress_logs[tun_id] = local_log

    def process_tunnel_logs(self):
        datalink_tun_logs = []
        acklink_tun_logs = []

        apply_ofst = False
        if self.mode == 'remote':
            if self.remote_ofst is not None and self.local_ofst is not None:
                apply_ofst = True

                if self.sender_side == 'remote':
                    data_e_ofst = self.remote_ofst
                    ack_i_ofst = self.remote_ofst
                    data_i_ofst = self.local_ofst
                    ack_e_ofst = self.local_ofst
                else:
                    data_i_ofst = self.remote_ofst
                    ack_e_ofst = self.remote_ofst
                    data_e_ofst = self.local_ofst
                    ack_i_ofst = self.local_ofst

        merge_tunnel_logs = path.join(context.src_dir, 'experiments',
                                      'merge_tunnel_logs.py')

        for tun_id in xrange(1, self.flows + 1):
            if self.mode == 'remote':
                self.download_tunnel_logs(tun_id)

            uid = uuid.uuid4()
            datalink_tun_log = path.join(
                utils.tmp_dir, '%s_flow%s_uid%s.log.merged' %
                (self.datalink_name, tun_id, uid))
            acklink_tun_log = path.join(
                utils.tmp_dir, '%s_flow%s_uid%s.log.merged' %
                (self.acklink_name, tun_id, uid))

            cmd = [
                merge_tunnel_logs, 'single', '-i',
                self.datalink_ingress_logs[tun_id], '-e',
                self.datalink_egress_logs[tun_id], '-o', datalink_tun_log
            ]
            if apply_ofst:
                cmd += [
                    '-i-clock-offset', data_i_ofst, '-e-clock-offset',
                    data_e_ofst
                ]
            call(cmd)

            cmd = [
                merge_tunnel_logs, 'single', '-i',
                self.acklink_ingress_logs[tun_id], '-e',
                self.acklink_egress_logs[tun_id], '-o', acklink_tun_log
            ]
            if apply_ofst:
                cmd += [
                    '-i-clock-offset', ack_i_ofst, '-e-clock-offset',
                    ack_e_ofst
                ]
            call(cmd)

            datalink_tun_logs.append(datalink_tun_log)
            acklink_tun_logs.append(acklink_tun_log)

        cmd = [merge_tunnel_logs, 'multiple', '-o', self.datalink_log]
        if self.mode == 'local':
            cmd += ['--link-log', self.mm_datalink_log]
        cmd += datalink_tun_logs
        call(cmd)

        cmd = [merge_tunnel_logs, 'multiple', '-o', self.acklink_log]
        if self.mode == 'local':
            cmd += ['--link-log', self.mm_acklink_log]
        cmd += acklink_tun_logs
        call(cmd)

    def run_congestion_control(self):
        if self.flows > 0:
            try:
                return self.run_with_tunnel()
            finally:
                utils.kill_proc_group(self.ts_manager)
                utils.kill_proc_group(self.tc_manager)
        else:
            # test without pantheon tunnel when self.flows = 0
            try:
                return self.run_without_tunnel()
            finally:
                utils.kill_proc_group(self.proc_first)
                utils.kill_proc_group(self.proc_second)

    def record_time_stats(self):
        stats_log = path.join(self.data_dir,
                              '%s_stats_run%s.log' % (self.cc, self.run_id))
        stats = open(stats_log, 'w')

        # save start time and end time of test
        if self.test_start_time is not None and self.test_end_time is not None:
            test_run_duration = ('Start at: %s\nEnd at: %s\n' %
                                 (self.test_start_time, self.test_end_time))
            sys.stderr.write(test_run_duration)
            stats.write(test_run_duration)

        if self.mode == 'remote':
            ofst_info = ''
            if self.local_ofst is not None:
                ofst_info += 'Local clock offset: %s ms\n' % self.local_ofst

            if self.remote_ofst is not None:
                ofst_info += 'Remote clock offset: %s ms\n' % self.remote_ofst

            if ofst_info:
                sys.stderr.write(ofst_info)
                stats.write(ofst_info)

        stats.close()

    # run congestion control test
    def run(self):
        msg = 'Testing scheme %s for experiment run %d/%d...' % (
            self.cc, self.run_id, self.run_times)
        sys.stderr.write(msg + '\n')

        # setup before running tests
        self.setup()

        # run receiver and sender
        if not self.run_congestion_control():
            sys.stderr.write('Error in testing scheme %s with run ID %d\n' %
                             (self.cc, self.run_id))
            return

        # write runtimes and clock offsets to file
        self.record_time_stats()

        sys.stderr.write('Done testing %s\n' % self.cc)
コード例 #9
0
ファイル: test.py プロジェクト: viswanathgs/pantheon
    def run_tunnel_managers(self):
        # run tunnel server manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                ts_manager_cmd = ['python', self.tunnel_manager]
            else:
                ts_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']
                ]
        else:
            ts_manager_cmd = ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel server manager (tsm)] ')
        self.ts_manager = Popen(ts_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid)
        ts_manager = self.ts_manager

        while True:
            running = ts_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break
            if not running:
                sys.stderr.write('WARNING: tunnel server manager terminated '
                                 'unexpectedly\n')
                return None, None

        ts_manager.stdin.write('prompt [tsm]\n')
        ts_manager.stdin.flush()

        # run tunnel client manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                tc_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']
                ]
            else:
                tc_manager_cmd = ['python', self.tunnel_manager]
        else:
            tc_manager_cmd = self.mm_cmd + ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel client manager (tcm)] ')
        # NB: using `preexec_fn=os.setsid` creates a new process group, so that
        # it is easy to kill all associated child processes afterwards.
        self.tc_manager = Popen(tc_manager_cmd,
                                stdin=PIPE,
                                stdout=PIPE,
                                preexec_fn=os.setsid)
        tc_manager = self.tc_manager

        while True:
            running = tc_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break
            if not running:
                sys.stderr.write('WARNING: tunnel client manager terminated '
                                 'unexpectedly\n')
                return ts_manager, None

        tc_manager.stdin.write('prompt [tcm]\n')
        tc_manager.stdin.flush()

        return ts_manager, tc_manager
コード例 #10
0
def start_hosts():
    sys.stderr.write('----- Starting hosts -----\n')

    # set of live hosts (including nodes and cloud servers)
    live_hosts = set()

    # list of cloud server to start
    clouds_to_start = []

    if expt_type == 'node':
        host_status = utils.check_ssh_connection(
            utils.host_cfg['nodes'].keys())

        for host, status in host_status.iteritems():
            if status:
                live_hosts.add(host)

                # get peer cloud of host
                node_cfg = utils.host_cfg['nodes'][host]
                peer_cloud = node_cfg['peer_cloud']
                clouds_to_start.append(peer_cloud)

        # run aws.py
        aws_py = path.join(context.scripts_dir, 'aws.py')
        with open(os.devnull, 'w') as DEVNULL:
            Popen([aws_py, 'start', '--hosts', ' '.join(clouds_to_start)],
                  stdout=DEVNULL,
                  stderr=DEVNULL).wait()

    else:
        if expt_type == 'cloud':
            gce_server_type = 'gce_servers'
        elif expt_type == 'emu':
            gce_server_type = 'emu_servers'

        for host in utils.host_cfg[gce_server_type]:
            clouds_to_start.append(host)

        # run gce.py
        gce_py = path.join(context.scripts_dir, 'gce.py')
        with open(os.devnull, 'w') as DEVNULL:
            Popen([gce_py, 'start', gce_server_type],
                  stdout=DEVNULL,
                  stderr=DEVNULL).wait()

    # check SSH connections to cloud servers
    host_status = utils.check_ssh_connection(clouds_to_start,
                                             retry_times=2,
                                             retry_timeout=20)

    for host, status in host_status.iteritems():
        if not status:
            sys.exit('Failed to start %s' % host)

        live_hosts.add(host)

    # print live hosts
    sys.stderr.write('Live hosts:')
    if not live_hosts:
        sys.stderr.write(' None\n')
    else:
        for host in live_hosts:
            sys.stderr.write(' ' + host)
        sys.stderr.write('\n')

    return live_hosts
コード例 #11
0
ファイル: test.py プロジェクト: StanfordLPNG/pantheon
class Test(object):
    def __init__(self, args, run_id, cc):
        self.mode = args.mode
        self.run_id = run_id
        self.cc = cc
        self.data_dir = path.abspath(args.data_dir)

        # shared arguments between local and remote modes
        self.flows = args.flows
        self.runtime = args.runtime
        self.interval = args.interval
        self.run_times = args.run_times

        # used for cleanup
        self.proc_first = None
        self.proc_second = None
        self.ts_manager = None
        self.tc_manager = None

        self.test_start_time = None
        self.test_end_time = None

        # local mode
        if self.mode == 'local':
            self.datalink_trace = args.uplink_trace
            self.acklink_trace = args.downlink_trace
            self.prepend_mm_cmds = args.prepend_mm_cmds
            self.append_mm_cmds = args.append_mm_cmds
            self.extra_mm_link_args = args.extra_mm_link_args

            # for convenience
            self.sender_side = 'remote'
            self.server_side = 'local'

        # remote mode
        if self.mode == 'remote':
            self.sender_side = args.sender_side
            self.server_side = args.server_side
            self.local_addr = args.local_addr
            self.local_if = args.local_if
            self.remote_if = args.remote_if
            self.local_desc = args.local_desc
            self.remote_desc = args.remote_desc

            self.ntp_addr = args.ntp_addr
            self.local_ofst = None
            self.remote_ofst = None

            self.r = utils.parse_remote_path(args.remote_path, self.cc)

        # arguments when there's a config
        self.test_config = None
        if hasattr(args, 'test_config'):
            self.test_config = args.test_config

        if self.test_config is not None:
            self.cc = self.test_config['test-name']
            self.flow_objs = {}
            cc_src_remote_dir = ''
            if self.mode == 'remote':
                cc_src_remote_dir = r['base_dir']

            tun_id = 1
            for flow in args.test_config['flows']:
                cc = flow['scheme']
                run_first, run_second = utils.who_runs_first(cc)

                local_p = path.join(context.src_dir, 'wrappers', cc + '.py')
                remote_p = path.join(cc_src_remote_dir, 'wrappers', cc + '.py')

                self.flow_objs[tun_id] = Flow(
                    cc=cc,
                    cc_src_local=local_p,
                    cc_src_remote=remote_p,
                    run_first=run_first,
                    run_second=run_second)
                tun_id += 1

    def setup_mm_cmd(self):
        mm_datalink_log = self.cc + '_mm_datalink_run%d.log' % self.run_id
        mm_acklink_log = self.cc + '_mm_acklink_run%d.log' % self.run_id
        self.mm_datalink_log = path.join(self.data_dir, mm_datalink_log)
        self.mm_acklink_log = path.join(self.data_dir, mm_acklink_log)

        if self.run_first == 'receiver' or self.flows > 0:
            # if receiver runs first OR if test inside pantheon tunnel
            uplink_log = self.mm_datalink_log
            downlink_log = self.mm_acklink_log
            uplink_trace = self.datalink_trace
            downlink_trace = self.acklink_trace
        else:
            # if sender runs first AND test without pantheon tunnel
            uplink_log = self.mm_acklink_log
            downlink_log = self.mm_datalink_log
            uplink_trace = self.acklink_trace
            downlink_trace = self.datalink_trace

        self.mm_cmd = []

        if self.prepend_mm_cmds:
            self.mm_cmd += self.prepend_mm_cmds.split()

        self.mm_cmd += [
            'mm-link', uplink_trace, downlink_trace,
            '--uplink-log=' + uplink_log,
            '--downlink-log=' + downlink_log]

        if self.extra_mm_link_args:
            self.mm_cmd += self.extra_mm_link_args.split()

        if self.append_mm_cmds:
            self.mm_cmd += self.append_mm_cmds.split()

    def prepare_tunnel_log_paths(self):
        # boring work making sure logs have correct paths on local and remote
        self.datalink_ingress_logs = {}
        self.datalink_egress_logs = {}
        self.acklink_ingress_logs = {}
        self.acklink_egress_logs = {}

        local_tmp = utils.tmp_dir

        if self.mode == 'remote':
            remote_tmp = self.r['tmp_dir']

        for tun_id in xrange(1, self.flows + 1):
            uid = uuid.uuid4()

            datalink_ingress_logname = ('%s_flow%s_uid%s.log.ingress' %
                                        (self.datalink_name, tun_id, uid))
            self.datalink_ingress_logs[tun_id] = path.join(
                    local_tmp, datalink_ingress_logname)

            datalink_egress_logname = ('%s_flow%s_uid%s.log.egress' %
                                       (self.datalink_name, tun_id, uid))
            self.datalink_egress_logs[tun_id] = path.join(
                    local_tmp, datalink_egress_logname)

            acklink_ingress_logname = ('%s_flow%s_uid%s.log.ingress' %
                                       (self.acklink_name, tun_id, uid))
            self.acklink_ingress_logs[tun_id] = path.join(
                    local_tmp, acklink_ingress_logname)

            acklink_egress_logname = ('%s_flow%s_uid%s.log.egress' %
                                      (self.acklink_name, tun_id, uid))
            self.acklink_egress_logs[tun_id] = path.join(
                    local_tmp, acklink_egress_logname)

            if self.mode == 'remote':
                if self.sender_side == 'local':
                    self.datalink_ingress_logs[tun_id] = path.join(
                            remote_tmp, datalink_ingress_logname)
                    self.acklink_egress_logs[tun_id] = path.join(
                            remote_tmp, acklink_egress_logname)
                else:
                    self.datalink_egress_logs[tun_id] = path.join(
                            remote_tmp, datalink_egress_logname)
                    self.acklink_ingress_logs[tun_id] = path.join(
                            remote_tmp, acklink_ingress_logname)

    def setup(self):
        # setup commonly used paths
        self.cc_src = path.join(context.src_dir, 'wrappers', self.cc + '.py')
        self.tunnel_manager = path.join(context.src_dir, 'experiments',
                                        'tunnel_manager.py')

        # record who runs first
        if self.test_config is None:
            self.run_first, self.run_second = utils.who_runs_first(self.cc)
        else:
            self.run_first = None
            self.run_second = None

        # wait for 3 seconds until run_first is ready
        self.run_first_setup_time = 3

        # setup output logs
        self.datalink_name = self.cc + '_datalink_run%d' % self.run_id
        self.acklink_name = self.cc + '_acklink_run%d' % self.run_id

        self.datalink_log = path.join(
            self.data_dir, self.datalink_name + '.log')
        self.acklink_log = path.join(
            self.data_dir, self.acklink_name + '.log')

        if self.flows > 0:
            self.prepare_tunnel_log_paths()

        if self.mode == 'local':
            self.setup_mm_cmd()
        else:
            # record local and remote clock offset
            if self.ntp_addr is not None:
                self.local_ofst, self.remote_ofst = utils.query_clock_offset(
                    self.ntp_addr, self.r['ssh_cmd'])

    # test congestion control without running pantheon tunnel
    def run_without_tunnel(self):
        port = utils.get_open_port()

        # run the side specified by self.run_first
        cmd = ['python', self.cc_src, self.run_first, port]
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_first))
        self.proc_first = Popen(cmd, preexec_fn=os.setsid)

        # sleep just in case the process isn't quite listening yet
        # the cleaner approach might be to try to verify the socket is open
        time.sleep(self.run_first_setup_time)

        self.test_start_time = utils.utc_time()
        # run the other side specified by self.run_second
        sh_cmd = 'python %s %s $MAHIMAHI_BASE %s' % (
            self.cc_src, self.run_second, port)
        sh_cmd = ' '.join(self.mm_cmd) + " -- sh -c '%s'" % sh_cmd
        sys.stderr.write('Running %s %s...\n' % (self.cc, self.run_second))
        self.proc_second = Popen(sh_cmd, shell=True, preexec_fn=os.setsid)

        signal.signal(signal.SIGALRM, utils.timeout_handler)
        signal.alarm(self.runtime)

        try:
            self.proc_first.wait()
            self.proc_second.wait()
        except utils.TimeoutError:
            pass
        else:
            signal.alarm(0)
            sys.stderr.write('Warning: test exited before time limit\n')
        finally:
            self.test_end_time = utils.utc_time()

        return True

    def run_tunnel_managers(self):
        # run tunnel server manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                ts_manager_cmd = ['python', self.tunnel_manager]
            else:
                ts_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']]
        else:
            ts_manager_cmd = ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel server manager (tsm)] ')
        self.ts_manager = Popen(ts_manager_cmd, stdin=PIPE, stdout=PIPE,
                                preexec_fn=os.setsid)
        ts_manager = self.ts_manager

        while True:
            running = ts_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        ts_manager.stdin.write('prompt [tsm]\n')
        ts_manager.stdin.flush()

        # run tunnel client manager
        if self.mode == 'remote':
            if self.server_side == 'local':
                tc_manager_cmd = self.r['ssh_cmd'] + [
                    'python', self.r['tunnel_manager']]
            else:
                tc_manager_cmd = ['python', self.tunnel_manager]
        else:
            tc_manager_cmd = self.mm_cmd + ['python', self.tunnel_manager]

        sys.stderr.write('[tunnel client manager (tcm)] ')
        self.tc_manager = Popen(tc_manager_cmd, stdin=PIPE, stdout=PIPE,
                                preexec_fn=os.setsid)
        tc_manager = self.tc_manager

        while True:
            running = tc_manager.stdout.readline()
            if 'tunnel manager is running' in running:
                sys.stderr.write(running)
                break

        tc_manager.stdin.write('prompt [tcm]\n')
        tc_manager.stdin.flush()

        return ts_manager, tc_manager

    def run_tunnel_server(self, tun_id, ts_manager):
        if self.server_side == self.sender_side:
            ts_cmd = 'mm-tunnelserver --ingress-log=%s --egress-log=%s' % (
                self.acklink_ingress_logs[tun_id],
                self.datalink_egress_logs[tun_id])
        else:
            ts_cmd = 'mm-tunnelserver --ingress-log=%s --egress-log=%s' % (
                self.datalink_ingress_logs[tun_id],
                self.acklink_egress_logs[tun_id])

        if self.mode == 'remote':
            if self.server_side == 'remote':
                if self.remote_if is not None:
                    ts_cmd += ' --interface=' + self.remote_if
            else:
                if self.local_if is not None:
                    ts_cmd += ' --interface=' + self.local_if

        ts_cmd = 'tunnel %s %s\n' % (tun_id, ts_cmd)
        ts_manager.stdin.write(ts_cmd)
        ts_manager.stdin.flush()

        # read the command to run tunnel client
        readline_cmd = 'tunnel %s readline\n' % tun_id
        ts_manager.stdin.write(readline_cmd)
        ts_manager.stdin.flush()

        cmd_to_run_tc = ts_manager.stdout.readline().split()
        return cmd_to_run_tc

    def run_tunnel_client(self, tun_id, tc_manager, cmd_to_run_tc):
        if self.mode == 'local':
            cmd_to_run_tc[1] = '$MAHIMAHI_BASE'
        else:
            if self.server_side == 'remote':
                cmd_to_run_tc[1] = self.r['ip']
            else:
                cmd_to_run_tc[1] = self.local_addr

        cmd_to_run_tc_str = ' '.join(cmd_to_run_tc)

        if self.server_side == self.sender_side:
            tc_cmd = '%s --ingress-log=%s --egress-log=%s' % (
                cmd_to_run_tc_str,
                self.datalink_ingress_logs[tun_id],
                self.acklink_egress_logs[tun_id])
        else:
            tc_cmd = '%s --ingress-log=%s --egress-log=%s' % (
                cmd_to_run_tc_str,
                self.acklink_ingress_logs[tun_id],
                self.datalink_egress_logs[tun_id])

        if self.mode == 'remote':
            if self.server_side == 'remote':
                if self.local_if is not None:
                    tc_cmd += ' --interface=' + self.local_if
            else:
                if self.remote_if is not None:
                    tc_cmd += ' --interface=' + self.remote_if

        tc_cmd = 'tunnel %s %s\n' % (tun_id, tc_cmd)
        readline_cmd = 'tunnel %s readline\n' % tun_id

        # re-run tunnel client after 20s timeout for at most 3 times
        max_run = 3
        curr_run = 0
        got_connection = ''
        while 'got connection' not in got_connection:
            curr_run += 1
            if curr_run > max_run:
                sys.stderr.write('Unable to establish tunnel\n')
                return False

            tc_manager.stdin.write(tc_cmd)
            tc_manager.stdin.flush()
            while True:
                tc_manager.stdin.write(readline_cmd)
                tc_manager.stdin.flush()

                signal.signal(signal.SIGALRM, utils.timeout_handler)
                signal.alarm(20)

                try:
                    got_connection = tc_manager.stdout.readline()
                    sys.stderr.write('Tunnel is connected\n')
                except utils.TimeoutError:
                    sys.stderr.write('Tunnel connection timeout\n')
                    break
                except IOError:
                    sys.stderr.write('Tunnel client failed to connect to '
                                     'tunnel server\n')
                    return False
                else:
                    signal.alarm(0)
                    if 'got connection' in got_connection:
                        break

        return True

    def run_first_side(self, tun_id, send_manager, recv_manager,
                       send_pri_ip, recv_pri_ip):

        first_src = self.cc_src
        second_src = self.cc_src

        if self.run_first == 'receiver':
            if self.mode == 'remote':
                if self.sender_side == 'local':
                    first_src = self.r['cc_src']
                else:
                    second_src = self.r['cc_src']

            port = utils.get_open_port()

            first_cmd = 'tunnel %s python %s receiver %s\n' % (
                tun_id, first_src, port)
            second_cmd = 'tunnel %s python %s sender %s %s\n' % (
                tun_id, second_src, recv_pri_ip, port)

            recv_manager.stdin.write(first_cmd)
            recv_manager.stdin.flush()
        elif self.run_first == 'sender':  # self.run_first == 'sender'
            if self.mode == 'remote':
                if self.sender_side == 'local':
                    second_src = self.r['cc_src']
                else:
                    first_src = self.r['cc_src']

            port = utils.get_open_port()

            first_cmd = 'tunnel %s python %s sender %s\n' % (
                tun_id, first_src, port)
            second_cmd = 'tunnel %s python %s receiver %s %s\n' % (
                tun_id, second_src, send_pri_ip, port)

            send_manager.stdin.write(first_cmd)
            send_manager.stdin.flush()

        # get run_first and run_second from the flow object
        else:
            assert(hasattr(self, 'flow_objs'))
            flow = self.flow_objs[tun_id]

            first_src = flow.cc_src_local
            second_src = flow.cc_src_local

            if flow.run_first == 'receiver':
                if self.mode == 'remote':
                    if self.sender_side == 'local':
                        first_src = flow.cc_src_remote
                    else:
                        second_src = flow.cc_src_remote

                port = utils.get_open_port()

                first_cmd = 'tunnel %s python %s receiver %s\n' % (
                    tun_id, first_src, port)
                second_cmd = 'tunnel %s python %s sender %s %s\n' % (
                    tun_id, second_src, recv_pri_ip, port)

                recv_manager.stdin.write(first_cmd)
                recv_manager.stdin.flush()
            else:  # flow.run_first == 'sender'
                if self.mode == 'remote':
                    if self.sender_side == 'local':
                        second_src = flow.cc_src_remote
                    else:
                        first_src = flow.cc_src_remote

                port = utils.get_open_port()

                first_cmd = 'tunnel %s python %s sender %s\n' % (
                    tun_id, first_src, port)
                second_cmd = 'tunnel %s python %s receiver %s %s\n' % (
                    tun_id, second_src, send_pri_ip, port)

                send_manager.stdin.write(first_cmd)
                send_manager.stdin.flush()

        return second_cmd

    def run_second_side(self, send_manager, recv_manager, second_cmds):
        time.sleep(self.run_first_setup_time)

        start_time = time.time()
        self.test_start_time = utils.utc_time()

        # start each flow self.interval seconds after the previous one
        for i in xrange(len(second_cmds)):
            if i != 0:
                time.sleep(self.interval)
            second_cmd = second_cmds[i]

            if self.run_first == 'receiver':
                send_manager.stdin.write(second_cmd)
                send_manager.stdin.flush()
            elif self.run_first == 'sender':
                recv_manager.stdin.write(second_cmd)
                recv_manager.stdin.flush()
            else:
                assert(hasattr(self, 'flow_objs'))
                flow = self.flow_objs[i]
                if flow.run_first == 'receiver':
                    send_manager.stdin.write(second_cmd)
                    send_manager.stdin.flush()
                elif flow.run_first == 'sender':
                    recv_manager.stdin.write(second_cmd)
                    recv_manager.stdin.flush()

        elapsed_time = time.time() - start_time
        if elapsed_time > self.runtime:
            sys.stderr.write('Interval time between flows is too long')
            return False

        time.sleep(self.runtime - elapsed_time)
        self.test_end_time = utils.utc_time()

        return True

    # test congestion control using tunnel client and tunnel server
    def run_with_tunnel(self):
        # run pantheon tunnel server and client managers
        ts_manager, tc_manager = self.run_tunnel_managers()

        # create alias for ts_manager and tc_manager using sender or receiver
        if self.sender_side == self.server_side:
            send_manager = ts_manager
            recv_manager = tc_manager
        else:
            send_manager = tc_manager
            recv_manager = ts_manager

        # run every flow
        second_cmds = []
        for tun_id in xrange(1, self.flows + 1):
            # run tunnel server for tunnel tun_id
            cmd_to_run_tc = self.run_tunnel_server(tun_id, ts_manager)

            # run tunnel client for tunnel tun_id
            if not self.run_tunnel_client(tun_id, tc_manager, cmd_to_run_tc):
                return False

            tc_pri_ip = cmd_to_run_tc[3]  # tunnel client private IP
            ts_pri_ip = cmd_to_run_tc[4]  # tunnel server private IP

            if self.sender_side == self.server_side:
                send_pri_ip = ts_pri_ip
                recv_pri_ip = tc_pri_ip
            else:
                send_pri_ip = tc_pri_ip
                recv_pri_ip = ts_pri_ip

            # run the side that runs first and get cmd to run the other side
            second_cmd = self.run_first_side(
                tun_id, send_manager, recv_manager, send_pri_ip, recv_pri_ip)
            second_cmds.append(second_cmd)

        # run the side that runs second
        if not self.run_second_side(send_manager, recv_manager, second_cmds):
            return False

        # stop all the running flows and quit tunnel managers
        ts_manager.stdin.write('halt\n')
        ts_manager.stdin.flush()
        tc_manager.stdin.write('halt\n')
        tc_manager.stdin.flush()

        # process tunnel logs
        self.process_tunnel_logs()

        return True

    def download_tunnel_logs(self, tun_id):
        assert(self.mode == 'remote')

        # download logs from remote side
        cmd = 'scp -C %s:' % self.r['host_addr']
        cmd += '%(remote_log)s %(local_log)s'

        # function to get a corresponding local path from a remote path
        f = lambda p: path.join(utils.tmp_dir, path.basename(p))

        if self.sender_side == 'remote':
            local_log = f(self.datalink_egress_logs[tun_id])
            call(cmd % {'remote_log': self.datalink_egress_logs[tun_id],
                        'local_log': local_log}, shell=True)
            self.datalink_egress_logs[tun_id] = local_log

            local_log = f(self.acklink_ingress_logs[tun_id])
            call(cmd % {'remote_log': self.acklink_ingress_logs[tun_id],
                        'local_log': local_log}, shell=True)
            self.acklink_ingress_logs[tun_id] = local_log
        else:
            local_log = f(self.datalink_ingress_logs[tun_id])
            call(cmd % {'remote_log': self.datalink_ingress_logs[tun_id],
                        'local_log': local_log}, shell=True)
            self.datalink_ingress_logs[tun_id] = local_log

            local_log = f(self.acklink_egress_logs[tun_id])
            call(cmd % {'remote_log': self.acklink_egress_logs[tun_id],
                        'local_log': local_log}, shell=True)
            self.acklink_egress_logs[tun_id] = local_log


    def process_tunnel_logs(self):
        datalink_tun_logs = []
        acklink_tun_logs = []

        apply_ofst = False
        if self.mode == 'remote':
            if self.remote_ofst is not None and self.local_ofst is not None:
                apply_ofst = True

                if self.sender_side == 'remote':
                    data_e_ofst = self.remote_ofst
                    ack_i_ofst = self.remote_ofst
                    data_i_ofst = self.local_ofst
                    ack_e_ofst = self.local_ofst
                else:
                    data_i_ofst = self.remote_ofst
                    ack_e_ofst = self.remote_ofst
                    data_e_ofst = self.local_ofst
                    ack_i_ofst = self.local_ofst

        merge_tunnel_logs = path.join(context.src_dir, 'experiments',
                                      'merge_tunnel_logs.py')

        for tun_id in xrange(1, self.flows + 1):
            if self.mode == 'remote':
                self.download_tunnel_logs(tun_id)

            uid = uuid.uuid4()
            datalink_tun_log = path.join(
                utils.tmp_dir, '%s_flow%s_uid%s.log.merged'
                % (self.datalink_name, tun_id, uid))
            acklink_tun_log = path.join(
                utils.tmp_dir, '%s_flow%s_uid%s.log.merged'
                % (self.acklink_name, tun_id, uid))

            cmd = [merge_tunnel_logs, 'single',
                   '-i', self.datalink_ingress_logs[tun_id],
                   '-e', self.datalink_egress_logs[tun_id],
                   '-o', datalink_tun_log]
            if apply_ofst:
                cmd += ['-i-clock-offset', data_i_ofst,
                        '-e-clock-offset', data_e_ofst]
            call(cmd)

            cmd = [merge_tunnel_logs, 'single',
                   '-i', self.acklink_ingress_logs[tun_id],
                   '-e', self.acklink_egress_logs[tun_id],
                   '-o', acklink_tun_log]
            if apply_ofst:
                cmd += ['-i-clock-offset', ack_i_ofst,
                        '-e-clock-offset', ack_e_ofst]
            call(cmd)

            datalink_tun_logs.append(datalink_tun_log)
            acklink_tun_logs.append(acklink_tun_log)

        cmd = [merge_tunnel_logs, 'multiple', '-o', self.datalink_log]
        if self.mode == 'local':
            cmd += ['--link-log', self.mm_datalink_log]
        cmd += datalink_tun_logs
        call(cmd)

        cmd = [merge_tunnel_logs, 'multiple', '-o', self.acklink_log]
        if self.mode == 'local':
            cmd += ['--link-log', self.mm_acklink_log]
        cmd += acklink_tun_logs
        call(cmd)

    def run_congestion_control(self):
        if self.flows > 0:
            try:
                return self.run_with_tunnel()
            finally:
                utils.kill_proc_group(self.ts_manager)
                utils.kill_proc_group(self.tc_manager)
        else:
            # test without pantheon tunnel when self.flows = 0
            try:
                return self.run_without_tunnel()
            finally:
                utils.kill_proc_group(self.proc_first)
                utils.kill_proc_group(self.proc_second)

    def record_time_stats(self):
        stats_log = path.join(
            self.data_dir, '%s_stats_run%s.log' % (self.cc, self.run_id))
        stats = open(stats_log, 'w')

        # save start time and end time of test
        if self.test_start_time is not None and self.test_end_time is not None:
            test_run_duration = (
                'Start at: %s\nEnd at: %s\n' %
                (self.test_start_time, self.test_end_time))
            sys.stderr.write(test_run_duration)
            stats.write(test_run_duration)

        if self.mode == 'remote':
            ofst_info = ''
            if self.local_ofst is not None:
                ofst_info += 'Local clock offset: %s ms\n' % self.local_ofst

            if self.remote_ofst is not None:
                ofst_info += 'Remote clock offset: %s ms\n' % self.remote_ofst

            if ofst_info:
                sys.stderr.write(ofst_info)
                stats.write(ofst_info)

        stats.close()

    # run congestion control test
    def run(self):
        msg = 'Testing scheme %s for experiment run %d/%d...' % (
            self.cc, self.run_id, self.run_times)
        sys.stderr.write(msg + '\n')

        # setup before running tests
        self.setup()

        # run receiver and sender
        if not self.run_congestion_control():
            sys.stderr.write('Error in testing scheme %s with run ID %d\n' %
                             (self.cc, self.run_id))
            return

        # write runtimes and clock offsets to file
        self.record_time_stats()

        sys.stderr.write('Done testing %s\n' % self.cc)