Fix exact-version purges and delete-marker metadata healing

Require write-quorum absence for missing purge retries, aggregate removed and already-absent votes only for physical purges, and resolve receiver purges by version across pools. Preserve marker metadata through creation and healing while retaining DELETE response semantics.

Add real-disk quorum, pool, callback, metadata, heal and outbound replication regressions. Synchronize capacity fixture installation and restoration with concurrent IAM readers.

Signed-off-by: Feng Ruohang <rh@vonng.com>
(cherry picked from commit 22ba4d426677aa07470927fc349a43afd87e19b6)
This commit is contained in:
Feng Ruohang
2026-09-16 21:07:29 +08:00
parent 8d06424b12
commit eb4f5e5b31
10 changed files with 938 additions and 14 deletions
+41 -4
View File
@@ -82,17 +82,26 @@ type markerRecoveryTarget struct {
func replicationTestCapacity(obj ObjectLayer) func() {
var restore []func()
for _, pool := range obj.(*erasureServerPools).serverPools {
pool.erasureDisksMu.Lock()
for _, set := range pool.sets {
original := set.getDisks
disks := append([]StorageAPI(nil), original()...)
original := pool.erasureDisks[set.setIndex]
disks := append([]StorageAPI(nil), original...)
for i, disk := range disks {
if disk != nil {
disks[i] = tagTestCapacityDisk{StorageAPI: disk}
}
}
set.getDisks = func() []StorageAPI { return disks }
restore = append(restore, func() { set.getDisks = original })
// GetDisks copies this list under the same mutex. Keep its function
// stable while background IAM scans use the fixture, both when
// installing the adapter and when restoring the original disks.
pool.erasureDisks[set.setIndex] = disks
restore = append(restore, func() {
pool.erasureDisksMu.Lock()
pool.erasureDisks[set.setIndex] = original
pool.erasureDisksMu.Unlock()
})
}
pool.erasureDisksMu.Unlock()
}
return func() {
for _, fn := range restore {
@@ -101,6 +110,34 @@ func replicationTestCapacity(obj ObjectLayer) func() {
}
}
func TestReplicationCapacityConcurrentIAM(t *testing.T) {
z, _ := consistencyPools(t)
if _, _, err := initAPIHandlerTest(t.Context(), z, nil, MakeBucketOptions{}); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
defer cancel()
iam := globalIAMSys
started, finished := make(chan struct{}), make(chan error, 1)
go func() {
close(started)
for range 20 {
if err := iam.Load(ctx, false); err != nil {
finished <- err
return
}
}
finished <- nil
}()
<-started
for range 5000 {
replicationTestCapacity(z)()
}
if err := <-finished; err != nil {
t.Fatal(err)
}
}
func testReplicationMRFMarkerRecovery(t *testing.T, obj ObjectLayer, backend, bucket string, router http.Handler, creds auth.Credentials, tc markerRecoveryCase) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()