From b6c3108e53124dda06e2a300fdf3a97cf29e2259 Mon Sep 17 00:00:00 2001
From: hector <42570491+majinghe@users.noreply.github.com>
Date: Mon, 31 Aug 2026 08:43:28 +0800
Subject: [PATCH] fix(ci): keep functional workflow chain running after
failures (#6926)
* fix(ci): isolate s3 compat temp file paths
* fix(ci): use rooted auto-testing s3 temp fix
* fix(ci): follow auto-testing main after temp-path merge
* fix(ci): stabilize tier mqtt bootstrap on shared runner
* feat(ci): publish functional reports and keep workflows non-blocking
* fix(ci): keep functional chain running after failures
* fix(ci): standardize functional workflow cleanup steps
---
.github/workflows/rustfs-heal-test.yml | 37 ++-
.github/workflows/rustfs-kms-test.yml | 182 +++++++++++++-
.github/workflows/rustfs-pool-expand-test.yml | 235 +++++++++++-------
.github/workflows/rustfs-s3-compat-test.yml | 182 ++++++++++++++
.github/workflows/rustfs-tier-test.yml | 182 +++++++++++++-
5 files changed, 722 insertions(+), 96 deletions(-)
diff --git a/.github/workflows/rustfs-heal-test.yml b/.github/workflows/rustfs-heal-test.yml
index d890335e5..3e366481b 100644
--- a/.github/workflows/rustfs-heal-test.yml
+++ b/.github/workflows/rustfs-heal-test.yml
@@ -70,11 +70,24 @@ jobs:
warp --version || true
df -h /data | tail -1
- - name: Reset test environment (before)
+ - name: Cleanup environment (before)
if: ${{ inputs.cleanup_before != 'false' }}
run: |
- chmod +x auto-testing/rustfs_heal_test.sh
- ./auto-testing/rustfs_heal_test.sh --reset -y
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
- name: Install RustFS package & start cluster
run: |
@@ -115,10 +128,24 @@ jobs:
/tmp/rustfs-warp.*.log
if-no-files-found: warn
- - name: Reset test environment (after)
+ - name: Cleanup environment (after)
if: ${{ always() && inputs.cleanup_after != 'false' }}
run: |
- ./auto-testing/rustfs_heal_test.sh --reset -y
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
- name: Notify on failure
if: failure()
diff --git a/.github/workflows/rustfs-kms-test.yml b/.github/workflows/rustfs-kms-test.yml
index 07f80d520..0cc3dd16a 100644
--- a/.github/workflows/rustfs-kms-test.yml
+++ b/.github/workflows/rustfs-kms-test.yml
@@ -12,7 +12,7 @@ on:
required: false
type: string
workflow_run:
- # Strict shared-environment order: run after S3 compatibility test succeeds.
+ # Strict shared-environment order: run after S3 compatibility test completes.
workflows: ["RustFS S3 Compatibility Test"]
types: [completed]
@@ -38,8 +38,9 @@ env:
jobs:
kms-test:
runs-on: smoke-testing
+ continue-on-error: true
timeout-minutes: 420
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run' }}
steps:
- name: Checkout auto-testing scripts
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@@ -86,6 +87,7 @@ jobs:
- name: Run KMS suite
id: test
+ continue-on-error: true
env:
LOG_FILE: /tmp/rustfs-kms.log
run: |
@@ -119,12 +121,65 @@ jobs:
else
PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}"
fi
+ CASE_TABLE="/tmp/rustfs-kms-cases.md"
+ python3 - "${LOG_FILE}" "${CASE_TABLE}" <<'PY'
+ import re
+ import sys
+
+ log_file, out_file = sys.argv[1], sys.argv[2]
+ ansi = re.compile(r'\x1b\[[0-9;]*m')
+ start_re = re.compile(r'^---\s+([A-Z]+-[0-9]+)\s+(.+?)\s+---$')
+ done_re = re.compile(r'^\[(PASS|FAIL|UNSUPPORTED)\]\s+([A-Z]+-[0-9]+)\b')
+
+ rows = []
+ index = {}
+ try:
+ with open(log_file, 'r', encoding='utf-8', errors='replace') as fh:
+ for raw in fh:
+ line = ansi.sub('', raw).strip()
+ m = start_re.match(line)
+ if m:
+ case_id, name = m.group(1), m.group(2)
+ if case_id not in index:
+ index[case_id] = len(rows)
+ rows.append([case_id, name, 'RUNNING'])
+ continue
+ m = done_re.match(line)
+ if m:
+ status, case_id = m.group(1), m.group(2)
+ if case_id in index:
+ rows[index[case_id]][2] = status
+ else:
+ rows.append([case_id, case_id, status])
+ index[case_id] = len(rows) - 1
+ except FileNotFoundError:
+ rows = []
+
+ counts = {'PASS': 0, 'FAIL': 0, 'UNSUPPORTED': 0, 'RUNNING': 0}
+ for _, _, status in rows:
+ counts[status] = counts.get(status, 0) + 1
+
+ with open(out_file, 'w', encoding='utf-8') as out:
+ out.write('## Case Summary\n\n')
+ out.write(f"- Total: {len(rows)}\\n")
+ out.write(f"- PASS: {counts.get('PASS', 0)}\\n")
+ out.write(f"- FAIL: {counts.get('FAIL', 0)}\\n")
+ out.write(f"- UNSUPPORTED: {counts.get('UNSUPPORTED', 0)}\\n")
+ out.write('\\n')
+ out.write('| Case | Name | Status |\\n')
+ out.write('| --- | --- | --- |\\n')
+ for case_id, name, status in rows:
+ out.write(f'| {case_id} | {name} | {status} |\\n')
+ PY
{
echo "# RustFS KMS test report"
echo ""
echo "- Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "- Trigger: ${{ github.event_name }}"
echo "- Package: ${PACKAGE_SOURCE}"
+ echo "- Test Step Outcome: ${{ steps.test.outcome }}"
+ echo ""
+ cat "${CASE_TABLE}" || true
echo ""
echo "## Log tail"
echo '```text'
@@ -133,6 +188,129 @@ jobs:
} | tee "${REPORT_FILE}"
cat "${REPORT_FILE}" >> "${GITHUB_STEP_SUMMARY}"
+ - name: Upload functional report to dashboard
+ if: always()
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ env.PF_TESTING_GH_TOKEN }}
+ REPORT_FILE: /tmp/rustfs-kms-report.md
+ SUITE: kms
+ run: |
+ set -euo pipefail
+ if [ -z "${GH_TOKEN:-}" ]; then
+ echo "PF_TESTING_GH_TOKEN is not configured; skipping dashboard upload"
+ exit 0
+ fi
+ DATE="$(date -u +%Y-%m-%d)"
+ REPORT_PATH="functional-reports/${SUITE}/${DATE}.md"
+ CONTENT="$(python3 -c 'import base64,sys;print(base64.b64encode(open(sys.argv[1],"rb").read()).decode())' "${REPORT_FILE}")"
+ SHA="$(gh api "repos/rustfs/dashboard/contents/${REPORT_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${SHA}" ]; then
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" --arg sha "${SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ fi
+
+ cat > /tmp/rustfs-functional-index.html <<'EOF'
+
+
+
+
+
+ RustFS Functional Test Reports
+
+
+
+
+
+
RustFS Functional Test Reports
+
S3, KMS, Tier report tabs. Each tab lists reports by date.
+
+
+
+
+
+
+
+ EOF
+
+ INDEX_PATH="functional/index.html"
+ INDEX_CONTENT="$(python3 -c 'import base64;print(base64.b64encode(open("/tmp/rustfs-functional-index.html","rb").read()).decode())')"
+ INDEX_SHA="$(gh api "repos/rustfs/dashboard/contents/${INDEX_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${INDEX_SHA}" ]; then
+ jq -n --arg msg "functional ui update" --arg content "${INDEX_CONTENT}" --arg sha "${INDEX_SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "functional ui init" --arg content "${INDEX_CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ fi
+
- name: Upload report and logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
diff --git a/.github/workflows/rustfs-pool-expand-test.yml b/.github/workflows/rustfs-pool-expand-test.yml
index 7dedb7787..31db6579a 100644
--- a/.github/workflows/rustfs-pool-expand-test.yml
+++ b/.github/workflows/rustfs-pool-expand-test.yml
@@ -46,7 +46,7 @@ on:
type: boolean
default: true
workflow_run:
- # Strict shared-environment order: run after tier test succeeds.
+ # Strict shared-environment order: run after tier test completes.
workflows: ["RustFS Tier Test"]
types: [completed]
@@ -75,11 +75,124 @@ env:
RUSTFS_NIGHTLY_PACKAGE_URL: ${{ vars.RUSTFS_NIGHTLY_PACKAGE_URL || 'https://dl.rustfs.com/artifacts/rustfs/packages/nightly/rustfs-nightly-latest.deb' }}
jobs:
+ heal-test:
+ name: Heal test
+ runs-on: smoke-testing
+ timeout-minutes: 480
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run' }}
+ steps:
+ - name: Checkout auto-testing scripts
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ with:
+ repository: rustfs/auto-testing
+ ref: main
+ path: auto-testing
+ persist-credentials: false
+ token: ${{ secrets.PF_TESTING_GH_TOKEN }}
+
+ - name: Show environment
+ run: |
+ uname -a
+ jq --version
+ openssl version
+ df -h /data | tail -1
+
+ - name: Cleanup environment (before)
+ if: ${{ inputs.cleanup_before != 'false' }}
+ run: |
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
+
+ - 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
+ ./auto-testing/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
+ ./auto-testing/rustfs_heal_test.sh "${ARGS[@]}"
+
+ - name: Run heal test (write -> outage -> heal -> verify)
+ run: |
+ ARGS=(--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)
+ if [ -n "${{ inputs.package_url }}" ]; then
+ ARGS+=(--package-url "${{ inputs.package_url }}")
+ else
+ ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
+ fi
+ ./auto-testing/rustfs_heal_test.sh "${ARGS[@]}"
+
+ - 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: Cleanup environment (after)
+ if: ${{ always() && inputs.cleanup_after != 'false' }}
+ run: |
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
+
+ - name: Notify on failure
+ if: failure()
+ run: |
+ echo "RustFS heal test failed"
+ echo "Package source: ${{ inputs.package_url || 'nightly (R2 latest)' }}"
+ echo "See the uploaded log artifact for details."
+
+ # Pool expansion runs after heal regardless of heal outcome.
pool-expansion-test:
name: Pool expansion / decommission test
runs-on: smoke-testing
timeout-minutes: 360
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
+ needs: heal-test
+ if: ${{ always() && (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run') }}
steps:
- name: Checkout auto-testing scripts
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@@ -98,13 +211,26 @@ jobs:
warp --version || true
df -h /data | tail -1
- - name: Reset test environment (before)
+ - name: Cleanup environment (before)
if: ${{ inputs.cleanup_before != 'false' }}
run: |
- chmod +x auto-testing/rustfs_pool_expand.sh
- ./auto-testing/rustfs_pool_expand.sh --reset -y
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
- - name: Install RustFS package & start first pool
+ - name: Install RustFS package & start cluster
run: |
ARGS=(--steps "1,2,3" -y --endpoint "${{ env.RUSTFS_API_ENDPOINT }}")
if [ -n "${{ inputs.package_url }}" ]; then
@@ -163,10 +289,24 @@ jobs:
/tmp/rustfs-warp.*.log
if-no-files-found: warn
- - name: Reset test environment (after)
+ - name: Cleanup environment (after)
if: ${{ always() && inputs.cleanup_after != 'false' }}
run: |
- ./auto-testing/rustfs_pool_expand.sh --reset -y
+ set -euo pipefail
+ read -r -a NODES <<< "${RUSTFS_NODES:-vm000 vm001 vm002}"
+ SSH_USER="${RUSTFS_SSH_USER:-azureuser}"
+ for node in "${NODES[@]}"; do
+ ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${SSH_USER}@${node}" '
+ set -euo pipefail
+ SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo -n"
+ ${SUDO} systemctl stop rustfs 2>/dev/null || true
+ if ${SUDO} dpkg -l rustfs 2>/dev/null | grep -q "^ii"; then
+ ${SUDO} dpkg -P rustfs
+ fi
+ for i in 1 2 3 4; do ${SUDO} rm -rf /data/rustfs${i}/mnmd; done
+ ${SUDO} rm -rf /var/log/rustfs /var/lib/rustfs/kms /var/lib/rustfs/kms-backup
+ '
+ done
- name: Notify on failure
if: failure()
@@ -174,82 +314,3 @@ jobs:
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 auto-testing scripts
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- with:
- repository: rustfs/auto-testing
- ref: main
- path: auto-testing
- persist-credentials: false
- token: ${{ secrets.PF_TESTING_GH_TOKEN }}
-
- - name: Reset test environment (before)
- if: ${{ inputs.cleanup_before != 'false' }}
- run: |
- chmod +x auto-testing/rustfs_heal_test.sh
- ./auto-testing/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
- ./auto-testing/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
- ./auto-testing/rustfs_heal_test.sh "${ARGS[@]}"
-
- - name: Run heal test (write -> outage -> heal -> verify)
- run: |
- ARGS=(--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)
- if [ -n "${{ inputs.package_url }}" ]; then
- ARGS+=(--package-url "${{ inputs.package_url }}")
- else
- ARGS+=(--package-url "${{ env.RUSTFS_NIGHTLY_PACKAGE_URL }}")
- fi
- ./auto-testing/rustfs_heal_test.sh "${ARGS[@]}"
-
- - 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: |
- ./auto-testing/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."
diff --git a/.github/workflows/rustfs-s3-compat-test.yml b/.github/workflows/rustfs-s3-compat-test.yml
index 2efe2986b..fffd0621f 100644
--- a/.github/workflows/rustfs-s3-compat-test.yml
+++ b/.github/workflows/rustfs-s3-compat-test.yml
@@ -33,10 +33,12 @@ env:
RUSTFS_NODES: ${{ secrets.RUSTFS_NODES || vars.RUSTFS_NODES }}
RUSTFS_SSH_USER: ${{ secrets.RUSTFS_SSH_USER || vars.RUSTFS_SSH_USER }}
RUSTFS_NIGHTLY_PACKAGE_URL: ${{ vars.RUSTFS_NIGHTLY_PACKAGE_URL || 'https://dl.rustfs.com/artifacts/rustfs/packages/nightly/rustfs-nightly-latest.deb' }}
+ PF_TESTING_GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }}
jobs:
s3-compat-test:
runs-on: smoke-testing
+ continue-on-error: true
timeout-minutes: 360
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
steps:
@@ -76,6 +78,7 @@ jobs:
- name: Run S3 compatibility suite
id: test
+ continue-on-error: true
env:
LOG_FILE: /tmp/rustfs-s3-compat.log
run: |
@@ -109,12 +112,68 @@ jobs:
else
PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}"
fi
+ CASE_TABLE="/tmp/rustfs-s3-compat-cases.md"
+ python3 - "${LOG_FILE}" "${CASE_TABLE}" <<'PY'
+ import re
+ import sys
+
+ log_file, out_file = sys.argv[1], sys.argv[2]
+ ansi = re.compile(r'\x1b\[[0-9;]*m')
+ start_re = re.compile(r'^---\s+([A-Z]+-[0-9]+)\s+(.+?)\s+---$')
+ done_re = re.compile(r'^\[(PASS|FAIL|UNSUPPORTED)\]\s+([A-Z]+-[0-9]+)\b')
+
+ rows = []
+ index = {}
+ current = None
+ try:
+ with open(log_file, 'r', encoding='utf-8', errors='replace') as fh:
+ for raw in fh:
+ line = ansi.sub('', raw).strip()
+ m = start_re.match(line)
+ if m:
+ case_id, name = m.group(1), m.group(2)
+ current = case_id
+ if case_id not in index:
+ index[case_id] = len(rows)
+ rows.append([case_id, name, 'RUNNING'])
+ continue
+ m = done_re.match(line)
+ if m:
+ status, case_id = m.group(1), m.group(2)
+ if case_id in index:
+ rows[index[case_id]][2] = status
+ else:
+ rows.append([case_id, case_id, status])
+ index[case_id] = len(rows) - 1
+ current = None
+ except FileNotFoundError:
+ rows = []
+
+ counts = {'PASS': 0, 'FAIL': 0, 'UNSUPPORTED': 0, 'RUNNING': 0}
+ for _, _, status in rows:
+ counts[status] = counts.get(status, 0) + 1
+
+ with open(out_file, 'w', encoding='utf-8') as out:
+ out.write('## Case Summary\n\n')
+ out.write(f"- Total: {len(rows)}\\n")
+ out.write(f"- PASS: {counts.get('PASS', 0)}\\n")
+ out.write(f"- FAIL: {counts.get('FAIL', 0)}\\n")
+ out.write(f"- UNSUPPORTED: {counts.get('UNSUPPORTED', 0)}\\n")
+ out.write('\\n')
+ out.write('| Case | Name | Status |\\n')
+ out.write('| --- | --- | --- |\\n')
+ for case_id, name, status in rows:
+ out.write(f'| {case_id} | {name} | {status} |\\n')
+ PY
{
echo "# RustFS S3 compatibility test report"
echo ""
echo "- Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "- Trigger: ${{ github.event_name }}"
echo "- Package: ${PACKAGE_SOURCE}"
+ echo "- Test Step Outcome: ${{ steps.test.outcome }}"
+ echo ""
+ cat "${CASE_TABLE}" || true
echo ""
echo "## Log tail"
echo '```text'
@@ -123,6 +182,129 @@ jobs:
} | tee "${REPORT_FILE}"
cat "${REPORT_FILE}" >> "${GITHUB_STEP_SUMMARY}"
+ - name: Upload functional report to dashboard
+ if: always()
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ env.PF_TESTING_GH_TOKEN }}
+ REPORT_FILE: /tmp/rustfs-s3-compat-report.md
+ SUITE: s3
+ run: |
+ set -euo pipefail
+ if [ -z "${GH_TOKEN:-}" ]; then
+ echo "PF_TESTING_GH_TOKEN is not configured; skipping dashboard upload"
+ exit 0
+ fi
+ DATE="$(date -u +%Y-%m-%d)"
+ REPORT_PATH="functional-reports/${SUITE}/${DATE}.md"
+ CONTENT="$(python3 -c 'import base64,sys;print(base64.b64encode(open(sys.argv[1],"rb").read()).decode())' "${REPORT_FILE}")"
+ SHA="$(gh api "repos/rustfs/dashboard/contents/${REPORT_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${SHA}" ]; then
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" --arg sha "${SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ fi
+
+ cat > /tmp/rustfs-functional-index.html <<'EOF'
+
+
+
+
+
+ RustFS Functional Test Reports
+
+
+
+
+
+
RustFS Functional Test Reports
+
S3, KMS, Tier report tabs. Each tab lists reports by date.
+
+
+
+
+
+
+
+ EOF
+
+ INDEX_PATH="functional/index.html"
+ INDEX_CONTENT="$(python3 -c 'import base64;print(base64.b64encode(open("/tmp/rustfs-functional-index.html","rb").read()).decode())')"
+ INDEX_SHA="$(gh api "repos/rustfs/dashboard/contents/${INDEX_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${INDEX_SHA}" ]; then
+ jq -n --arg msg "functional ui update" --arg content "${INDEX_CONTENT}" --arg sha "${INDEX_SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "functional ui init" --arg content "${INDEX_CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ fi
+
- name: Upload report and logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
diff --git a/.github/workflows/rustfs-tier-test.yml b/.github/workflows/rustfs-tier-test.yml
index 2fda50b70..765e0d867 100644
--- a/.github/workflows/rustfs-tier-test.yml
+++ b/.github/workflows/rustfs-tier-test.yml
@@ -12,7 +12,7 @@ on:
required: false
type: string
workflow_run:
- # Strict shared-environment order: run after KMS test succeeds.
+ # Strict shared-environment order: run after KMS test completes.
workflows: ["RustFS KMS Test"]
types: [completed]
@@ -38,8 +38,9 @@ env:
jobs:
tier-test:
runs-on: smoke-testing
+ continue-on-error: true
timeout-minutes: 420
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run' }}
steps:
- name: Checkout auto-testing scripts
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@@ -107,6 +108,7 @@ jobs:
- name: Run tier suite
id: test
+ continue-on-error: true
env:
LOG_FILE: /tmp/rustfs-tier.log
run: |
@@ -140,12 +142,65 @@ jobs:
else
PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}"
fi
+ CASE_TABLE="/tmp/rustfs-tier-cases.md"
+ python3 - "${LOG_FILE}" "${CASE_TABLE}" <<'PY'
+ import re
+ import sys
+
+ log_file, out_file = sys.argv[1], sys.argv[2]
+ ansi = re.compile(r'\x1b\[[0-9;]*m')
+ start_re = re.compile(r'^---\s+([A-Z]+-[0-9]+)\s+(.+?)\s+---$')
+ done_re = re.compile(r'^\[(PASS|FAIL|UNSUPPORTED)\]\s+([A-Z]+-[0-9]+)\b')
+
+ rows = []
+ index = {}
+ try:
+ with open(log_file, 'r', encoding='utf-8', errors='replace') as fh:
+ for raw in fh:
+ line = ansi.sub('', raw).strip()
+ m = start_re.match(line)
+ if m:
+ case_id, name = m.group(1), m.group(2)
+ if case_id not in index:
+ index[case_id] = len(rows)
+ rows.append([case_id, name, 'RUNNING'])
+ continue
+ m = done_re.match(line)
+ if m:
+ status, case_id = m.group(1), m.group(2)
+ if case_id in index:
+ rows[index[case_id]][2] = status
+ else:
+ rows.append([case_id, case_id, status])
+ index[case_id] = len(rows) - 1
+ except FileNotFoundError:
+ rows = []
+
+ counts = {'PASS': 0, 'FAIL': 0, 'UNSUPPORTED': 0, 'RUNNING': 0}
+ for _, _, status in rows:
+ counts[status] = counts.get(status, 0) + 1
+
+ with open(out_file, 'w', encoding='utf-8') as out:
+ out.write('## Case Summary\n\n')
+ out.write(f"- Total: {len(rows)}\\n")
+ out.write(f"- PASS: {counts.get('PASS', 0)}\\n")
+ out.write(f"- FAIL: {counts.get('FAIL', 0)}\\n")
+ out.write(f"- UNSUPPORTED: {counts.get('UNSUPPORTED', 0)}\\n")
+ out.write('\\n')
+ out.write('| Case | Name | Status |\\n')
+ out.write('| --- | --- | --- |\\n')
+ for case_id, name, status in rows:
+ out.write(f'| {case_id} | {name} | {status} |\\n')
+ PY
{
echo "# RustFS tier test report"
echo ""
echo "- Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "- Trigger: ${{ github.event_name }}"
echo "- Package: ${PACKAGE_SOURCE}"
+ echo "- Test Step Outcome: ${{ steps.test.outcome }}"
+ echo ""
+ cat "${CASE_TABLE}" || true
echo ""
echo "## Log tail"
echo '```text'
@@ -154,6 +209,129 @@ jobs:
} | tee "${REPORT_FILE}"
cat "${REPORT_FILE}" >> "${GITHUB_STEP_SUMMARY}"
+ - name: Upload functional report to dashboard
+ if: always()
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ env.PF_TESTING_GH_TOKEN }}
+ REPORT_FILE: /tmp/rustfs-tier-report.md
+ SUITE: tier
+ run: |
+ set -euo pipefail
+ if [ -z "${GH_TOKEN:-}" ]; then
+ echo "PF_TESTING_GH_TOKEN is not configured; skipping dashboard upload"
+ exit 0
+ fi
+ DATE="$(date -u +%Y-%m-%d)"
+ REPORT_PATH="functional-reports/${SUITE}/${DATE}.md"
+ CONTENT="$(python3 -c 'import base64,sys;print(base64.b64encode(open(sys.argv[1],"rb").read()).decode())' "${REPORT_FILE}")"
+ SHA="$(gh api "repos/rustfs/dashboard/contents/${REPORT_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${SHA}" ]; then
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" --arg sha "${SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "report(${SUITE}): ${DATE}" --arg content "${CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${REPORT_PATH}" --input - >/dev/null
+ fi
+
+ cat > /tmp/rustfs-functional-index.html <<'EOF'
+
+
+
+
+
+ RustFS Functional Test Reports
+
+
+
+
+
+
RustFS Functional Test Reports
+
S3, KMS, Tier report tabs. Each tab lists reports by date.
+
+
+
+
+
+
+
+ EOF
+
+ INDEX_PATH="functional/index.html"
+ INDEX_CONTENT="$(python3 -c 'import base64;print(base64.b64encode(open("/tmp/rustfs-functional-index.html","rb").read()).decode())')"
+ INDEX_SHA="$(gh api "repos/rustfs/dashboard/contents/${INDEX_PATH}" -q '.sha' 2>/dev/null || true)"
+ if [ -n "${INDEX_SHA}" ]; then
+ jq -n --arg msg "functional ui update" --arg content "${INDEX_CONTENT}" --arg sha "${INDEX_SHA}" \
+ '{message:$msg, content:$content, sha:$sha}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ else
+ jq -n --arg msg "functional ui init" --arg content "${INDEX_CONTENT}" \
+ '{message:$msg, content:$content}' \
+ | gh api --method PUT "repos/rustfs/dashboard/contents/${INDEX_PATH}" --input - >/dev/null
+ fi
+
- name: Upload report and logs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6