CI: system tests: make random_free_port() parallel-safe

...by using a crude port lock-and-reserve mechanism. This is
a small cherrypick from code that has been working in #23275
over dozens of CI runs. Am separating out into a small PR
because it's stable, harmless to serial runs, and will
simplify the eventual review of #23275.

Closes: #23488

Signed-off-by: Ed Santiago <santiago@redhat.com>
This commit is contained in:
Ed Santiago
2024-08-12 15:08:01 -06:00
parent 734c4b98d4
commit 420bd16a21
3 changed files with 59 additions and 3 deletions

View File

@ -41,6 +41,9 @@ if [ $(id -u) -eq 0 ]; then
_LOG_PROMPT='#'
fi
# Used in helpers.network, needed here in teardown
PORT_LOCK_DIR=$BATS_SUITE_TMPDIR/reserved-ports
###############################################################################
# BEGIN tools for fetching & caching test images
#
@ -205,6 +208,14 @@ function defer-assertion-failures() {
function basic_teardown() {
echo "# [teardown]" >&2
# Free any ports reserved by our test
if [[ -d $PORT_LOCK_DIR ]]; then
mylocks=$(grep -wlr $BATS_SUITE_TEST_NUMBER $PORT_LOCK_DIR || true)
if [[ -n "$mylocks" ]]; then
rm -f $mylocks
fi
fi
immediate-assertion-failures
# Unlike normal tests teardown will not exit on first command failure
# but rather only uses the return code of the teardown function.

View File

@ -273,6 +273,36 @@ function ether_get_name() {
### Ports and Ranges ###########################################################
# reserve_port() - create a lock file reserving a port, or return false
function reserve_port() {
local port=$1
mkdir -p $PORT_LOCK_DIR
local lockfile=$PORT_LOCK_DIR/$port
local locktmp=$PORT_LOCK_DIR/.$port.$$
echo $BATS_SUITE_TEST_NUMBER >$locktmp
if ln $locktmp $lockfile; then
rm -f $locktmp
return
fi
# Port already reserved
rm -f $locktmp
false
}
# unreserve_port() - free a temporarily-reserved port
function unreserve_port() {
local port=$1
local lockfile=$PORT_LOCK_DIR/$port
-e $lockfile || die "Cannot unreserve non-reserved port $port"
assert "$(< $lockfile)" = "$BATS_SUITE_TEST_NUMBER" \
"Port $port is not reserved by this test"
rm -f $lockfile
}
# random_free_port() - Get unbound port with pseudorandom number
# $1: Optional, dash-separated interval, [5000, 5999] by default
# $2: Optional binding address, any IPv4 address by default
@ -284,9 +314,14 @@ function random_free_port() {
local port
for port in $(shuf -i ${range}); do
if port_is_free $port $address $protocol; then
echo $port
return
# First make sure no other tests are using it
if reserve_port $port; then
if port_is_free $port $address $protocol; then
echo $port
return
fi
unreserve_port $port
fi
done
@ -308,8 +343,13 @@ function random_free_port_range() {
local lastport=
for i in $(seq 1 $((size - 1))); do
lastport=$((firstport + i))
if ! reserve_port $lastport; then
lastport=
break
fi
if ! port_is_free $lastport $address $protocol; then
echo "# port $lastport is in use; trying another." >&3
unreserve_port $lastport
lastport=
break
fi
@ -319,6 +359,8 @@ function random_free_port_range() {
return
fi
unreserve_port $firstport
maxtries=$((maxtries - 1))
done

View File

@ -22,6 +22,9 @@ rc=0
PODMAN_TMPDIR=$(mktemp -d --tmpdir=${TMPDIR:-/tmp} podman_helper_tests.XXXXXX)
trap 'rm -rf $PODMAN_TMPDIR' 0
# Used by random_free_port.
PORT_LOCK_DIR=$PODMAN_TMPDIR/reserved-ports
###############################################################################
# BEGIN test the parse_table helper