1
0
Fork 0
mirror of https://passt.top/passt synced 2025-05-19 07:55:34 +02:00

test: Add CI/demo scripts

Not really quick, definitely dirty.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
Stefano Brivio 2021-09-27 15:10:35 +02:00
parent ca325e7583
commit 061519b562
31 changed files with 4816 additions and 0 deletions

276
test/lib/layout Normal file
View file

@ -0,0 +1,276 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/layout - tmux pane layouts
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
# layout_host() - Simple host commands layout with info and host panes
layout_host() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
tmux split-window -h -l '35%' -t passt_test:1.0
PANE_HOST=0
PANE_INFO=1
get_info_cols
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T "test log"
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
info_layout "host commands only"
sleep 1
}
# layout_pasta() - Panes for host, pasta, and separate one for namespace
layout_pasta() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
tmux split-window -v -t passt_test
tmux split-window -h -t passt_test
tmux split-window -h -l '42%' -t passt_test:1.0
PANE_NS=0
PANE_INFO=1
PANE_HOST=2
PANE_PASST=3
get_info_cols
tmux pipe-pane -O -t ${PANE_NS} "cat >> ${LOGDIR}/pane_ns.log"
tmux select-pane -t ${PANE_NS} -T "namespace"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T "test log"
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
tmux select-pane -t ${PANE_PASST} -T "pasta"
info_layout "single pasta instance with namespace"
sleep 1
}
# layout_passt() - Panes for host, passt, and guest
layout_passt() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
tmux split-window -v -t passt_test
tmux split-window -h -t passt_test
tmux split-window -h -l '42%' -t passt_test:1.0
PANE_GUEST=0
PANE_INFO=1
PANE_HOST=2
PANE_PASST=3
get_info_cols
tmux pipe-pane -O -t ${PANE_GUEST} "cat >> ${LOGDIR}/pane_guest.log"
tmux select-pane -t ${PANE_GUEST} -T "guest"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T "test log"
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
tmux select-pane -t ${PANE_PASST} -T "passt"
info_layout "single passt instance with guest"
sleep 1
}
# layout_passt_in_pasta() - Host, passt within pasta, namespace and guest
layout_passt_in_pasta() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
tmux split-window -v -l '45%' -t passt_test
tmux split-window -h -t passt_test
tmux split-window -h -l '42%' -t passt_test:1.0
tmux split-window -v -t passt_test:1.0
PANE_GUEST=0
PANE_NS=1
PANE_INFO=2
PANE_HOST=3
PANE_PASST=4
get_info_cols
tmux pipe-pane -O -t ${PANE_GUEST} "cat >> ${LOGDIR}/pane_guest.log"
tmux select-pane -t ${PANE_GUEST} -T "guest"
tmux pipe-pane -O -t ${PANE_NS} "cat >> ${LOGDIR}/pane_ns.log"
tmux select-pane -t ${PANE_NS} -T "namespace"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T "test log"
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
tmux select-pane -t ${PANE_PASST} -T "passt in pasta (namespace)"
info_layout "passt and guest in namespace, connected by pasta"
sleep 1
}
# layout_two_guests() - Two guest panes, two passt panes, plus host and log
layout_two_guests() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
tmux split-window -v -t passt_test
tmux split-window -h -l '33%'
tmux split-window -h -t passt_test:1.1
tmux split-window -h -l '35%' -t passt_test:1.0
tmux split-window -v -t passt_test:1.0
for i in `seq 0 5`; do
tmux select-pane -t $i -T "${i}"
done
PANE_GUEST_1=0
PANE_GUEST_2=1
PANE_INFO=2
PANE_HOST=3
PANE_PASST_1=4
PANE_PASST_2=5
get_info_cols
tmux pipe-pane -O -t ${PANE_GUEST_1} "cat >> ${LOGDIR}/pane_guest_1.log"
tmux select-pane -t ${PANE_GUEST_1} -T "guest #1 in namespace #1"
tmux pipe-pane -O -t ${PANE_GUEST_2} "cat >> ${LOGDIR}/pane_guest_2.log"
tmux select-pane -t ${PANE_GUEST_2} -T "guest #2 in namespace #2"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T "test log"
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST_1} "cat >> ${LOGDIR}/pane_passt_1.log"
tmux select-pane -t ${PANE_PASST_1} -T "passt #1 in namespace #1"
tmux pipe-pane -O -t ${PANE_PASST_2} "cat >> ${LOGDIR}/pane_passt_2.log"
tmux select-pane -t ${PANE_PASST_2} -T "passt #2 in namespace #2"
info_layout "two guests, two passt instances, in namespaces"
sleep 1
}
# layout_demo_pasta() - Four panes for pasta demo
layout_demo_pasta() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
sleep 1
cmd_write 0 clear
tmux split-window -v -t passt_test
tmux split-window -h -t passt_test
tmux split-window -h -l '42%' -t passt_test:1.0
PANE_NS=0
PANE_INFO=1
PANE_HOST=2
PANE_PASST=3
get_info_cols
tmux pipe-pane -O -t ${PANE_NS} "cat >> ${LOGDIR}/pane_ns.log"
tmux select-pane -t ${PANE_NS} -T "namespace"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T ""
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
tmux select-pane -t ${PANE_PASST} -T "pasta"
sleep 1
}
# layout_demo_passt() - Four panes for passt demo
layout_demo_passt() {
sleep 3
tmux kill-pane -a -t 0
cmd_write 0 clear
sleep 1
cmd_write 0 clear
tmux split-window -v -t passt_test
tmux split-window -h -t passt_test
tmux split-window -h -l '42%' -t passt_test:1.0
PANE_GUEST=0
PANE_INFO=1
PANE_HOST=2
PANE_PASST=3
get_info_cols
tmux pipe-pane -O -t ${PANE_GUEST} "cat >> ${LOGDIR}/pane_guest.log"
tmux select-pane -t ${PANE_GUEST} -T "guest"
tmux send-keys -l -t ${PANE_INFO} 'while cat /tmp/.passt_test_log_pipe; do :; done'
tmux send-keys -t ${PANE_INFO} -N 100 C-m
tmux select-pane -t ${PANE_INFO} -T ""
tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
tmux select-pane -t ${PANE_HOST} -T "host"
tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
tmux select-pane -t ${PANE_PASST} -T "passt in pasta (namespace)"
sleep 1
}

262
test/lib/perf_report Executable file
View file

@ -0,0 +1,262 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/perf_report - Prepare JavaScript report for performance tests
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
PERF_LINK_COUNT=0
PERF_JS="${BASEPATH}/perf.js"
PERF_TEMPLATE_HTML="document.write('"'
Throughput in Gbps, latency in µs. Threads are <span style="font-family: monospace;">iperf3</span> processes, <i>passt</i> and <i>pasta</i> are currently single-threaded.<br/>
Click on numbers to show test execution. Measured at head, commit <span style="font-family: monospace;">__commit__</span>.
<style type="text/CSS">
table td { border: 0px solid; padding: 10px; }
table td { text-align: right; }
table th { text-align: center; font-weight: bold; }
table tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
table.passt tr:nth-child(3n+0) { background-color: #112315; }
table.passt tr:not(:nth-child(3n+0)) td { background-color: #101010; }
table.passt td:nth-child(6n+7) { background-color: #603302; }
table.passt tr:nth-child(1) { background-color: #363e61; }
td:empty { visibility: hidden; }
</style>
<ul>
<li><p>passt</p>
<table class="passt" width="70%">
<tr>
<th/>
<th id="perf_passt_tcp" colspan="__passt_tcp_cols__">TCP, __passt_tcp_threads__ at __passt_tcp_freq__ GHz</th>
<th id="perf_passt_udp" colspan="__passt_udp_cols__">UDP, __passt_udp_threads__ at __passt_udp_freq__ GHz</th>
</tr>
<tr>
<td align="right">MTU:</td>
__passt_tcp_header__
__passt_udp_header__
</tr>
__passt_tcp_LINE__ __passt_udp_LINE__
</table>
<style type="text/CSS">
table td { border: 0px solid; padding: 10px; }
table td { text-align: right; }
table th { text-align: center; font-weight: bold; }
table.pasta tr:nth-child(3n+0) { background-color: #112315; }
table.pasta tr:not(:nth-child(3n+0)) td { background-color: #101010; }
table.pasta td:nth-child(4n+5) { background-color: #603302; }
table.pasta tr:nth-child(1) { background-color: #363e61; }
td:empty { visibility: hidden; }
</style>
</li><li><p>pasta: local connections/traffic</p>
<table class="pasta" width="70%">
<tr>
<th/>
<th id="perf_pasta_lo_tcp" colspan="__pasta_lo_tcp_cols__">TCP, __pasta_lo_tcp_threads__ at __pasta_lo_tcp_freq__ GHz</th>
<th id="perf_pasta_lo_udp" colspan="__pasta_lo_udp_cols__">UDP, __pasta_lo_udp_threads__ at __pasta_lo_udp_freq__ GHz</th>
</th>
<tr>
<td align="right">MTU:</td>
__pasta_lo_tcp_header__
__pasta_lo_udp_header__
</tr>
__pasta_lo_tcp_LINE__ __pasta_lo_udp_LINE__
</table>
</li><li><p>pasta: connections/traffic via tap</p>
<table class="pasta" width="70%">
<tr>
<th/>
<th id="perf_pasta_tap_tcp" colspan="__pasta_tap_tcp_cols__">TCP, __pasta_tap_tcp_threads__ at __pasta_tap_tcp_freq__ GHz</th>
<th id="perf_pasta_tap_udp" colspan="__pasta_tap_udp_cols__">UDP, __pasta_tap_udp_threads__ at __pasta_tap_udp_freq__ GHz</th>
</tr>
<tr>
<td align="right">MTU:</td>
__pasta_tap_tcp_header__
__pasta_tap_udp_header__
</tr>
__pasta_tap_tcp_LINE__ __pasta_tap_udp_LINE__
</table>
</li></ul>'
PERF_TEMPLATE_JS="');
var perf_links = [
"
PERF_TEMPLATE_POST='];
for (var i = 0; i < perf_links.length; i++) {
var obj = document.getElementById(perf_links[i][0]);
obj.addEventListener("click", function(event) {
var ci_video = document.getElementById("ci_video");
var top = ci_video.offsetTop - 5;
event.preventDefault();
ci_video.play();
ci_video.pause();
for (var i = 0; i < perf_links.length; i++) {
if (this.id == perf_links[i][0]) {
ci_video.currentTime = perf_links[i][1] - 10;
}
}
window.scrollTo({ top: top, behavior: "smooth" })
ci_video.play();
}, false);
}
'
# perf_init() - Process first part of template
perf_init() {
echo "${PERF_TEMPLATE_HTML}" > "${PERF_JS}"
perf_report_sub commit "$(echo ${COMMIT} | sed "s/'/\\\'/g")"
}
# perf_fill_lines() - Fill multiple "LINE" directives in template, matching rows
perf_fill_lines() {
while true; do
__file_line="$(sed -n '/__.*_LINE__/{=;q}' "${PERF_JS}")"
[ -z "${__file_line}" ] && break
__line_no=0
__done=0
__line_buf="<tr>"
while true; do
__match_first_td=0
for __t in $(sed -n '/__.*_LINE__/{p;q}' "${PERF_JS}"); do
if [ ${__match_first_td} -eq 1 ]; then
__matching_line_no=0
while true; do
__line_part=
__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__matching_line_no}"'/p')"
[ -z "$(eval echo \$${__var_name})" ] && break
__line_part="$(eval echo \$${__var_name})"
__td_check="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
if [ "${__td_check}" = "${__td_match}" ]; then
__line_part="$(echo "${__line_part}" | sed -n 's/^<td>[^>]*<\/td>\(.*\)$/\1/p')"
break
fi
__matching_line_no=$((__matching_line_no + 1))
done
else
__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__line_no}"'/p')"
[ -z "$(eval echo \$${__var_name})" ] && __done=1 && break
__line_part="$(eval echo \$${__var_name})"
__td_match="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
fi
__line_buf="${__line_buf}${__line_part}"
__match_first_td=1
done
[ ${__done} -eq 1 ] && break
__line_no=$((__line_no + 1))
__line_buf="${__line_buf}</tr><tr>"
done
__line_buf="${__line_buf}</tr>"
__line_buf="$(printf '%s\n' "${__line_buf}" | sed -e 's/[]\/$*.^[]/\\&/g')"
sed -i "${__file_line}s/.*/${__line_buf}/" "${PERF_JS}"
done
}
# perf_finish() - Add trailing backslashes and process ending templates
perf_finish() {
perf_fill_lines
sed -i 's/^.*$/&\\/g' "${PERF_JS}"
echo "${PERF_TEMPLATE_JS}" >> "${PERF_JS}"
echo "${PERF_TEMPLATE_POST}" >> "${PERF_JS}"
}
# perf_report_sub() - Apply simple substitutions in template
perf_report_sub() {
__et="$(printf '%s\n' "${1}" | sed -e 's/[\/&]/\\&/g')"
__es="$(printf '%s\n' "${2}" | sed -e 's/[]\/$*.^[]/\\&/g')"
sed -i 's/__'"${__et}"'__/'"${__es}"'/g' "${PERF_JS}"
}
# perf_report_append() - Append generic string to current JavaScript report
perf_report_append() {
echo "${@}" >> "${PERF_JS}"
}
# perf_report_append() - Append generic string to current template buffer
perf_report_append_js() {
PERF_TEMPLATE_JS="${PERF_TEMPLATE_JS}${@}"
}
# perf_report() - Start of single test report
perf_report() {
__mode="${1}"
__proto="${2}"
__threads="${3}"
__freq="${4}"
REPORT_IN="${__mode}_${__proto}"
[ ${__threads} -eq 1 ] && __threads="one thread" || __threads="${__threads} threads"
perf_report_sub "${__mode}_${__proto}_threads" "${__threads}"
perf_report_sub "${__mode}_${__proto}_freq" "${__freq}"
perf_report_append_js "[ 'perf_${__mode}_${__proto}', $(video_time_now) ],"
}
# perf_th() - Table header for a set of tests
perf_th() {
shift
__th_buf=
__cols_count=0
for __arg; do
__th_buf="${__th_buf}<td>${__arg}</td>"
__cols_count=$((__cols_count + 1))
done
perf_report_sub "${REPORT_IN}_header" "${__th_buf}"
perf_report_sub "${REPORT_IN}_cols" ${__cols_count}
}
# perf_tr() - Main table row
perf_tr() {
__line_no=0
shift
while true; do
[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
__line_no=$((__line_no + 1))
done
eval ${REPORT_IN}_LINE_${__line_no}="\"<td>${@}</td>\""
}
# perf_td() - Single cell with test result
perf_td() {
__rewind="${1}"
shift
__line_no=0
while true; do
[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
__line_no=$((__line_no + 1))
done
__line_no=$((__line_no - 1))
[ -z "${1}" ] && __id=0 || __id="perf_${PERF_LINK_COUNT}"
eval ${REPORT_IN}_LINE_${__line_no}=\""\${${REPORT_IN}_LINE_${__line_no}}<td id=\"${__id}\">${1}</td>"\"
[ -z "${1}" ] && return
perf_report_append_js "[ '${__id}', $(($(video_time_now) - ${__rewind})) ],"
PERF_LINK_COUNT=$((PERF_LINK_COUNT + 1))
}
# perf_te() - End of a table, currently unused
pert_te() {
:
}

322
test/lib/setup Executable file
View file

@ -0,0 +1,322 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/setup - Set up and tear down passt and pasta environments
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
VCPUS="$( [ $(nproc) -ge 8 ] && echo 4 || echo $(( $(nproc) / 2 + 1 )) )"
__mem_kib="$(sed -n 's/MemTotal:[ ]*\([0-9]*\) kB/\1/p' /proc/meminfo)"
VMEM="$((${__mem_kib} / 1024 / 8))"
# setup_build() - Set up pane layout for build tests
setup_build() {
MODE=build
layout_host
}
# setup_passt() - Build guest initrd with mbuto, start qemu and passt
setup_passt() {
MODE=passt
layout_passt
__mbuto_dir="$(mktemp -d)"
pane_run GUEST "git -C ${__mbuto_dir} clone https://mbuto.lameexcu.se/mbuto/"
pane_wait GUEST
pane_run GUEST "${__mbuto_dir}/mbuto/mbuto -p passt -c lz4 -f mbuto.img"
pane_wait GUEST
rm -rf "${__mbuto_dir}"
# Ports:
#
# guest | host
# --------------|---------------------
# 10001 as server | forwarded to guest
# 10003 | as server
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/passt.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST "./passt ${__opts} -f -t 10001 -u 10001"
sleep 1
pane_run GUEST './qrap 5 kvm -m '${VMEM}' -cpu host -smp '${VCPUS} \
'-kernel' "/boot/vmlinuz-$(uname -r)" \
'-initrd mbuto.img -nographic -serial stdio' \
'-nodefaults ' \
'-append "console=ttyS0 mitigations=off apparmor=0 ' \
'virtio-net.napi_tx=1"' \
"-device virtio-net-pci,netdev=hostnet0,x-txburst=16384"\
"-netdev socket,fd=5,id=hostnet0"
pane_wait GUEST
}
# setup_pasta() - Create a network and user namespace, connect pasta to it
setup_pasta() {
MODE=pasta
layout_pasta
pane_run NS "unshare -rUn /bin/sh "
pane_wait NS
pane_run NS 'echo $$'
pane_wait NS
__pasta_pid="$(pane_parse NS)"
# Ports:
#
# ns | host
# ------------------|---------------------
# 10002 as server | spliced to ns
# 10003 spliced to init | as server
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/pasta.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST "./pasta ${__opts} -f -t 10002 -T 10003 -u 10002 -U 10003 ${__pasta_pid}"
sleep 1
}
# setup_passt_in_ns() - Set up namespace (with pasta), run qemu and passt into it
setup_passt_in_ns() {
MODE=passt_in_ns
layout_passt_in_pasta
# Ports:
#
# guest | ns | host
# -------------|--------------------|-----------------
# 10001 as server | forwarded to guest | spliced to ns
# 10002 | as server | spliced to ns
# 10003 | spliced to init | as server
# 10011 as server | forwarded to guest | spliced to ns
# 10012 | as server | spliced to ns
# 10013 | spliced to init | as server
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/pasta_with_passt.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST "./pasta ${__opts} -t 10001,10002,10011,10012 -T 10003,10013 -u 10001,10002,10011,10012 -U 10003,10013"
sleep 1
pane_run PASST ''
pane_wait PASST
pane_run PASST 'echo $$'
pane_wait PASST
__ns_pid="$(pane_parse PASST)"
pane_run GUEST "nsenter -t ${__ns_pid} -U -n --preserve-credentials"
pane_run NS "nsenter -t ${__ns_pid} -U -n --preserve-credentials"
pane_wait GUEST
pane_wait NS
pane_run NS "ip -j li sh | jq -rM '.[] | select(.link_type == \"ether\").ifname'"
pane_wait NS
__ifname="$(pane_parse NS)"
pane_run NS "/sbin/udhcpc -i ${__ifname}"
pane_wait NS
sleep 2
pane_run NS "/sbin/dhclient -6 ${__ifname}"
pane_wait NS
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/passt_in_pasta.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
#pane_run PASST "valgrind --max-stackframe=2097208 ./passt -f ${__opts} -t 10001,10011 -u 10001,10011"
pane_run PASST "./passt -f ${__opts} -t 10001,10011 -u 10001,10011"
sleep 1
pane_run GUEST './qrap 5 kvm -m '${VMEM}' -cpu host -smp '${VCPUS} \
'-kernel' "/boot/vmlinuz-$(uname -r)" \
'-initrd mbuto.img -nographic -serial stdio' \
'-nodefaults ' \
'-append "console=ttyS0 mitigations=off apparmor=0 ' \
'virtio-net.napi_tx=1"' \
"-device virtio-net-pci,netdev=hostnet0,x-txburst=131072"\
"-netdev socket,fd=5,id=hostnet0"
pane_wait GUEST
}
# setup_two_guests() - Set up two namespace, run qemu and passt in both of them
setup_two_guests() {
MODE=passt_in_ns
layout_two_guests
# Ports:
#
# guest #1 | guest #2 | ns #1 | ns #2 | host
# --------- |-----------|-----------|------------|------------
# 10001 as server | | to guest | to init | to ns #1
# 10002 | | as server | | to ns #1
# 10003 | | to init | to init | as server
# 10004 | as server | to init | to guest | to ns #2
# 10005 | | | as server | to ns #2
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/pasta_1.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST_1 "./pasta ${__opts} -t 10001,10002 -T 10003,10004 -u 10001,10002 -U 10003,10004"
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/pasta_2.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST_2 "./pasta ${__opts} -t 10004,10005 -T 10003,10001 -u 10004,10005 -U 10003,10001"
sleep 1
pane_run PASST_1 ''
pane_run PASST_2 ''
pane_wait PASST_1
pane_wait PASST_2
pane_run PASST_1 'echo $$'
pane_run PASST_2 'echo $$'
pane_wait PASST_1
pane_wait PASST_2
__ns1_pid="$(pane_parse PASST_1)"
__ns2_pid="$(pane_parse PASST_2)"
pane_run GUEST_1 "nsenter -t ${__ns1_pid} -U -n --preserve-credentials"
pane_run GUEST_2 "nsenter -t ${__ns2_pid} -U -n --preserve-credentials"
pane_run PASST_1 "ip -j li sh | jq -rM '.[] | select(.link_type == \"ether\").ifname'"
pane_wait PASST_1
__ifname="$(pane_parse PASST_1)"
pane_run GUEST_1 "/sbin/udhcpc -i ${__ifname}"
pane_run GUEST_2 "/sbin/udhcpc -i ${__ifname}"
pane_wait GUEST_1
pane_wait GUEST_2
sleep 2
pane_run GUEST_1 "/sbin/dhclient -6 ${__ifname}"
pane_run GUEST_2 "/sbin/dhclient -6 ${__ifname}"
pane_wait GUEST_1
pane_wait GUEST_2
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/passt_1.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST_1 "./passt -f ${__opts} -t 10001 -u 10001"
sleep 1
__opts=
[ ${PCAP} -eq 1 ] && __opts="${__opts} -p /tmp/passt_2.pcap"
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
pane_run PASST_2 "./passt -f ${__opts} -t 10004 -u 10004"
pane_run GUEST_2 'cp mbuto.img mbuto_2.img'
pane_wait GUEST_2
pane_run GUEST_1 './qrap 5 kvm -m '${VMEM}' -cpu host -smp '${VCPUS} \
'-kernel' "/boot/vmlinuz-$(uname -r)" \
'-initrd mbuto.img -nographic -serial stdio' \
'-nodefaults ' \
'-append "console=ttyS0 mitigations=off apparmor=0 ' \
'virtio-net.napi_tx=1"' \
"-device virtio-net-pci,netdev=hostnet0,x-txburst=16384"\
"-netdev socket,fd=5,id=hostnet0"
pane_run GUEST_2 './qrap 5 kvm -m '${VMEM}' -cpu host -smp '${VCPUS} \
'-kernel' "/boot/vmlinuz-$(uname -r)" \
'-initrd mbuto_2.img -nographic -serial stdio' \
'-nodefaults ' \
'-append "console=ttyS0 mitigations=off apparmor=0 ' \
'virtio-net.napi_tx=1"' \
"-device virtio-net-pci,netdev=hostnet0,x-txburst=16384"\
"-netdev socket,fd=5,id=hostnet0"
pane_wait GUEST_1
pane_wait GUEST_2
}
# teardown_passt() - Kill qemu and passt
teardown_passt() {
tmux send-keys -t ${PANE_PASST} "C-c"
pane_wait PASST
tmux send-keys -t ${PANE_GUEST} "C-c"
pane_wait GUEST
}
# teardown_passt() - Exit namespace, kill pasta process
teardown_pasta() {
tmux send-keys -t ${PANE_PASST} "C-c"
pane_wait PASST
tmux send-keys -t ${PANE_NS} "C-d"
pane_wait NS
}
# teardown_passt_in_ns() - Exit namespace, kill qemu, passt and pasta
teardown_passt_in_ns() {
tmux send-keys -t ${PANE_GUEST} "C-c"
pane_wait GUEST
tmux send-keys -t ${PANE_GUEST} "C-d"
tmux send-keys -t ${PANE_PASST} "C-c"
pane_wait PASST
tmux send-keys -t ${PANE_PASST} "C-d"
tmux send-keys -t ${PANE_NS} "C-d"
pane_wait GUEST
pane_wait NS
pane_wait PASST
}
# teardown_two_guests() - Exit namespaces, kill qemu processes, passt and pasta
teardown_two_guests() {
tmux send-keys -t ${PANE_GUEST_1} "C-c"
pane_wait GUEST_1
tmux send-keys -t ${PANE_GUEST_1} "C-d"
tmux send-keys -t ${PANE_GUEST_2} "C-c"
pane_wait GUEST_2
tmux send-keys -t ${PANE_GUEST_2} "C-d"
tmux send-keys -t ${PANE_PASST_1} "C-c"
pane_wait PASST_1
tmux send-keys -t ${PANE_PASST_1} "C-d"
tmux send-keys -t ${PANE_PASST_2} "C-c"
pane_wait PASST_2
tmux send-keys -t ${PANE_PASST_2} "C-d"
tmux send-keys -t ${PANE_NS_1} "C-d"
tmux send-keys -t ${PANE_NS_2} "C-d"
pane_wait GUEST_1
pane_wait GUEST_2
ns_1_wait
ns_2_wait
pane_wait PASST_1
pane_wait PASST_2
}
# setup() - Run setup_*() functions
# $*: Suffix list of setup_*() functions to be called
setup() {
for arg do
eval setup_${arg}
done
}
# teardown() - Run teardown_*() functions
# $*: Suffix list of teardown_*() functions to be called
teardown() {
for arg do
eval teardown_${arg}
done
}

610
test/lib/term Executable file
View file

@ -0,0 +1,610 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/term - Set up tmux sessions and panes, handle terminals and logs
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
# Commands of X terminals for "CI" and "demo" runs
DEMO_XTERM="cool-retro-term --verbose --workdir"
CI_XTERM="mate-terminal --hide-menubar --profile=passt_ci --working-directory"
STATUS_FILE=
STATUS_FILE_NTESTS=
STATUS_FILE_INDEX=0
STATUS_COLS=
STATUS_PASS=0
STATUS_FAIL=0
PR_RED='\033[1;31m'
PR_GREEN='\033[1;32m'
PR_YELLOW='\033[1;33m'
PR_BLUE='\033[1;34m'
PR_NC='\033[0m'
PR_DELAY_INIT=100 # ms
# info() - Highlight test log pane, print message to it and to log file
# $@: Message to print
info() {
tmux select-pane -t ${PANE_INFO}
echo "${@}" >> /tmp/.passt_test_log_pipe
echo "${@}" >> "${LOGFILE}"
}
# info_n() - Highlight, print message to pane and to log file without newline
# $@: Message to print
info_n() {
tmux select-pane -t ${PANE_INFO}
printf "${@}" >> /tmp/.passt_test_log_pipe
printf "${@}" >> "${LOGFILE}"
}
# info_nolog() - Highlight test log pane, print message to it
# $@: Message to print
info_nolog() {
tmux select-pane -t ${PANE_INFO}
echo "${@}" >> /tmp/.passt_test_log_pipe
}
# info_nolog() - Print message to log file
# $@: Message to print
log() {
echo "${@}" >> "${LOGFILE}"
}
# info_nolog_n() - Send message to pane without highlighting it, without newline
# $@: Message to print
info_nolog_n() {
tmux send-keys -l -t ${PANE_INFO} "${@}"
}
# info_sep() - Print given separator, horizontally filling test log pane
# $1: Separator character
info_sep() {
tmux send-keys -l -N ${STATUS_COLS} -t ${PANE_INFO} "${1}"
tmux send-keys -t ${PANE_INFO} C-m
}
# sleep_char() - Sleep for typed characted resembling interactive input
# $1: Character typed to pane
sleep_char() {
[ ${FAST} -eq 1 ] && return
if [ "${1}" = " " ]; then
PR_DELAY=$((PR_DELAY + 40))
elif [ -n "$(printf '%s' "${1}" | tr -d [:alnum:])" ]; then
PR_DELAY=$((PR_DELAY + 30))
elif [ ${PR_DELAY} -ge 30 ]; then
PR_DELAY=$((PR_DELAY / 3 * 2))
fi
sleep "$(printf 0.%03i ${PR_DELAY})" || sleep 1
}
# display_delay() - Simple delay, omitted if $FAST is set
display_delay() {
[ ${FAST} -eq 1 ] && return
sleep "${1}" || sleep 1
}
# switch_pane() - Highlight given pane and reset character delay
# $1: Pane number
switch_pane() {
tmux select-pane -t ${1}
PR_DELAY=${PR_DELAY_INIT}
display_delay "0.2"
}
# cmd_write() - Write a command to a pane, letter by letter, and execute it
# $1: Pane number
# $@: Command to issue
cmd_write() {
__pane_no=${1}
shift
switch_pane ${__pane_no}
__str="${@}"
while [ -n "${__str}" ]; do
__rem="${__str#?}"
__first="${__str%"$__rem"}"
if [ "${__first}" = ";" ]; then
tmux send-keys -t ${__pane_no} -l '\;'
else
tmux send-keys -t ${__pane_no} -l "${__first}"
fi
sleep_char "${__first}"
__str="${__rem}"
done
tmux send-keys -t ${__pane_no} "C-m"
}
# text_write() - Write text to info pane, letter by letter
# $1: Pane number
# $@: Command to issue
text_write() {
__str="${@}"
while [ -n "${__str}" ]; do
__rem="${__str#?}"
__first="${__str%"$__rem"}"
if [ "${__first}" = ";" ]; then
tmux send-keys -t ${PANE_INFO} -l '\;'
else
tmux send-keys -t ${PANE_INFO} -l "${__first}"
fi
sleep_char "${__first}"
__str="${__rem}"
done
}
# text_backspace() - Slow backspace motion for demo
# $1: Number of backspace characters
text_backspace() {
for __count in $(seq 0 ${1}); do
tmux send-keys -t ${PANE_INFO} Bspace
sleep 0.1
done
}
# em_write() - Write to log pane in red, for demo
# $@: Text
em_write() {
info_n "${PR_RED}${@}${PR_NC}"
}
# pane_kill() - Kill a single pane given its name
# $1: Pane name
pane_kill() {
__pane_number=$(eval echo \$PANE_${1})
tmux kill-pane -t ${__pane_number}
}
# pane_highlight() - Highlight a single pane given its name
# $1: Pane name
pane_highlight() {
__pane_number=$(eval echo \$PANE_${1})
switch_pane ${__pane_number}
sleep 3
}
# pane_run() - Issue a command in given pane name
# $1: Pane name
# $@: Command to issue
pane_run() {
__pane_name="${1}"
shift
__pane_number=$(eval echo \$PANE_${__pane_name})
eval ${__pane_name}_LAST_CMD=\"\${@}\"
cmd_write ${__pane_number} "${@}"
}
# pane_wait() - Wait for command to be done in given pane name
# $1: Pane name
pane_wait() {
__pane_lc="$(echo "${1}" | tr [A-Z] [a-z])"
while [ "$(tail -n1 ${LOGDIR}/pane_${__pane_lc}.log)" != '$ ' ] && \
[ "$(tail -n1 ${LOGDIR}/pane_${__pane_lc}.log)" != '# ' ] && \
[ "$(tail -n1 ${LOGDIR}/pane_${__pane_lc}.log)" != '# # ' ]; do
sleep 0.1 || sleep 1
done
}
# pane_parse() - Print last line, @EMPTY@ if command had no output
# $1: Pane name
pane_parse() {
__pane_lc="$(echo "${1}" | tr [A-Z] [a-z])"
__buf="$(tail -n2 ${LOGDIR}/pane_${__pane_lc}.log | head -n1 | tr -d -c [:print:])"
[ "# $(eval printf '%s' \"\$${1}_LAST_CMD\")" != "${__buf}" ] && \
[ "$ $(eval printf '%s' \"\$${1}_LAST_CMD\")" != "${__buf}" ] &&
printf '%s' "${__buf}" || printf '@EMPTY@'
}
# status_file_end() - Display and log messages when tests from one file are done
status_file_end() {
[ -z "${STATUS_FILE}" ] && return
info_sep "="
log
tmux select-pane -t ${PANE_INFO} -T ""
STATUS_FILE=
}
# status_file_start() - Display and log messages when tests from one file start
status_file_start() {
switch_pane ${PANE_INFO}
status_file_end
info_nolog "Starting tests in file: ${1}\n"
log "=== ${1}"
tmux select-pane -t ${PANE_INFO} -T "${1}"
STATUS_FILE="${1}"
STATUS_FILE_NTESTS="${2}"
STATUS_FILE_INDEX=0
}
# status_file_start() - Display and log messages when a single test starts
status_test_start() {
switch_pane ${PANE_INFO}
info_nolog "Starting test: ${1}"
log "> ${1}"
STATUS_FILE_INDEX=$((STATUS_FILE_INDEX + 1))
tmux select-pane -t ${PANE_INFO} -T "${STATUS_FILE} [${STATUS_FILE_INDEX}/${STATUS_FILE_NTESTS}] - ${1}"
}
# info_check() - Display and log messages for a single test condition check
info_check() {
switch_pane ${PANE_INFO}
printf "${PR_YELLOW}?${PR_NC} ${@}" >> /tmp/.passt_test_log_pipe
printf "? ${@}" >> "${LOGFILE}"
}
# info_check_passed() - Display and log a new line when a check passes
info_check_passed() {
switch_pane ${PANE_INFO}
printf "\n" >> /tmp/.passt_test_log_pipe
printf "\n" >> ${LOGFILE}
}
# info_check_failed() - Display and log messages when a check fails
info_check_failed() {
switch_pane ${PANE_INFO}
printf " ${PR_RED}!${PR_NC}\n" >> /tmp/.passt_test_log_pipe
printf " < failed.\n" >> "${LOGFILE}"
}
# info_passed() - Display, log, and make status bar blink when a test passes
info_passed() {
switch_pane ${PANE_INFO}
info_nolog "...${PR_GREEN}passed${PR_NC}.\n"
log "...passed."
log
for i in `seq 1 3`; do
tmux set status-right-style 'bg=colour1 fg=colour2 bold'
sleep "0.1"
tmux set status-right-style 'bg=colour1 fg=colour233 bold'
sleep "0.1"
done
}
# info_failed() - Display, log, and make status bar blink when a test passes
info_failed() {
switch_pane ${PANE_INFO}
info_nolog "...${PR_RED}failed${PR_NC}.\n"
log "...failed."
log
for i in `seq 1 3`; do
tmux set status-right-style 'bg=colour1 fg=colour196 bold'
sleep "0.1"
tmux set status-right-style 'bg=colour1 fg=colour233 bold'
sleep "0.1"
done
pause_continue \
"Press any key to pause test session" \
"Resuming in " \
"Paused, press any key to continue" \
5
}
# info_skipped() - Display and log skipped test
info_skipped() {
switch_pane ${PANE_INFO}
info_nolog "...${PR_YELLOW}skipped${PR_NC}.\n"
log "...skipped."
log
}
# info_layout() - Display string for new test layout
info_layout() {
switch_pane ${PANE_INFO}
info_nolog "Test layout: ${PR_BLUE}${@}${PR_NC}.\n"
}
# status_test_ok() - Update counter of passed tests, log and display message
status_test_ok() {
STATUS_PASS=$((STATUS_PASS + 1))
tmux set status-right "PASS: ${STATUS_PASS} | FAIL: ${STATUS_FAIL} | #(TZ="UTC" date -Iseconds)"
info_passed
}
# status_test_fail() - Update counter of failed tests, log and display message
status_test_fail() {
STATUS_FAIL=$((STATUS_FAIL + 1))
tmux set status-right "PASS: ${STATUS_PASS} | FAIL: ${STATUS_FAIL} | #(TZ="UTC" date -Iseconds)"
info_failed
}
# status_test_fail() - Update counter of failed tests, log and display message
status_test_skip() {
info_skipped
}
# table_header() - Print table header to log pane
# $1: Header description
# $@: Column headers
table_header() {
perf_th ${@}
__ifs="${IFS}"
IFS=" "
__desc="${1}"
shift
__max_len=4
__count=0
for __h in ${@}; do
[ ${#__h} -gt ${__max_len} ] && __max_len=${#__h}
__count=$((__count + 1))
done
# > xxxx |<
__outer_len=$((__max_len + 3))
__width_fields=$((__outer_len * __count + 1))
TABLE_HEADER_LEFT=$((STATUS_COLS - __width_fields))
TABLE_CELL_SIZE=$((__max_len + 2))
TABLE_COLS=${__count}
__pad_left=$((TABLE_HEADER_LEFT - ${#__desc} - 2))
__buf="$(printf %-${__pad_left}s%s "" "${__desc}: ")"
for __h in ${@}; do
__pad_left=$(( (TABLE_CELL_SIZE - ${#__h} + 1) / 2))
__pad_right=$(( (TABLE_CELL_SIZE - ${#__h}) / 2))
__buf="${__buf}$(printf "|%-${__pad_left}s%s%-${__pad_right}s" "" ${__h} "")"
done
info_n "${__buf}|"
IFS="${__ifs}"
}
# table_row() - Print main table row to log pane
# $@: Column headers
table_row() {
perf_tr ${@}
__line="${@}"
__buf="$(printf %-${TABLE_HEADER_LEFT}s "")"
for __i in $(seq 1 ${TABLE_COLS}); do
__buf="${__buf}|"
for __j in $(seq 1 ${TABLE_CELL_SIZE}); do
__buf="${__buf}-"
done
done
info_n "\n${__buf}|\n"
__pad_left=$(( (TABLE_HEADER_LEFT - ${#__line} + 1) / 2))
__pad_right=$(( (TABLE_HEADER_LEFT - ${#__line}) / 2))
info_n "$(printf "%-${__pad_left}s%s%-${__pad_right}s|" "" "${__line}" "")"
}
# table_line() - Print simple line to log pane
# $@: Column headers
table_line() {
perf_tr ${@}
__line="${@}"
info_n "\n"
__pad_left=$(( (TABLE_HEADER_LEFT - ${#__line} + 1) / 2))
__pad_right=$(( (TABLE_HEADER_LEFT - ${#__line}) / 2))
info_n "$(printf "%-${__pad_left}s%s%-${__pad_right}s|" "" "${__line}" "")"
}
table_cell() {
__len="${1}"
shift
__content="${@}"
__pad_left=$((TABLE_CELL_SIZE - __len - 1))
info_n "$(printf "%-${__pad_left}s%s |" "" "${__content}")"
}
table_end() {
__buf="$(printf %-${TABLE_HEADER_LEFT}s "")"
for __i in $(seq 1 ${TABLE_COLS}); do
__buf="${__buf}'"
for __j in $(seq 1 ${TABLE_CELL_SIZE}); do
__buf="${__buf}-"
done
done
info_n "\n${__buf}'\n"
}
table_value_throughput() {
[ "${1}" = "-" ] && table_cell 1 "-" && perf_td 0 "" && return 0
__v="$(echo "scale=1; x=( ${1} + 10^8 / 2 ) / 10^9; if ( x < 1 && x > 0 ) print 0; x" | bc -l)"
perf_td 31 "${__v}"
__red="${2}"
__yellow="${3}"
if [ "$(echo "${__v} < ${__red}" | bc -l)" = "1" ]; then
table_cell ${#__v} "${PR_RED}${__v}${PR_NC}"
return 1
elif [ "$(echo "${__v} < ${__yellow}" | bc -l)" = "1" ]; then
table_cell ${#__v} "${PR_YELLOW}${__v}${PR_NC}"
return 1
else
table_cell ${#__v} "${PR_GREEN}${__v}${PR_NC}"
return 0
fi
}
table_value_latency() {
[ "${1}" = "-" ] && table_cell 1 "-" && perf_td 0 "" && return 0
__v="$(echo "scale=6; 1 / ${1} * 10^6" | bc -l)"
__v="${__v%.*}"
perf_td 11 "${__v}"
__red="${2}"
__yellow="${3}"
if [ "$(echo "${__v} > ${__red}" | bc -l)" = "1" ]; then
table_cell ${#__v} "${PR_RED}${__v}${PR_NC}"
return 1
elif [ "$(echo "${__v} > ${__yellow}" | bc -l)" = "1" ]; then
table_cell ${#__v} "${PR_YELLOW}${__v}${PR_NC}"
return 1
else
table_cell ${#__v} "${PR_GREEN}${__v}${PR_NC}"
return 0
fi
}
# pause_continue() - Pause for a while, wait for keystroke, resume on second one
pause_continue() {
tmux select-pane -t ${PANE_INFO}
info_nolog "${1}"
info_nolog_n "${2}"
__pause_tmp="$(mktemp)"
echo >> "${__pause_tmp}"
tmux pipe-pane -O -t ${PANE_INFO} "cat >> ${__pause_tmp}"
__pane_buf=
__wait=0
sleep 1
for __i in $(seq ${4} -1 0); do
if [ "$(tail -n1 ${__pause_tmp} | tr -d -c [:print:])" != "${__pane_buf}" ]; then
__wait=1
break
fi
if [ ${__i} -ne ${4} ]; then
tmux send-keys -t ${PANE_INFO} Bspace
tmux send-keys -t ${PANE_INFO} Bspace
__pane_buf="${__pane_buf} "
fi
info_nolog_n "${__i} "
__pane_buf="${__pane_buf}${__i} "
sleep 1
done
if [ ${__wait} -eq 1 ]; then
tmux send-keys -t ${PANE_INFO} Bspace
tmux send-keys -t ${PANE_INFO} Bspace
info_nolog ""
info_nolog "${3}"
__pane_buf="$(tail -n1 ${__pause_tmp})"
while true; do
[ "$(tail -n1 ${__pause_tmp})" != "${__pane_buf}" ] && break
sleep 1
done
fi
tmux pipe-pane -O -t ${PANE_INFO} ""
rm "${__pause_tmp}"
info_nolog ""
}
# run_term() - Start tmux session, X terminal if requested, running entry point
run_term() {
export SHELL="/bin/sh"
if [ ${CI} -eq 1 ]; then
__xterm_done="$(mktemp)"
${CI_XTERM} "$(pwd)" -e "sh -c \"printf '\e[8;50;240t'; tmux new-session -s passt_test ./ci from_term; echo >${__xterm_done}\""
while ! [ -s "${__xterm_done}" ]; do sleep 1; done
rm "${__xterm_done}"
elif [ ${DEMO} -eq 1 ]; then
while true; do
${DEMO_XTERM} "$(pwd)" -e sh -c 'tmux new-session -s passt_test ./run_demo from_term'
[ $? -ne 0 ] && { tmux kill-session -t passt_test; continue; }
break
done
else
tmux new-session -s passt_test ./run from_term
fi
}
# term() - Set up terminal window and panes for regular tests or CI
term() {
tmux set status-interval 1
tmux rename-window ''
tmux set window-status-format '#W'
tmux set window-status-current-format '#W'
tmux set status-left ''
tmux set window-status-separator ''
tmux set window-status-style 'bg=colour1 fg=colour233 bold'
tmux set status-style 'bg=colour1 fg=colour233 bold'
tmux set status-right-style 'bg=colour1 fg=colour233 bold'
tmux new-window -n "Testing commit: ${COMMIT}"
tmux set window-status-format '#W'
tmux set window-status-current-format '#W'
tmux set status-left ''
tmux set window-status-separator ''
tmux set window-status-current-style 'bg=colour1 fg=colour233 bold'
tmux set status-right '#(TZ="UTC" date -Iseconds)'
tmux set status-right-length 50
tmux set status-right-style 'bg=colour1 fg=colour233 bold'
tmux set history-limit 500000
tmux select-pane -t 0 -T ''
tmux set pane-border-format '#T'
tmux set pane-border-style 'fg=colour2 bg=colour233'
tmux set pane-active-border-style 'fg=colour233 bg=colour4 bold'
tmux set pane-border-status bottom
}
# term_demo() - Set up terminal window and panes for demo
term_demo() {
tmux set status-interval 1
tmux rename-window ''
tmux set window-status-format '#W'
tmux set window-status-current-format '#W'
tmux set status-left ''
tmux set window-status-separator ''
tmux set window-status-style 'bg=colour1 fg=colour15 bold'
tmux set status-right ''
tmux set status-style 'bg=colour1 fg=colour15 bold'
tmux set status-right-style 'bg=colour1 fg=colour15 bold'
tmux new-window -n "Demo at commit: ${COMMIT}"
tmux set window-status-format '#W'
tmux set window-status-current-format '#W'
tmux set status-left ''
tmux set window-status-separator ''
tmux select-pane -t 0 -T ''
tmux set pane-border-format '#T'
tmux set pane-border-style 'fg=colour2 bg=colour233'
tmux set pane-active-border-style 'fg=colour15 bg=colour4 bold'
tmux set pane-border-status bottom
}

378
test/lib/test Executable file
View file

@ -0,0 +1,378 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/test - List tests and run them, evaluating directives from files
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
# Empty, 'passt' or 'pasta', to match against 'onlyfor' directive
MODE=
# test_iperf3() - Ugly helper for iperf3c/iperf3s directives
# $1: Role: client or server
# $2: Pane name, can be lowercase
# $3: Destination name or address for client
# $4: Port number, ${i} is translated to process index
# $5: Number of processes to run in parallel
# $@: Options
test_iperf3() {
__role="${1}"; shift
__pane="$(echo "${1}" | tr [a-z] [A-Z])"; shift
[ "${__role}" = "client" ] && __dest="${1}" && shift || __dest=""
__port="${1}"; shift
__procs="$((${1} - 1))"; shift
[ "${__role}" = "server" ] && __role_opt="-c" || __role_opt="-s1J"
if [ ${__role} = "client" ]; then
UDP_CLIENT=0
for __opt in ${@}; do
[ "${__opt}" = "-u" ] && UDP_CLIENT=1
done
(
sleep 2
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}');' \
'do ( iperf3 -c '"${__dest}"' -p '"${__port}" \
"${@}" ' -T s${i} & echo $! > c${i}.pid & ); done'
sleep 36
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}'); do'\
'kill -INT $(cat c${i}.pid) 2>/dev/null; done'
) &
return
fi
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}'); do' \
':> s${i}.bw; done'
pane_wait "${__pane}"
if [ ${UDP_CLIENT} -eq 0 ]; then
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}');' \
'do ( ( iperf3 -s1J -p '"${__port} ${@}" \
'& echo $! > s${i}.pid ) 2>/dev/null' \
'| jq -rM ".end.sum_received.bits_per_second"' \
'> s${i}.bw & );' \
'done'
else
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}');' \
'do ( ( iperf3 -s1J -i 30 -p '"${__port} ${@}" \
'& echo $! > s${i}.pid ) 2>/dev/null' \
'| jq -rM ".intervals[0].sum.bits_per_second"' \
'> s${i}.bw & );' \
'done'
fi
pane_wait "${__pane}"
sleep 38
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}'); do' \
'kill -INT $(cat s${i}.pid) 2>/dev/null; done'
sleep 1
pane_wait "${__pane}"
pane_run "${__pane}" '(cat s*.bw |' \
'sed '"'"'s/\(.*\)/\1\+/g'"'"' |' \
'tr -d "\n"; echo 0) | bc -l'
pane_wait "${__pane}"
pane_parse "${__pane}"
pane_run "${__pane}" 'for i in $(seq 0 '${__procs}'); do' \
'rm -f s${i}.bw; done'
pane_wait "${__pane}"
}
# test_one() - Run a single test file evaluating directives
# $1: Name of test file, relative to test/ directory
test_one() {
__dirclean=
__test_file="test/${1}"
__type="$(file -b --mime-type ${__test_file})"
if [ "${__type}" = "text/x-shellscript" ]; then
status_file_start "${1}" 1
"${__test_file}" && status_test_ok || status_test_fail
return
fi
__ntests="$(grep -c "^test$(printf '\t')" "${__test_file}")"
[ ${DEMO} -eq 0 ] && status_file_start "${1}" "${__ntests}"
[ ${CI} -eq 1 ] && video_link "${1}"
__subs=
__nok=-1
__perf_nok=0
__skip=0
while IFS= read -r __line; do
# Strip comments
__line="${__line%%#*}"
# tab-split command and arguments, apply variable substitutions
__cmd="${__line%%$(printf '\t')*}"
__arg="${__line#*$(printf '\t')*}"
__arg="$(subs_apply "${__subs}" "${__arg}")"
[ ${__nok} -eq 1 ] && [ "${__cmd}" != "test" ] && continue
case ${__cmd} in
"tempdir")
__tmpdir="$(mktemp -d)"
__subs="$(list_add_pair "${__subs}" "__${__arg}__" "${__tmpdir}")"
__dirclean="$(list_add "${__dirclean}" "${__tmpdir}")"
;;
"temp")
__tmpfile="$(mktemp)"
__subs="$(list_add_pair "${__subs}" "__${__arg}__" "${__tmpfile}")"
__dirclean="$(list_add "${__dirclean}" "${__tmpfile}")"
;;
"test")
[ ${__perf_nok} -eq 0 ] || __nok=1
[ ${__nok} -eq 1 ] && status_test_fail
[ ${__nok} -eq 0 ] && status_test_ok
status_test_start "${__arg}"
__nok=0
__perf_nok=0
;;
"host")
pane_run HOST "${__arg}"
pane_wait HOST
;;
"hostb")
pane_run HOST "${__arg}"
;;
"hostw")
pane_wait HOST
;;
"htools")
pane_run HOST 'which '"${__arg}"' >/dev/null || echo skip'
pane_wait HOST
[ "$(pane_parse HOST)" = "skip" ] && { __skip=1; break; }
;;
"passt")
pane_run PASST "${__arg}"
pane_wait PASST
;;
"passtb")
pane_run PASST "${__arg}"
;;
"passtw")
pane_wait PASST
;;
"pout")
__varname="${__arg%% *}"
pane_run PASST "${__arg#* }"
pane_wait PASST
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse PASST)")"
;;
"guest")
pane_run GUEST "${__arg}"
pane_wait GUEST
;;
"guestb")
pane_run GUEST "${__arg}"
;;
"guestw")
pane_wait GUEST
;;
"guest1")
pane_run GUEST_1 "${__arg}"
pane_wait GUEST_1
;;
"guest1b")
pane_run GUEST_1 "${__arg}"
;;
"guest1w")
pane_wait GUEST_1
;;
"gtools")
pane_run GUEST 'which '"${__arg}"' >/dev/null || echo skip'
pane_wait GUEST
[ "$(pane_parse GUEST)" = "skip" ] && { __skip=1; break; }
;;
"g1tools")
pane_run GUEST_1 'which '"${__arg}"' >/dev/null || echo skip'
pane_wait GUEST_1
[ "$(pane_parse GUEST_1)" = "skip" ] && { __skip=1; break; }
;;
"g2tools")
pane_run GUEST_2 'which '"${__arg}"' >/dev/null || echo skip'
pane_wait GUEST_2
[ "$(pane_parse GUEST_2)" = "skip" ] && { __skip=1; break; }
;;
"guest2")
pane_run GUEST_2 "${__arg}"
pane_wait GUEST_2
;;
"guest2b")
pane_run GUEST_2 "${__arg}"
;;
"guest2w")
pane_wait GUEST_2
;;
"ns")
pane_run NS "${__arg}"
pane_wait NS
;;
"nsb")
pane_run NS "${__arg}"
;;
"nsw")
pane_wait NS
;;
"nstools")
pane_run NS 'which '"${__arg}"' >/dev/null || echo skip'
pane_wait NS
[ "$(pane_parse NS)" = "skip" ] && { __skip=1; break; }
;;
"gout")
__varname="${__arg%% *}"
pane_run GUEST "${__arg#* }"
pane_wait GUEST
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse GUEST)")"
;;
"g1out")
__varname="${__arg%% *}"
pane_run GUEST_1 "${__arg#* }"
pane_wait GUEST_1
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse GUEST_1)")"
;;
"g2out")
__varname="${__arg%% *}"
pane_run GUEST_2 "${__arg#* }"
pane_wait GUEST_2
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse GUEST_2)")"
;;
"hout")
__varname="${__arg%% *}"
pane_run HOST "${__arg#* }"
pane_wait HOST
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse HOST)")"
;;
"nsout")
__varname="${__arg%% *}"
pane_run NS "${__arg#* }"
pane_wait NS
__subs="$(list_add_pair "${__subs}" "__${__varname}__" "$(pane_parse NS)")"
;;
"check")
info_check "${__arg}"
eval "${__arg} || __nok=1"
if [ ${__nok} -eq 1 ]; then
info_check_failed
else
info_check_passed
fi
;;
"sleep")
sleep "${__arg}"
;;
"info")
info "${__arg}"
;;
"report")
perf_report ${__arg}
;;
"th")
table_header ${__arg}
;;
"tr")
table_row "${__arg}"
;;
"tl")
table_line "${__arg}"
;;
"te")
table_end
;;
"bw")
table_value_throughput ${__arg} || __perf_nok=1
;;
"lat")
table_value_latency ${__arg} || __perf_nok=1
;;
"iperf3c")
set -x
test_iperf3 client ${__arg}
set +x
;;
"iperf3s")
set -x
__subs="$(list_add_pair "${__subs}" "__${__arg%% *}__" "$(test_iperf3 server ${__arg#* })" )"
set +x
;;
"set")
__subs="$(list_add_pair "${__subs}" "__${__arg%% *}__" "${__arg#* }")"
;;
# Demo commands
"say")
text_write "${__arg}"
;;
"em")
em_write "${__arg}"
;;
"nl")
info_nolog ""
;;
"hl")
pane_highlight "${__arg}"
;;
"bsp")
text_backspace "${__arg}"
;;
"killp")
pane_kill "${__arg}"
;;
esac
done < "${__test_file}"
for __d in ${__dirclean}; do
rm -rf ${__d}
done
[ ${DEMO} -eq 1 ] && return
[ ${__skip} -eq 1 ] && status_test_skip && return
[ ${__perf_nok} -eq 0 ] || __nok=1
[ ${__nok} -eq 0 ] && status_test_ok || status_test_fail
}
# test() - Build list of tests to run, in order, then issue test_one()
# $1: Name of directory containing set of test files, relative to test/
test() {
__list=
__rem=1
cd test
while [ ${__rem} -eq 1 ]; do
__rem=0
for __f in "${1}"/*; do
__type="$(file -b --mime-type ${__f})"
if [ "${__type}" = "text/x-shellscript" ]; then
__list="$(list_add "${__list}" "${__f}")"
continue
fi
if [ -n "$(file_def "${__f}" onlyfor)" ] && \
! list_has "$(file_def "${__f}" onlyfor)" "${MODE}"; then
continue
fi
if list_has_all "${__list}" "$(file_def "${__f}" req)"; then
__list="$(list_add "${__list}" "${__f}")"
else
__rem=1
fi
done
done
cd ..
for __f in ${__list}; do
test_one "${__f}"
done
}

142
test/lib/util Executable file
View file

@ -0,0 +1,142 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/util - Convenience functions
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
# list_has() - Check whether a tab-separated list contains a given token
# $1: List
# $2: Token
# Return: 0 if token was found or is empty, 1 otherwise
list_has() {
[ -z "${2}" ] && return 0
__ifs="${IFS}"
IFS=' '
for __t in ${1}; do
[ "${__t}" = "${2}" ] && IFS="${__ifs}" && return 0
done
IFS="${__ifs}"
return 1
}
# list_add() - Add token to tab-separated list, unless it's already present
# $1: List
# $2: Token
list_add() {
list_has "${1}" "${2}" && return
[ -n "${1}" ] && printf '%s\t%s\n' "${1}" "${2}" || printf '%s\n' "${2}"
}
# list_remove_pair() - Drop pair with given key if present
# $1: List
# $2: Key
list_remove_pair()
{
__ifs="${IFS}"
IFS=' '
__skip_next=0
for __t in ${1}; do
[ ${__skip_next} -eq 1 ] && __skip_next=0 && continue
[ "${__t}" = "${2}" ] && __skip_next=1 && continue
printf '%s\t' "${__t}"
done
printf "\n"
IFS="${__ifs}"
}
# list_add_pair() - Add token pair to list, replace if the first one is present
# $1: List
# $2: First token
# $3: Second token
list_add_pair() {
[ -z "${3}" ] && return
if [ -n "${1}" ]; then
__new_list="$(list_remove_pair "${1}" "${2}")"
printf '%s\t%s\t%s' "${__new_list}" "${2}" "${3}"
else
printf '%s\t%s' "${2}" "${3}"
fi
printf "\n"
}
# list_has_all() - Check whether a list contains all given IFS-separated tokens
# $1: List
# $2: List of tokens
# Return: 0 if list of tokens was found or is empty, 1 otherwise
list_has_all() {
[ -z "${2}" ] && return 0
for __i in ${2}; do
list_has "${1}" "${__i}" || return 1
done
return 0
}
# file_def() - List of tokens tab-separated line from file, starting with key
# $1: Filename
# $2: Token
file_def() {
sed -n 's/^'"${2}"'\t\(.*\)/\1/p' "${1}" | tr ' ' '\t'
}
# subs_apply() - Apply substitutions using a list of token pairs
# $1: List of substitutions
# $2: String where substitutions have to be applied
subs_apply() {
echo "in subs_apply" >> /tmp/subs_apply
__ifs="${IFS}"
IFS=' '
__newarg="${2}"
__s=
for __t in ${1}; do
[ -z "${__s}" ] && __s="${__t}" && continue
echo "t: --${__t}--, --${__s}--" >> /tmp/subs_apply
__et="$(printf '%s\n' "$__t" | sed -e 's/[\/&]/\\&/g')"
__es="$(printf '%s\n' "$__s" | sed -e 's/[]\/$*.^[]/\\&/g')"
__newarg="$(printf '%s' "${__newarg}" | sed "s/${__es}/${__et}/g")"
__s=
done
printf '%s' "${__newarg}"
IFS="${__ifs}"
}
# set_mode() - Set 'passt' or 'pasta' mode for terminal control, renaming panes
# $1: Mode to be set
set_mode() {
MODE="${1}"
if [ "${1}" = "pasta" ]; then
tmux select-pane -t ${PANE_GUEST} -T "namespace"
tmux select-pane -t ${PANE_PASST} -T "pasta"
else
tmux select-pane -t ${PANE_GUEST} -T "guest"
tmux select-pane -t ${PANE_PASST} -T "passt"
fi
}
# get_info_cols() - Get number of columns for info pane
get_info_cols() {
__log_pane_cols=
__j=0
for __i in $(tmux list-panes -t passt_test:1.0 -F "#{pane_width}"); do
[ ${__j} -eq ${PANE_INFO} ] && STATUS_COLS=${__i} && break
__j=$((__j + 1))
done
}

119
test/lib/video Executable file
View file

@ -0,0 +1,119 @@
#!/bin/sh
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# PASST - Plug A Simple Socket Transport
# for qemu/UNIX domain socket mode
#
# PASTA - Pack A Subtle Tap Abstraction
# for network namespace/tap device mode
#
# test/lib/video - Video grabbing, JavaScript fragments with links
#
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio@redhat.com>
FFMPEG_PID_FILE="$(mktemp)"
VIDEO_START_SECONDS=
VIDEO_NAME=
VIDEO_LINKS_TEMPLATE="document.write('"'
Skip to:
'
VIDEO_LINKS_TEMPLATE_JS="
');
var video___VIDEO_NAME__links = [
"
VIDEO_LINKS_TEMPLATE_POST='];
for (var i = 0; i < video___VIDEO_NAME__links.length; i++) {
var obj = document.getElementById(video___VIDEO_NAME__links[i][0]);
obj.addEventListener("click", function(event) {
var __VIDEO_NAME___video = document.getElementById("__VIDEO_NAME___video");
var top = __VIDEO_NAME___video.offsetTop - 5;
event.preventDefault();
__VIDEO_NAME___video.play();
__VIDEO_NAME___video.pause();
for (var i = 0; i < video___VIDEO_NAME__links.length; i++) {
if (this.id == video___VIDEO_NAME__links[i][0]) {
__VIDEO_NAME___video.currentTime = video___VIDEO_NAME__links[i][1];
}
}
window.scrollTo({ top: top, behavior: "smooth" })
__VIDEO_NAME___video.play();
}, false);
}
'
VIDEO_LINKS_BUF=
VIDEO_LINKS_COUNT=0
# video_append_links() - Append generic string to JavaScript links file
video_append_links()
{
printf "${@}" >> "${BASEPATH}/${VIDEO_NAME}.js"
}
# video_append_links() - Append generic string to buffer for links
video_append_links_js()
{
VIDEO_LINKS_BUF="${VIDEO_LINKS_BUF}${@}"
}
# video_grab() - Fetch window geometry, start grabbing video
video_grab() {
VIDEO_NAME="${1}"
rm -f "${BASEPATH}/${VIDEO_NAME}.mp4" "${BASEPATH}/${VIDEO_NAME}.webm" "${BASEPATH}/${VIDEO_NAME}.js"
echo "${VIDEO_LINKS_TEMPLATE}" > "${BASEPATH}/${VIDEO_NAME}.js"
__x=$(xwininfo -id $(xdotool getactivewindow) | sed -n 's/[ ]*Absolute upper-left X:[ ]*\([0-9]*\)$/\1/p')
__y=$(xwininfo -id $(xdotool getactivewindow) | sed -n 's/[ ]*Absolute upper-left Y:[ ]*\([0-9]*\)$/\1/p')
__width=$(xwininfo -id $(xdotool getactivewindow) | sed -n 's/[ ]*Width:[ ]*\([0-9]*\)$/\1/p')
__height=$(xwininfo -id $(xdotool getactivewindow) | sed -n 's/[ ]*Height:[ ]*\([0-9]*\)$/\1/p')
[ $((__width % 2)) ] && __width=$((__width - 1))
[ $((__height % 2)) ] && __height=$((__height - 1))
sleep 3
VIDEO_START_SECONDS=$(sed -n 's/\([0-9]*\).[0-9]* [0-9]*.[0-9]*/\1/p' /proc/uptime)
ffmpeg -f x11grab -framerate 15 -video_size "${__width}x${__height}" -i "+${__x},${__y}" -vcodec libx264 -preset ultrafast -qp 0 -pix_fmt yuv444p -draw_mouse 0 "${BASEPATH}/${VIDEO_NAME}.mp4" & echo $! > "${FFMPEG_PID_FILE}"
}
# video_time_now() - Print current video timestamp, in seconds
video_time_now() {
__now=$(sed -n 's/\([0-9]*\).[0-9]* [0-9]*.[0-9]*/\1/p' /proc/uptime)
echo $((__now - VIDEO_START_SECONDS))
}
# video_stop() - Stop grabbing, finalise JavaScript templates, convert to webm
video_stop() {
sed -i 's/^.*$/&\\/g' "${BASEPATH}/${VIDEO_NAME}.js"
echo "${VIDEO_LINKS_TEMPLATE_JS}" | sed "s/__VIDEO_NAME__/${VIDEO_NAME}/g" >> "${BASEPATH}/${VIDEO_NAME}.js"
echo "${VIDEO_LINKS_BUF}" >> "${BASEPATH}/${VIDEO_NAME}.js"
echo "${VIDEO_LINKS_TEMPLATE_POST}" | sed "s/__VIDEO_NAME__/${VIDEO_NAME}/g" >> "${BASEPATH}/${VIDEO_NAME}.js"
kill -INT $(cat "${FFMPEG_PID_FILE}")
while ps -p $(cat "${FFMPEG_PID_FILE}") >/dev/null; do sleep 1; done
rm "${FFMPEG_PID_FILE}"
[ ${1} -ne 0 ] && return
ffmpeg -an -fflags +genpts -i "${BASEPATH}/${VIDEO_NAME}.mp4" -c:v libvpx-vp9 -row-mt 1 -minrate 10k -maxrate 200k -b:v 200k "${BASEPATH}/${VIDEO_NAME}.webm"
}
# video_link() - Append single link to given video chapter
video_link() {
[ ${VIDEO_LINKS_COUNT} -eq 0 ] && __sep="" || __sep=" |"
__id="video_link_${VIDEO_LINKS_COUNT}"
video_append_links "${__sep} <a id=\"${__id}\">${1}</a>"
video_append_links_js "[ '${__id}', $(($(video_time_now) - 1)) ],"
VIDEO_LINKS_COUNT=$((VIDEO_LINKS_COUNT + 1))
}