diff --git a/cmd/erasure-metadata.go b/cmd/erasure-metadata.go index c245ef2f4..b2f8c0dfd 100644 --- a/cmd/erasure-metadata.go +++ b/cmd/erasure-metadata.go @@ -382,14 +382,39 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time. return FileInfo{}, errErasureReadQuorum } + // Find the successor mod time in quorum, otherwise leave the + // candidate's successor modTime as found + succModTimeMap := make(map[time.Time]int) + var candidate FileInfo + var found bool for i, hash := range metaHashes { if hash == maxHash { if metaArr[i].IsValid() { - return metaArr[i], nil + if !found { + candidate = metaArr[i] + found = true + } + succModTimeMap[metaArr[i].SuccessorModTime]++ } } } + var succModTime time.Time + var smodTimeQuorum bool + for smodTime, count := range succModTimeMap { + if count >= quorum { + smodTimeQuorum = true + succModTime = smodTime + break + } + } + if found { + if smodTimeQuorum { + candidate.SuccessorModTime = succModTime + candidate.IsLatest = succModTime.IsZero() + } + return candidate, nil + } return FileInfo{}, errErasureReadQuorum } diff --git a/cmd/erasure-metadata_test.go b/cmd/erasure-metadata_test.go index b5cf54461..8cee75dda 100644 --- a/cmd/erasure-metadata_test.go +++ b/cmd/erasure-metadata_test.go @@ -158,7 +158,7 @@ func TestObjectToPartOffset(t *testing.T) { } func TestFindFileInfoInQuorum(t *testing.T) { - getNFInfo := func(n int, quorum int, t int64, dataDir string) []FileInfo { + getNFInfo := func(n int, quorum int, t int64, dataDir string, succModTimes []time.Time) []FileInfo { fi := newFileInfo("test", 8, 8) fi.AddObjectPart(1, "etag", 100, 100, UTCNow(), nil, nil) fi.ModTime = time.Unix(t, 0) @@ -167,6 +167,10 @@ func TestFindFileInfoInQuorum(t *testing.T) { for i := range fis { fis[i] = fi fis[i].Erasure.Index = i + 1 + if succModTimes != nil { + fis[i].SuccessorModTime = succModTimes[i] + fis[i].IsLatest = succModTimes[i].IsZero() + } quorum-- if quorum == 0 { break @@ -175,39 +179,79 @@ func TestFindFileInfoInQuorum(t *testing.T) { return fis } + commonSuccModTime := time.Date(2023, time.August, 25, 0, 0, 0, 0, time.UTC) + succModTimesInQuorum := make([]time.Time, 16) + succModTimesNoQuorum := make([]time.Time, 16) + for i := 0; i < 16; i++ { + if i < 4 { + continue + } + succModTimesInQuorum[i] = commonSuccModTime + if i < 9 { + continue + } + succModTimesNoQuorum[i] = commonSuccModTime + } tests := []struct { - fis []FileInfo - modTime time.Time - expectedErr error - expectedQuorum int + fis []FileInfo + modTime time.Time + succmodTimes []time.Time + expectedErr error + expectedQuorum int + expectedSuccModTime time.Time + expectedIsLatest bool }{ { - fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), + fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), modTime: time.Unix(1603863445, 0), expectedErr: nil, expectedQuorum: 8, }, { - fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), + fis: getNFInfo(16, 7, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), modTime: time.Unix(1603863445, 0), expectedErr: errErasureReadQuorum, expectedQuorum: 8, }, { - fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21"), + fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", nil), modTime: time.Unix(1603863445, 0), expectedErr: errErasureReadQuorum, expectedQuorum: 0, }, + { + fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesInQuorum), + modTime: time.Unix(1603863445, 0), + expectedErr: nil, + expectedQuorum: 12, + expectedSuccModTime: commonSuccModTime, + expectedIsLatest: false, + }, + { + fis: getNFInfo(16, 16, 1603863445, "36a21454-a2ca-11eb-bbaa-93a81c686f21", succModTimesNoQuorum), + modTime: time.Unix(1603863445, 0), + expectedErr: nil, + expectedQuorum: 12, + expectedSuccModTime: time.Time{}, + expectedIsLatest: true, + }, } for _, test := range tests { test := test t.Run("", func(t *testing.T) { - _, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum) + fi, err := findFileInfoInQuorum(context.Background(), test.fis, test.modTime, "", test.expectedQuorum) if err != test.expectedErr { t.Errorf("Expected %s, got %s", test.expectedErr, err) } + if test.succmodTimes != nil { + if !test.expectedSuccModTime.Equal(fi.SuccessorModTime) { + t.Errorf("Expected successor mod time to be %v but got %v", test.expectedSuccModTime, fi.SuccessorModTime) + } + if test.expectedIsLatest != fi.IsLatest { + t.Errorf("Expected IsLatest to be %v but got %v", test.expectedIsLatest, fi.IsLatest) + } + } }) } }