From b235762fdb72f35c0f809fece17f84b07e765394 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Sun, 12 Jul 2026 13:41:46 +0800 Subject: [PATCH] fix(ci): unblock e2e-s3tests startup and create disk dirs for distributed volumes (#4768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheduled e2e-s3tests sweep failed at "Wait for RustFS ready" in both topologies because the server never started (issue #4762). Two independent startup faults, both surfaced now that the local physical-disk-independence guard is enforced: - single: RUSTFS_VOLUMES=/data/rustfs{0...3} all live on one physical device on the runner, so the guard aborts startup. Set the CI-sanctioned RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true (what the guard's own error message and the e2e tests already use). - multi: the entrypoint's process_data_volumes skipped every non-absolute entry, so the distributed URL form "http://rustfs{1...4}:9000/data/rustfs{0...3}" never created /data/rustfs0..3. LocalDisk init then aborts with VolumeNotFound because resolve_local_disk_root no longer auto-creates the disk root. This also broke the shipped .docker/compose/docker-compose.cluster.yaml for real distributed docker deployments. entrypoint.sh now: 1. Expands multiple {N...M} ranges per token (the URL form carries two). The previous single-pass expander collapsed it to "http://rustfs1" and dropped the disk path; it now re-scans until no ranges remain, operating on only the first brace to keep multi-range tokens intact. 2. Creates the local filesystem path for URL-form endpoints (stripping scheme://host:port) without appending them as CLI args — rustfs reads the distributed form from RUSTFS_VOLUMES directly. Absolute-path and default (/data) inputs expand byte-identically to before. The multi compose also gets the CI disk-check bypass, since the four disks share one device inside each node's container. --- .github/workflows/e2e-s3tests.yml | 9 +++ entrypoint.sh | 91 ++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/.github/workflows/e2e-s3tests.yml b/.github/workflows/e2e-s3tests.yml index c4f02c4d1..23598e803 100644 --- a/.github/workflows/e2e-s3tests.yml +++ b/.github/workflows/e2e-s3tests.yml @@ -174,6 +174,10 @@ jobs: run: | docker network inspect rustfs-net >/dev/null 2>&1 || docker network create rustfs-net docker rm -f rustfs-single >/dev/null 2>&1 || true + # The four disks share one physical device on the runner (a single + # loopback filesystem), so the local physical-disk-independence guard + # would refuse to start. Bypass it — this is the CI use case the guard + # explicitly sanctions via RUSTFS_UNSAFE_BYPASS_DISK_CHECK. docker run -d --name rustfs-single \ --network rustfs-net \ -p "${S3_PORT}:9000" \ @@ -181,6 +185,7 @@ jobs: -e RUSTFS_ACCESS_KEY="${S3_ACCESS_KEY}" \ -e RUSTFS_SECRET_KEY="${S3_SECRET_KEY}" \ -e RUSTFS_VOLUMES="/data/rustfs{0...3}" \ + -e RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true \ -v /tmp/rustfs-single:/data \ rustfs-ci @@ -200,6 +205,10 @@ jobs: RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY} RUSTFS_SECRET_KEY: ${S3_SECRET_KEY} RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}" + # Each node's four disks share one physical device inside its + # container, so bypass the local physical-disk-independence guard + # (the CI use case it explicitly sanctions). + RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "true" services: rustfs1: diff --git a/entrypoint.sh b/entrypoint.sh index a302dcffa..53f776395 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -191,43 +191,72 @@ process_data_volumes() { # Convert comma/tab to space VOLUME_LIST_RAW=$(echo "$VOLUME_RAW" | tr ',\t' ' ') - VOLUME_LIST="" - for vol in $VOLUME_LIST_RAW; do - # Helper to manually expand {N..M} since sh doesn't support it on variables - if echo "$vol" | grep -E -q "\{[0-9]+\.\.\.?[0-9]+\}"; then - PREFIX=${vol%%\{*} - SUFFIX=${vol##*\}} - RANGE=${vol#*\{} - RANGE=${RANGE%\}} - RANGE=$(echo "$RANGE" | sed 's/\.\.\./../') - START=${RANGE%%..*} - END=${RANGE##*..} - - # Check if START and END are numbers - if [ "$START" -eq "$START" ] 2>/dev/null && [ "$END" -eq "$END" 2>/dev/null ]; then - i=$START - while [ "$i" -le "$END" ]; do - VOLUME_LIST="$VOLUME_LIST ${PREFIX}${i}${SUFFIX}" - i=$((i+1)) - done - else - # Fallback if not numbers - VOLUME_LIST="$VOLUME_LIST $vol" - fi - else - VOLUME_LIST="$VOLUME_LIST $vol" - fi + # Manually expand {N...M} ranges since sh doesn't support brace expansion on + # variables. A single token can carry MORE than one range — the distributed + # form "http://rustfs{1...4}:9000/data/rustfs{0...3}" has two — so expand the + # FIRST range in each token and re-scan until no ranges remain. Operating on + # only the first brace (via #*{ / %%}* rather than ##*} / %}) is what keeps + # multi-range tokens intact; the previous single-pass expander collapsed them + # to "http://rustfs1" and silently dropped the disk path. + VOLUME_LIST="$VOLUME_LIST_RAW" + while echo "$VOLUME_LIST" | grep -E -q "\{[0-9]+\.\.\.?[0-9]+\}"; do + NEXT_LIST="" + for vol in $VOLUME_LIST; do + if echo "$vol" | grep -E -q "\{[0-9]+\.\.\.?[0-9]+\}"; then + PREFIX=${vol%%\{*} + REST=${vol#*\}} + RANGE=${vol#*\{} + RANGE=${RANGE%%\}*} + RANGE=$(echo "$RANGE" | sed 's/\.\.\./../') + START=${RANGE%%..*} + END=${RANGE##*..} + + # Check if START and END are numbers + if [ "$START" -eq "$START" ] 2>/dev/null && [ "$END" -eq "$END" 2>/dev/null ]; then + i=$START + while [ "$i" -le "$END" ]; do + NEXT_LIST="$NEXT_LIST ${PREFIX}${i}${REST}" + i=$((i+1)) + done + else + # Fallback if not numbers + NEXT_LIST="$NEXT_LIST $vol" + fi + else + NEXT_LIST="$NEXT_LIST $vol" + fi + done + VOLUME_LIST="$NEXT_LIST" done + # CREATE_DIRS holds every local filesystem path that must exist before + # startup. DATA_VOLUMES holds only the paths that are also appended as CLI + # args (bare absolute paths). Distributed URL-form endpoints are read from + # RUSTFS_VOLUMES by rustfs directly, so they must NOT be appended as args, + # but their local path component still has to exist on disk — otherwise + # LocalDisk init aborts with VolumeNotFound (rustfs no longer auto-creates + # the disk root). + CREATE_DIRS="" for vol in $VOLUME_LIST; do case "$vol" in - /*) DATA_VOLUMES="$DATA_VOLUMES $vol" ;; - *) : ;; # skip non-absolute paths (including http(s):// URLs) + /*) + DATA_VOLUMES="$DATA_VOLUMES $vol" + CREATE_DIRS="$CREATE_DIRS $vol" + ;; + *://*) + # e.g. http://node0:9000/data/rustfs0 -> /data/rustfs0 + vol_rest=${vol#*://} + case "$vol_rest" in + */*) CREATE_DIRS="$CREATE_DIRS /${vol_rest#*/}" ;; + *) : ;; # URL without a path component; nothing local to create + esac + ;; + *) : ;; # skip anything else we don't recognize esac done - - echo "Initializing data directories:$DATA_VOLUMES" - for vol in $DATA_VOLUMES; do + + echo "Initializing data directories:$CREATE_DIRS" + for vol in $CREATE_DIRS; do if [ ! -d "$vol" ]; then echo " mkdir -p $vol" mkdir -p "$vol"