mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-13 09:17:41 +08:00
* fix(s3): evaluate conditional reads before opening object bodies Reuse locked metadata snapshots for conditional GETs and unify HEAD preconditions and cache headers. Preserve body-cache hooks and validate encryption metadata and key authorization before returning bodyless errors. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * refactor(s3): unify conditional reads at metadata quorum Apply scoped metadata conditions to the existing reader pipeline instead of maintaining parallel prepared-read branches. Preserve quorum checks, early-stop, coalescing, and cache-hook behavior on condition misses. Track metadata-only termination through selection and reject body access or a changed decision. Keep concrete reader futures off request stacks. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * test(s3): strengthen conditional read acceptance evidence Assert zero shard, decode, cache-fill, and remote-body calls on matching conditions with live positive controls. Exercise GET and HEAD through s3s and Hyper with conditional-header and precedence matrices. Add reproducible paired application-read benchmarks with isolated metadata controls and strict drift, artifact, and resource gates. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * refactor(s3): remove obsolete conditional comparison helper Keep ETag comparison tests on the canonical read-condition evaluator, remove unused date-trait imports, and name the HTTP case type. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * fix(s3): validate full conditional etag fields Reject adjacent tags and malformed trailing list members before accepting a match. Keep the existing request-scoped metadata read and authorization order, while removing redundant validation wrappers and unused benchmark overrides. Replace simulated phase coverage with real 304/412 cache-fill probes and extend HTTP/1.1 and HTTP/2 ingress regressions. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * fix(ecstore): cache terminal conditional metadata safely Preserve quorum-validated conditional metadata when early termination cancels the remaining fanout. Restrict those entries to terminal GET readers; body and full-layout consumers still obtain their own metadata. Serialize cache publication per key so conditional entries cannot replace complete layouts or renew their TTL. Retain generation fencing and the final selected-metadata predicate check before any body access. Cover real four-disk hedging, nonterminal body and range reads, consumer isolation, generation races, atomic publication and actual cache expiry. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> * fix(log-analyzer): align heal writer failure rule with current logs Match the current structured all-targets failure, retain legacy log support, and reject partial or unrelated failures. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com> Co-authored-by: zhi22915 <qiuzgang@gmail.com>
117 lines
5.2 KiB
Python
117 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify that paired conditional-read evidence cannot bypass acceptance gates."""
|
|
|
|
import copy
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from run_conditional_read_abba import compare, parse_rows, run_case
|
|
|
|
|
|
def row():
|
|
return {"size": 4096, "kind": "miss", "cache": False, "slowtail_ms": "0",
|
|
"ops_per_sec": 1000, "p95_ms": 2, "p99_ms": 3,
|
|
"lock_p95_ms": 1, "lock_p99_ms": 2}
|
|
|
|
|
|
class AcceptanceTests(unittest.TestCase):
|
|
def test_stable_equivalent_legs_pass(self):
|
|
self.assertTrue(compare([{("case",): row()} for _ in range(4)])["accepted"])
|
|
|
|
def test_baseline_drift_blocks_candidate_attribution(self):
|
|
legs = [{("case",): row()} for _ in range(4)]
|
|
legs[3][("case",)]["ops_per_sec"] = 800
|
|
result = compare(legs)
|
|
self.assertFalse(result["accepted"])
|
|
self.assertEqual(result["reason"], "baseline_drift")
|
|
self.assertNotIn("cases", result)
|
|
|
|
def test_miss_throughput_and_lock_regressions_fail(self):
|
|
for metric, value in [("ops_per_sec", 900), ("lock_p99_ms", 4)]:
|
|
legs = [{("case",): row()} for _ in range(4)]
|
|
for index in (1, 2):
|
|
legs[index][("case",)][metric] = value
|
|
self.assertFalse(compare(legs)["accepted"], metric)
|
|
|
|
def test_missing_workload_is_rejected(self):
|
|
legs = [{("case",): row()} for _ in range(4)]
|
|
legs[1] = {}
|
|
with self.assertRaises(ValueError):
|
|
compare(legs)
|
|
|
|
def test_complete_rows_include_first_libtest_output_line(self):
|
|
rows = []
|
|
for size in (4096, 1_300_000):
|
|
for kind in ("unconditional", "hit", "miss"):
|
|
value = copy.copy(row())
|
|
value.update(size=size, kind=kind)
|
|
rows.append("CONDITIONAL_BENCH " + json.dumps(value))
|
|
output = "test app::benchmark ... " + "\n".join(rows)
|
|
self.assertEqual(len(parse_rows(output)), 6)
|
|
with self.assertRaises(ValueError):
|
|
parse_rows(output + "\n" + rows[0])
|
|
|
|
def test_wrong_shape_or_nonfinite_metrics_are_rejected(self):
|
|
rows = []
|
|
for size in (4096, 1_300_000):
|
|
for kind in ("unconditional", "hit", "miss"):
|
|
value = copy.copy(row())
|
|
value.update(size=size, kind=kind)
|
|
rows.append(value)
|
|
for field, value in [("kind", "unknown"), ("p99_ms", float("nan"))]:
|
|
changed = copy.deepcopy(rows)
|
|
changed[0][field] = value
|
|
output = "\n".join("CONDITIONAL_BENCH " + json.dumps(item) for item in changed)
|
|
with self.assertRaises(ValueError):
|
|
parse_rows(output)
|
|
|
|
|
|
class IsolationTests(unittest.TestCase):
|
|
def process(self):
|
|
process = MagicMock()
|
|
process.__enter__.return_value = process
|
|
process.communicate.return_value = ("saved output", None)
|
|
process.returncode = 0
|
|
return process
|
|
|
|
def test_changed_executable_is_rejected_before_launch(self):
|
|
with patch("run_conditional_read_abba.digest", return_value="changed"), \
|
|
patch("run_conditional_read_abba.subprocess.Popen") as launch:
|
|
with self.assertRaisesRegex(RuntimeError, "before execution"):
|
|
run_case(Path("binary"), "expected", {})
|
|
launch.assert_not_called()
|
|
|
|
def test_changed_executable_is_rejected_after_launch(self):
|
|
with patch("run_conditional_read_abba.digest", side_effect=["expected", "changed"]), \
|
|
patch("run_conditional_read_abba.check_resource_isolation"), \
|
|
patch("run_conditional_read_abba.subprocess.Popen", return_value=self.process()):
|
|
with self.assertRaisesRegex(RuntimeError, "during execution"):
|
|
run_case(Path("binary"), "expected", {})
|
|
|
|
def test_final_case_checks_resource_isolation_after_exit(self):
|
|
with patch("run_conditional_read_abba.digest", return_value="expected"), \
|
|
patch("run_conditional_read_abba.check_resource_isolation", side_effect=RuntimeError("concurrent build")), \
|
|
patch("run_conditional_read_abba.subprocess.Popen", return_value=self.process()):
|
|
with self.assertRaisesRegex(RuntimeError, "concurrent build"):
|
|
run_case(Path("binary"), "expected", {})
|
|
|
|
def test_concurrent_build_during_execution_kills_case_and_saves_log(self):
|
|
process = self.process()
|
|
process.communicate.side_effect = [subprocess.TimeoutExpired("binary", 1), ("saved output", None)]
|
|
log = MagicMock()
|
|
with patch("run_conditional_read_abba.digest", return_value="expected"), \
|
|
patch("run_conditional_read_abba.check_resource_isolation", side_effect=RuntimeError("concurrent build")) as check, \
|
|
patch("run_conditional_read_abba.subprocess.Popen", return_value=process):
|
|
with self.assertRaisesRegex(RuntimeError, "concurrent build"):
|
|
run_case(Path("binary"), "expected", {}, log)
|
|
check.assert_called_once_with(check_load=False)
|
|
process.kill.assert_called_once()
|
|
log.write_text.assert_called_once_with("saved output")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|