diff --git a/cmd/bucket-cors-middleware_test.go b/cmd/bucket-cors-middleware_test.go index d36d9034e..71af4cf0b 100644 --- a/cmd/bucket-cors-middleware_test.go +++ b/cmd/bucket-cors-middleware_test.go @@ -711,3 +711,33 @@ func testBucketCorsLoadFailedBucketFailsClosed(obj ObjectLayer, _ string, _ stri t.Fatalf("load-failed CORS lookup performed %d synchronous bucket metadata reads", got) } } + +// TestBucketCorsResidentConfigSurvivesRefreshFailure: a resident bucket keeps +// its last loaded CORS configuration through a failed refresh, like every +// other bucket configuration, and the failure set never records a resident +// bucket. Only a bucket that was never loaded fails closed. +func TestBucketCorsResidentConfigSurvivesRefreshFailure(t *testing.T) { + ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{ + t: t, + objAPITest: testBucketCorsResidentConfigSurvivesRefreshFailure, + endpoints: []string{"GetBucketCors"}, + }) +} + +func testBucketCorsResidentConfigSurvivesRefreshFailure(obj ObjectLayer, _ string, bucket string, _ http.Handler, _ auth.Credentials, t *testing.T) { + if _, err := updateLocalBucketCORSMetadata(t.Context(), obj, bucket, []byte(testSiteReplicationCORSDoc)); err != nil { + t.Fatal(err) + } + sys := globalBucketMetadataSys + sys.Lock() + sys.noteLoadFailure(bucket) + _, marked := sys.loadFailed[bucket] + sys.Unlock() + if marked { + t.Fatal("a resident bucket was recorded as a load failure") + } + cfg, _, err := sys.GetResidentCorsConfig(bucket) + if err != nil || cfg == nil { + t.Fatalf("resident CORS configuration lost after a refresh failure: cfg=%v err=%v", cfg, err) + } +} diff --git a/cmd/bucket-metadata-sys.go b/cmd/bucket-metadata-sys.go index 8f60c6df9..bb59155e4 100644 --- a/cmd/bucket-metadata-sys.go +++ b/cmd/bucket-metadata-sys.go @@ -51,17 +51,25 @@ type BucketMetadataSys struct { initialized bool group *singleflight.Group metadataMap map[string]BucketMetadata - // loadFailed records real buckets whose metadata could not be loaded at - // startup or during a refresh. They are absent from metadataMap even though - // the subsystem is initialized, and without this bit a resident-only lookup - // could not tell them apart from a name that is not a bucket at all. The - // set is bounded by the number of failed loads and empty in normal operation. + // loadFailed records real buckets whose metadata has never been loaded + // successfully because the startup load or a refresh failed. They are + // absent from metadataMap even though the subsystem is initialized, and + // without this bit a resident-only lookup could not tell them apart from a + // name that is not a bucket at all. It never holds a resident bucket, is + // bounded by the number of failed loads, and is empty in normal operation. loadFailed map[string]struct{} } // noteLoadFailure and clearLoadFailure maintain loadFailed; both expect the -// caller to hold sys.Lock. -func (sys *BucketMetadataSys) noteLoadFailure(bucket string) { sys.loadFailed[bucket] = struct{}{} } +// caller to hold sys.Lock. A bucket that is resident keeps its last loaded +// metadata through a failed refresh, exactly like every other bucket +// configuration, so the set only ever holds non-resident buckets. +func (sys *BucketMetadataSys) noteLoadFailure(bucket string) { + if _, resident := sys.metadataMap[bucket]; !resident { + sys.loadFailed[bucket] = struct{}{} + } +} + func (sys *BucketMetadataSys) clearLoadFailure(bucket string) { delete(sys.loadFailed, bucket) } // Count returns number of bucket metadata map entries.