mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-03 03:47:42 +00:00
6c99d4fe22
nix-flake-update opens a PR every Sunday that changes flake.lock and nothing else. flake.lock is consumed only by Nix packaging — cargo never reads it — yet it was in no paths filter, so each of those PRs ran the full Continuous Integration pipeline and the merge then ran Build and Release too. Added to all four lists that have to agree: ci.yml's push and pull_request paths-ignore, build.yml's push paths-ignore, and ci-docs-only.yml's paths. The cron stays. flake.lock still has consumers — anyone running `nix build` or `nix develop` — so stopping the updates would remove the workflow's output, not just its CI cost. Those four lists drifting is a silent failure, so scripts/check_ci_paths_sync.sh now asserts the pair that matters: ci.yml's pull_request paths-ignore must equal ci-docs-only.yml's paths. An entry present only in the first means a PR touching those files triggers neither workflow, nobody reports "Test and Lint" or "Quick Checks", and the PR waits on a required check forever. It also asserts both companion job names still exist, since renaming one produces the same hang. The push list is not compared: no required check is reported for push events. Also in this cleanup: - nix-flake-update's GITHUB_TOKEN drops to contents: read. The branch push and the pull request are both made by update-flake-lock with the FLAKE_UPDATE_TOKEN PAT, so the write scopes were an unused repo-write credential on an unattended weekly job. - build.yml's build_docker dispatch input is documented as advisory. docker.yml triggers on workflow_run and requires the triggering event to be a tag push, so a manual dispatch never reaches it whatever this input says. - The nine disabled workflow files get a banner saying so. Their disabled_manually state lives in GitHub's UI and is invisible when reading the file, which has already misled one audit into treating dead workflows as live. Refs: rustfs/backlog#1598, rustfs/backlog#1603
368 lines
14 KiB
YAML
368 lines
14 KiB
YAML
# Copyright 2024 RustFS Team
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Full ceph/s3-tests sweep against RustFS.
|
|
#
|
|
# The per-PR compatibility gate lives in ci.yml (job s3-implemented-tests) and
|
|
# runs only the implemented_tests.txt whitelist. This workflow complements it:
|
|
#
|
|
# - Scheduled weekly full sweep (TEST_SCOPE=all): runs the ENTIRE upstream
|
|
# suite and reports promotion candidates (tests that newly pass) and
|
|
# unclassified tests. The job fails only on regressions in the implemented
|
|
# whitelist or on infrastructure errors — expected failures from
|
|
# not-yet-implemented features do not turn the run red.
|
|
# - Manual runs (workflow_dispatch): same, with configurable mode/scope.
|
|
#
|
|
# All test execution is delegated to scripts/s3-tests/run.sh (single source of
|
|
# truth, also used locally and by the PR gate). This workflow only provisions
|
|
# the server topology: a single node, or a real 4-node distributed cluster
|
|
# (erasure-coded across nodes) behind an HAProxy round-robin load balancer.
|
|
#
|
|
# Runner infrastructure (ci-1, backlog#1149): this sweep needs BOTH a working
|
|
# Docker daemon (to build the source image and run the single/multi topologies)
|
|
# AND a Python toolchain with pip (for awscurl/tox and the pytest harness).
|
|
# It previously ran on the self-hosted `sm-standard-4` label, which is served
|
|
# by heterogeneous runners: some scheduled runs landed on minimal pods that
|
|
# had neither `pip` (bare python3, `No module named pip`) nor `docker.sock`,
|
|
# so "Install Python tools" died before any test executed (all 15 recorded
|
|
# runs failed). Two changes make the infra assumptions explicit instead of
|
|
# trusting drifting runner state:
|
|
# 1. Pin to GitHub-hosted `ubuntu-latest`, which reliably ships Docker,
|
|
# docker compose, python3 and pip (no self-hosted fleet drift).
|
|
# 2. Set up Python explicitly (actions/setup-python) before any pip usage,
|
|
# so the workflow stays correct even if the runner image changes.
|
|
# The PR gate (ci.yml s3-implemented-tests) is unaffected: it avoids Docker
|
|
# via DEPLOY_MODE=binary and defers all pip setup to run.sh's self-bootstrap.
|
|
|
|
# DISABLED. This workflow is switched off in the repository's Actions settings
|
|
# (state: disabled_manually) and does not run on any trigger, including its cron
|
|
# and workflow_dispatch. That state lives in GitHub's UI and is invisible when
|
|
# reading this file, which has already misled at least one audit — hence this
|
|
# banner. Re-enabling is a UI action; anyone doing so should first check that the
|
|
# workflow still matches the current CI layout. See rustfs/backlog#1603.
|
|
#
|
|
name: e2e-s3tests
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
test-mode:
|
|
description: "Server topology"
|
|
required: true
|
|
type: choice
|
|
default: "single"
|
|
options:
|
|
- single
|
|
- multi
|
|
scope:
|
|
description: "Test scope: full upstream suite or implemented whitelist"
|
|
required: true
|
|
type: choice
|
|
default: "all"
|
|
options:
|
|
- all
|
|
- implemented
|
|
xdist:
|
|
description: "pytest-xdist workers. '0' to disable."
|
|
required: false
|
|
default: "4"
|
|
maxfail:
|
|
description: "Stop after N failures. '0' to run everything."
|
|
required: false
|
|
default: "0"
|
|
markexpr:
|
|
description: "Optional pytest -m expression"
|
|
required: false
|
|
default: ""
|
|
schedule:
|
|
# Weekly full sweep (Sunday 02:00 UTC): full suite, run against BOTH the
|
|
# single-node and the 4-node distributed topologies (matrix below).
|
|
- cron: "0 2 * * 0"
|
|
|
|
env:
|
|
# main user
|
|
S3_ACCESS_KEY: rustfsadmin-ci
|
|
S3_SECRET_KEY: rustfssecret-ci
|
|
# alt user (must be different from main for many s3-tests)
|
|
S3_ALT_ACCESS_KEY: rustfsalt
|
|
S3_ALT_SECRET_KEY: rustfsalt
|
|
|
|
S3_REGION: us-east-1
|
|
S3_HOST: 127.0.0.1
|
|
S3_PORT: "9000"
|
|
|
|
RUST_LOG: info
|
|
PLATFORM: linux/amd64
|
|
BUILDX_CACHE_SCOPE: rustfs-e2e-s3tests-source
|
|
|
|
# Scheduled runs have no inputs; default to a full sweep.
|
|
TEST_SCOPE: ${{ github.event.inputs.scope || 'all' }}
|
|
XDIST: ${{ github.event.inputs.xdist || '4' }}
|
|
MAXFAIL: ${{ github.event.inputs.maxfail || '0' }}
|
|
MARKEXPR: ${{ github.event.inputs.markexpr || '' }}
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs['test-mode'] || 'single' }}
|
|
cancel-in-progress: true
|
|
|
|
# Only alert-on-failure needs more than read access; it declares its own
|
|
# job-level `issues: write`.
|
|
permissions:
|
|
contents: read
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
s3tests:
|
|
# GitHub-hosted: reliably provides Docker + docker compose + python3/pip.
|
|
# See the header note (ci-1) for why the self-hosted sm-standard-4 label
|
|
# was abandoned. TODO(ci-8): scheduled-failure alerting (auto-open issue)
|
|
# is added by the ci-8 composite action; do not implement it here.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 180
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# Scheduled sweeps cover both topologies; manual runs use the input.
|
|
test-mode: ${{ github.event_name == 'schedule' && fromJSON('["single", "multi"]') || fromJSON(format('["{0}"]', github.event.inputs.test-mode || 'single')) }}
|
|
env:
|
|
TEST_MODE: ${{ matrix.test-mode }}
|
|
steps:
|
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
|
|
# Provision Python explicitly rather than trusting the runner image to
|
|
# ship a working pip (ci-1: a bare python3 without pip is what broke the
|
|
# scheduled sweep). Keeps the workflow correct across runner drift.
|
|
- name: Set up Python
|
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Cache pip downloads
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
|
with:
|
|
path: ~/.cache/pip
|
|
key: ${{ runner.os }}-pip-e2e-s3tests-${{ hashFiles('.github/workflows/e2e-s3tests.yml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-pip-e2e-s3tests-
|
|
|
|
- name: Install Python tools
|
|
run: |
|
|
python3 -m pip install --user --upgrade pip awscurl tox
|
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
|
|
- name: Enable buildx
|
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
|
|
|
- name: Build RustFS image (source, cached)
|
|
run: |
|
|
DOCKER_BUILDKIT=1 docker buildx build --load \
|
|
--platform "${PLATFORM}" \
|
|
--cache-from "type=gha,scope=${BUILDX_CACHE_SCOPE}" \
|
|
--cache-to "type=gha,mode=max,scope=${BUILDX_CACHE_SCOPE}" \
|
|
-t rustfs-ci \
|
|
-f Dockerfile.source .
|
|
|
|
- name: Start single RustFS
|
|
if: env.TEST_MODE == 'single'
|
|
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" \
|
|
-e RUSTFS_ADDRESS=0.0.0.0:9000 \
|
|
-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
|
|
|
|
- name: Start 4-node distributed cluster
|
|
if: env.TEST_MODE == 'multi'
|
|
run: |
|
|
# A real distributed deployment: every node lists all endpoints in
|
|
# RUSTFS_VOLUMES so data is erasure-coded ACROSS nodes. Do not use
|
|
# node-local volume paths here — that would create four independent
|
|
# single-node stores behind the load balancer.
|
|
cat > compose.yml <<EOF
|
|
x-rustfs-node: &rustfs-node
|
|
image: rustfs-ci
|
|
networks: [rustfs-net]
|
|
environment:
|
|
RUSTFS_ADDRESS: "0.0.0.0:9000"
|
|
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:
|
|
<<: *rustfs-node
|
|
hostname: rustfs1
|
|
volumes: [rustfs1-data:/data]
|
|
rustfs2:
|
|
<<: *rustfs-node
|
|
hostname: rustfs2
|
|
volumes: [rustfs2-data:/data]
|
|
rustfs3:
|
|
<<: *rustfs-node
|
|
hostname: rustfs3
|
|
volumes: [rustfs3-data:/data]
|
|
rustfs4:
|
|
<<: *rustfs-node
|
|
hostname: rustfs4
|
|
volumes: [rustfs4-data:/data]
|
|
lb:
|
|
image: haproxy:2.9
|
|
hostname: lb
|
|
networks: [rustfs-net]
|
|
ports:
|
|
- "${S3_PORT}:9000"
|
|
volumes:
|
|
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
|
networks:
|
|
rustfs-net:
|
|
name: rustfs-net
|
|
volumes:
|
|
rustfs1-data:
|
|
rustfs2-data:
|
|
rustfs3-data:
|
|
rustfs4-data:
|
|
EOF
|
|
|
|
cat > haproxy.cfg <<'EOF'
|
|
defaults
|
|
mode http
|
|
timeout connect 5s
|
|
timeout client 30s
|
|
timeout server 30s
|
|
|
|
frontend fe_s3
|
|
bind *:9000
|
|
default_backend be_s3
|
|
|
|
backend be_s3
|
|
balance roundrobin
|
|
server s1 rustfs1:9000 check
|
|
server s2 rustfs2:9000 check
|
|
server s3 rustfs3:9000 check
|
|
server s4 rustfs4:9000 check
|
|
EOF
|
|
|
|
docker compose -f compose.yml up -d
|
|
|
|
- name: Wait for RustFS ready
|
|
run: |
|
|
for _ in {1..120}; do
|
|
if curl -sf "http://${S3_HOST}:${S3_PORT}/health" >/dev/null 2>&1; then
|
|
echo "RustFS is ready"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "Health check timed out" >&2
|
|
if [ "${TEST_MODE}" = "single" ]; then
|
|
docker logs rustfs-single || true
|
|
else
|
|
docker compose -f compose.yml logs --tail=200 || true
|
|
fi
|
|
exit 1
|
|
|
|
- name: Run ceph s3-tests
|
|
run: |
|
|
set +e
|
|
DEPLOY_MODE=existing \
|
|
TEST_MODE="${TEST_MODE}" \
|
|
TEST_SCOPE="${TEST_SCOPE}" \
|
|
XDIST="${XDIST}" \
|
|
MAXFAIL="${MAXFAIL}" \
|
|
MARKEXPR="${MARKEXPR}" \
|
|
./scripts/s3-tests/run.sh
|
|
RC=$?
|
|
set -e
|
|
|
|
if [ "${TEST_SCOPE}" = "implemented" ]; then
|
|
# Whitelist run: every failure is a regression.
|
|
exit "${RC}"
|
|
fi
|
|
|
|
# Full sweep: failures outside the implemented whitelist are
|
|
# inventory (promotion candidates / unimplemented features), not a
|
|
# gate. Fail only on whitelist regressions or infrastructure errors.
|
|
JUNIT="artifacts/s3tests-${TEST_MODE}/junit.xml"
|
|
if [ ! -f "${JUNIT}" ]; then
|
|
echo "No junit.xml produced — infrastructure failure (exit ${RC})" >&2
|
|
exit "${RC}"
|
|
fi
|
|
python3 scripts/s3-tests/report_compat.py \
|
|
--junit "${JUNIT}" \
|
|
--lists-dir scripts/s3-tests \
|
|
--fail-on-regression
|
|
|
|
- name: Publish compatibility report
|
|
if: always()
|
|
run: |
|
|
REPORT="artifacts/s3tests-${TEST_MODE}/compat-report.md"
|
|
if [ -f "${REPORT}" ]; then
|
|
cat "${REPORT}" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Collect RustFS logs
|
|
if: always()
|
|
run: |
|
|
mkdir -p "artifacts/rustfs-${TEST_MODE}"
|
|
if [ "${TEST_MODE}" = "single" ]; then
|
|
docker logs rustfs-single > "artifacts/rustfs-${TEST_MODE}/rustfs.log" 2>&1 || true
|
|
docker inspect rustfs-single > "artifacts/rustfs-${TEST_MODE}/inspect.json" || true
|
|
else
|
|
docker compose -f compose.yml logs --no-color > "artifacts/rustfs-${TEST_MODE}/cluster.log" 2>&1 || true
|
|
fi
|
|
|
|
- name: Upload artifacts
|
|
if: always() && env.ACT != 'true'
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: s3tests-${{ env.TEST_MODE }}
|
|
path: artifacts/**
|
|
|
|
alert-on-failure:
|
|
name: Alert on scheduled failure
|
|
needs: [s3tests]
|
|
# `always()` is required: without it this job is skipped when a needed
|
|
# job fails. Alerts only for scheduled runs (backlog#1149 ci-8) — manual
|
|
# dispatch failures are already watched by a human.
|
|
if: always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure')
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
steps:
|
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
- name: Open or update failure-tracking issue
|
|
uses: ./.github/actions/schedule-failure-issue
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|