mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 06:13:14 +00:00
ci(ilm): add gated s3-tests lane for lifecycle expiration cases (#4715)
* ci(ilm): move first batch of 5 s3-tests lifecycle expiration cases into a gated behavior lane (backlog#1148 ilm-10)
The 20 lifecycle cases in excluded_tests.txt were labeled "vendor-specific"
but the real blocker was the absence of a Ceph lc_debug_interval equivalent.
RUSTFS_ILM_DEBUG_DAY_SECS (ilm-5) now provides that, so Days>=1 expiration
behavior is testable in seconds.
These cases assert that objects/versions/uploads are actually removed by the
background scanner and the stale-multipart cleanup loop. They cannot join the
default single-server s3-implemented-tests gate: it disables the scanner, and a
global RUSTFS_ILM_DEBUG_DAY_SECS also shrinks the x-amz-expiration header that
the already-passing test_lifecycle_expiration_header_* cases assert on. So this
adds an isolated lane instead of putting them in implemented_tests.txt.
First batch (5), each verified against the pinned upstream source and the
RustFS evaluator/scanner/stale-multipart execution paths:
- test_lifecycle_expiration (prefix Days=1/Days=5, scanner delete)
- test_lifecyclev2_expiration (same via ListObjectsV2)
- test_lifecycle_noncur_expiration (NoncurrentVersionExpiration)
- test_lifecycle_deletemarker_expiration (ExpiredObjectDeleteMarker cascade)
- test_lifecycle_multipart_expiration (AbortIncompleteMultipartUpload)
Dropped from the batch, kept excluded with reason:
- test_lifecycle_expiration_days0: RustFS accepts Expiration{Days:0} (returns
200; only Days<0 is rejected in crates/lifecycle/src/core.rs validate()),
while AWS/this test expect InvalidArgument. Real validation gap.
Changes:
- scripts/s3-tests/lifecycle_behavior_tests.txt: new exact-node-id run set.
- scripts/s3-tests/run.sh: honor an IMPLEMENTED_TESTS_FILE override so a lane
can point at an alternate whitelist.
- .github/workflows/ci.yml: new s3-lifecycle-behavior-tests PR-gate job that
starts rustfs with RUSTFS_ILM_DEBUG_DAY_SECS=10, scanner enabled (cycle 2s,
no start delay) and a 2s stale-multipart cleanup interval, running the new
list serially.
- scripts/s3-tests/report_compat.py: recognize the behavior list so the weekly
scope=all sweep (plain server) does not misclassify expected failures.
- scripts/s3-tests/excluded_tests.txt: remove the 5 enabled cases; re-annotate
the remaining 15 lifecycle exclusions with real reasons + batch-2 plan.
- docs/architecture/s3-compatibility-matrix.md: sync counts (implemented 451,
excluded 274, behavior 5) and document the lane.
Refs backlog#1148 (ilm-10), master plan backlog#1155.
* fix(lifecycle): reject zero-day expiration/noncurrent/abort rules (backlog#1148 ilm-10) (#4722)
This commit is contained in:
@@ -36,6 +36,12 @@ LIST_FILES = {
|
||||
"implemented": "implemented_tests.txt",
|
||||
"unimplemented": "unimplemented_tests.txt",
|
||||
"excluded": "excluded_tests.txt",
|
||||
# Lifecycle behavior lane (backlog#1148 ilm-10): gated by its own ci.yml job
|
||||
# against a debug-accelerated server. In the full `scope=all` sweep these run
|
||||
# against the plain server (no scanner / no RUSTFS_ILM_DEBUG_DAY_SECS) and are
|
||||
# EXPECTED to fail there, so a failure here is neither a regression nor an
|
||||
# unclassified failure.
|
||||
"behavior": "lifecycle_behavior_tests.txt",
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +128,7 @@ def main() -> int:
|
||||
promotions: dict[str, list[str]] = {"unimplemented": [], "excluded": []}
|
||||
unclassified_passed: list[str] = []
|
||||
unclassified_failed: list[str] = []
|
||||
behavior_passed: list[str] = []
|
||||
counts = {"passed": 0, "failed": 0, "error": 0, "skipped": 0}
|
||||
|
||||
for name, status in results.items():
|
||||
@@ -129,13 +136,19 @@ def main() -> int:
|
||||
if status in ("failed", "error"):
|
||||
if name in lists["implemented"]:
|
||||
regressions.append(name)
|
||||
elif name not in lists["unimplemented"] and name not in lists["excluded"]:
|
||||
elif (
|
||||
name not in lists["unimplemented"]
|
||||
and name not in lists["excluded"]
|
||||
and name not in lists["behavior"]
|
||||
):
|
||||
unclassified_failed.append(name)
|
||||
elif status == "passed":
|
||||
if name in lists["unimplemented"]:
|
||||
promotions["unimplemented"].append(name)
|
||||
elif name in lists["excluded"]:
|
||||
promotions["excluded"].append(name)
|
||||
elif name in lists["behavior"]:
|
||||
behavior_passed.append(name)
|
||||
elif name not in lists["implemented"]:
|
||||
unclassified_passed.append(name)
|
||||
|
||||
@@ -162,6 +175,12 @@ def main() -> int:
|
||||
promotions["excluded"],
|
||||
"Passing despite being excluded — re-evaluate the exclusion.",
|
||||
)
|
||||
lines += render_section(
|
||||
"Lifecycle behavior lane (passing in this run)",
|
||||
behavior_passed,
|
||||
"Gated by the dedicated `s3-lifecycle-behavior-tests` lane; expected to "
|
||||
"fail in the plain `scope=all` sweep (no debug day / scanner).",
|
||||
)
|
||||
lines += render_section(
|
||||
"Unclassified passes",
|
||||
unclassified_passed,
|
||||
|
||||
Reference in New Issue
Block a user