Files
rustfs/.github/workflows/rustfs-pool-expand-test.yml
T
hector 2e6c820f53 test(heal): relative disk target and fail fast on terminal-but-short (#6748)
* test(heal): relative disk target and fail fast on terminal-but-short

The absolute 40 GiB heal target was calibrated to the background scanner
(auto-heal), which is now disabled for determinism; with only the explicit
heal the recovered node lands at ~36 GiB for 40 GiB survivors. Make the
success criterion relative: the outage node must reach at least 90% of the
least-used surviving node (absolute HEAL_TARGET_GB floor optional, default
0 = relative only).

Also fail fast when the heal task reaches a terminal success but the disk
target is not met (previously the monitor kept polling until timeout), and
drop the misleading 'progress absent' warning on the final (cleaned) task
response — mid-run progress is reported correctly.

Validated live: heal summary=finished, 0 failed, vm000/vm001=40GB,
vm002=40GB (target 36GB), test PASSED.

* test(heal): gate success on server verdict + data read-back, drop disk GB gate

The per-node disk-usage target (40 GiB / 90% of survivors) is not a
code-level invariant: EC distributes different shards per node, so the
final GB per node depends on the layout, not on heal correctness. Gate the
test on what the server actually verifies:

- Heal task terminal success (finished/completed) with objectsFailed == 0
  (the server's per-object scan/repair verdict).
- S3 read-back verification: list the test bucket and GET a sample of
  objects, requiring HTTP 200 for every read (end-to-end proof the data is
  still reconstructable after repair). The GET uses a discard mode so
  binary bodies are not captured (no null-byte warnings / SIGPIPE).

Per-node disk usage stays in the output as observability (with a warning if
the outage node gained no usage), not as the pass/fail gate. Removes the
heal_target_gb input and the relative-target logic.

Validated live: heal summary=finished, 0 failed, 20/20 objects read back,
vm002_used=40GB, PASS.
2026-08-27 22:25:17 +08:00

239 lines
8.8 KiB
YAML

name: RustFS Pool Expansion / Decommission Test
on:
workflow_dispatch:
inputs:
rustfs_version:
description: 'RustFS release tag to test (e.g. 1.0.0-rc.3)'
required: false
default: '1.0.0-rc.3'
package_url:
description: 'Direct .deb URL (nightly/R2/dev). Overrides rustfs_version.'
required: false
type: string
pools:
description: 'Number of pools to expand to (2 = first rebalance only)'
type: choice
options:
- '2'
- '3'
default: '3'
storage_threshold:
description: 'Stop writing when storage usage reaches N%'
required: false
default: '50'
warp_duration:
description: 'warp write duration (e.g. 5m, 10m)'
required: false
default: '10m'
run_decommission:
description: 'Run the pool decommission step (3-pool topology only)'
type: boolean
default: true
stop_node_gb:
description: 'Heal: stop the outage node when surviving nodes reach N GiB'
required: false
default: '15'
warp_stop_gb:
description: 'Heal: stop warp when surviving nodes reach N GiB'
required: false
default: '40'
cleanup_before:
description: 'Reset the nodes before the test (DESTROYS existing data/config)'
type: boolean
default: true
cleanup_after:
description: 'Reset the nodes after the test (DESTROYS test data/config)'
type: boolean
default: true
workflow_run:
# Run after the nightly build completes: pool expansion first, then heal.
workflows: ["Nightly GNU Build"]
types: [completed]
permissions:
contents: read
# Only one pool-expansion test at a time: the workflow mutates a shared
# test environment, so concurrent runs must not clobber each other.
concurrency:
group: rustfs-pool-expansion-test
cancel-in-progress: false
defaults:
run:
shell: bash
env:
RUSTFS_ACCESS_KEY: ${{ secrets.RUSTFS_ACCESS_KEY }}
RUSTFS_SECRET_KEY: ${{ secrets.RUSTFS_SECRET_KEY }}
RUSTFS_API_ENDPOINT: ${{ secrets.RUSTFS_API_ENDPOINT || vars.RUSTFS_API_ENDPOINT || vars.RUSTFS_RC_ENDPOINT }}
RUSTFS_NODES: ${{ secrets.RUSTFS_NODES || vars.RUSTFS_NODES }}
RUSTFS_SSH_USER: ${{ secrets.RUSTFS_SSH_USER || vars.RUSTFS_SSH_USER }}
# Package used by the nightly run (workflow_dispatch inputs are empty for
# workflow_run events), i.e. the latest nightly deb published by nightly-gnu.yml.
RUSTFS_NIGHTLY_PACKAGE_URL: ${{ vars.RUSTFS_NIGHTLY_PACKAGE_URL || 'https://dl.rustfs.com/artifacts/rustfs/packages/nightly/rustfs-nightly-latest.deb' }}
jobs:
pool-expansion-test:
runs-on: smoke-testing
timeout-minutes: 360
# Run on manual dispatch, or when the nightly build completed successfully
# (its deb is what the tests install). Skipped when nightly failed.
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
- name: Show environment
run: |
uname -a
jq --version
openssl version
warp --version || true
df -h /data | tail -1
- name: Reset test environment (before)
if: ${{ inputs.cleanup_before != 'false' }}
run: |
chmod +x scripts/test/rustfs_pool_expand.sh
./scripts/test/rustfs_pool_expand.sh --reset -y
- name: Install RustFS package & start first pool
run: |
ARGS=(--steps "1,2,3" -y --endpoint "${{ env.RUSTFS_API_ENDPOINT }}")
if [ -n "${{ inputs.package_url }}" ]; then
ARGS+=(--package-url "${{ inputs.package_url }}")
elif [ -n "${{ inputs.rustfs_version }}" ]; then
ARGS+=(--version "${{ inputs.rustfs_version }}")
else
ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
fi
./scripts/test/rustfs_pool_expand.sh "${ARGS[@]}"
- name: Preflight checks
run: |
ARGS=(--preflight --endpoint "${{ env.RUSTFS_API_ENDPOINT }}")
if [ -n "${{ inputs.package_url }}" ]; then
ARGS+=(--package-url "${{ inputs.package_url }}")
elif [ -n "${{ inputs.rustfs_version }}" ]; then
ARGS+=(--version "${{ inputs.rustfs_version }}")
else
ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
fi
./scripts/test/rustfs_pool_expand.sh "${ARGS[@]}"
- name: Run pool expansion & decommission test
id: pool_test
run: |
set -o pipefail
STEPS="4,5,6"
if [ "${{ inputs.pools || '3' }}" = "3" ]; then
STEPS="$STEPS,7,8"
if [ "${{ inputs.run_decommission != 'false' }}" = "true" ]; then
STEPS="$STEPS,9"
fi
fi
./scripts/test/rustfs_pool_expand.sh \
--steps "$STEPS" --with-warp -y \
--endpoint "${{ env.RUSTFS_API_ENDPOINT }}" \
--storage-threshold "${{ inputs.storage_threshold || '50' }}" \
--warp-duration "${{ inputs.warp_duration || '10m' }}" \
--log-file /tmp/rustfs-pool-test.log
- name: Upload test logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: rustfs-pool-test-${{ github.run_id }}
path: |
/tmp/rustfs-pool-test*.log
/tmp/rustfs-warp.*.log
if-no-files-found: warn
- name: Reset test environment (after)
if: ${{ always() && inputs.cleanup_after != 'false' }}
run: |
./scripts/test/rustfs_pool_expand.sh --reset -y
- name: Notify on failure
if: failure()
run: |
echo "RustFS pool expansion test failed"
echo "Package source: ${{ inputs.package_url || inputs.rustfs_version || 'nightly (R2 latest)' }}"
echo "See the uploaded log artifact for details."
# Heal regression runs after the pool test regardless of its outcome: a pool
# failure must be reported (it makes the run red) but must not block heal.
heal-test:
name: Heal test (after pool test)
runs-on: smoke-testing
timeout-minutes: 480
needs: pool-expansion-test
if: ${{ always() && (github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success') }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
- name: Reset test environment (before)
if: ${{ inputs.cleanup_before != 'false' }}
run: |
chmod +x scripts/test/rustfs_heal_test.sh
./scripts/test/rustfs_heal_test.sh --reset -y
- name: Install RustFS package & start cluster
run: |
ARGS=(--steps "1,2" -y --endpoint "${{ env.RUSTFS_API_ENDPOINT }}")
if [ -n "${{ inputs.package_url }}" ]; then
ARGS+=(--package-url "${{ inputs.package_url }}")
else
ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
fi
./scripts/test/rustfs_heal_test.sh "${ARGS[@]}"
- name: Preflight checks
run: |
ARGS=(--preflight --endpoint "${{ env.RUSTFS_API_ENDPOINT }}")
if [ -n "${{ inputs.package_url }}" ]; then
ARGS+=(--package-url "${{ inputs.package_url }}")
else
ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
fi
./scripts/test/rustfs_heal_test.sh "${ARGS[@]}"
- name: Run heal test (write -> outage -> heal -> verify)
run: |
./scripts/test/rustfs_heal_test.sh \
--steps 3,4,5,6,7 -y \
--endpoint "${{ env.RUSTFS_API_ENDPOINT }}" \
--stop-node-gb "${{ inputs.stop_node_gb || '15' }}" \
--warp-stop-gb "${{ inputs.warp_stop_gb || '40' }}" \
--log-file /tmp/rustfs-heal-test.log
- name: Upload test logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: rustfs-heal-test-${{ github.run_id }}
path: |
/tmp/rustfs-heal-test.log
/tmp/rustfs-warp.*.log
if-no-files-found: warn
- name: Reset test environment (after)
if: ${{ always() && inputs.cleanup_after != 'false' }}
run: |
./scripts/test/rustfs_heal_test.sh --reset -y
- name: Notify on failure
if: failure()
run: |
echo "RustFS heal test failed"
echo "See the uploaded log artifact for details."