Add more advanced cases for dangling (#18968)

This commit is contained in:
Harshavardhana 2024-02-04 14:36:13 -08:00 committed by GitHub
parent 8b68e0bfdc
commit f225ca3312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 112 additions and 62 deletions

View File

@ -900,11 +900,10 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
// We can consider an object data not reliable // We can consider an object data not reliable
// when xl.meta is not found in read quorum disks. // when xl.meta is not found in read quorum disks.
// or when xl.meta is not readable in read quorum disks. // or when xl.meta is not readable in read quorum disks.
danglingErrsCount := func(cerrs []error) (int, int, int) { danglingErrsCount := func(cerrs []error) (int, int) {
var ( var (
notFoundCount int notFoundCount int
corruptedCount int nonActionableCount int
driveNotFoundCount int
) )
for _, readErr := range cerrs { for _, readErr := range cerrs {
if readErr == nil { if readErr == nil {
@ -913,29 +912,16 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
switch { switch {
case errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound): case errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound):
notFoundCount++ notFoundCount++
case errors.Is(readErr, errFileCorrupt):
corruptedCount++
default: default:
// All other errors are non-actionable // All other errors are non-actionable
driveNotFoundCount++ nonActionableCount++
} }
} }
return notFoundCount, corruptedCount, driveNotFoundCount return notFoundCount, nonActionableCount
} }
ndataErrs := make([]error, len(dataErrs)) notFoundMetaErrs, nonActionableMetaErrs := danglingErrsCount(errs)
for i := range dataErrs { notFoundPartsErrs, nonActionablePartsErrs := danglingErrsCount(dataErrs)
if errs[i] != dataErrs[i] {
// Only count part errors, if the error is not
// same as xl.meta error. This is to avoid
// double counting when both parts and xl.meta
// are not available.
ndataErrs[i] = dataErrs[i]
}
}
notFoundMetaErrs, corruptedMetaErrs, driveNotFoundMetaErrs := danglingErrsCount(errs)
notFoundPartsErrs, corruptedPartsErrs, driveNotFoundPartsErrs := danglingErrsCount(ndataErrs)
for _, m := range metaArr { for _, m := range metaArr {
if m.IsValid() { if m.IsValid() {
@ -945,10 +931,9 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
} }
if !validMeta.IsValid() { if !validMeta.IsValid() {
// validMeta is invalid because notFoundPartsErrs is // validMeta is invalid because all xl.meta is missing apparently
// greater than parity blocks, thus invalidating the FileInfo{} // we should figure out if dataDirs are also missing > dataBlocks.
// every dataErrs[i], metaArr[i] is an empty FileInfo{} dataBlocks := (len(dataErrs) + 1) / 2
dataBlocks := (len(ndataErrs) + 1) / 2
if notFoundPartsErrs > dataBlocks { if notFoundPartsErrs > dataBlocks {
// Not using parity to ensure that we do not delete // Not using parity to ensure that we do not delete
// any valid content, if any is recoverable. But if // any valid content, if any is recoverable. But if
@ -965,25 +950,40 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
return validMeta, false return validMeta, false
} }
if driveNotFoundMetaErrs > 0 || driveNotFoundPartsErrs > 0 { if nonActionableMetaErrs > 0 || nonActionablePartsErrs > 0 {
return validMeta, false return validMeta, false
} }
if validMeta.Deleted { if validMeta.Deleted {
// notFoundPartsErrs is ignored since // notFoundPartsErrs is ignored since
// - delete marker does not have any parts // - delete marker does not have any parts
return validMeta, corruptedMetaErrs+notFoundMetaErrs > len(errs)/2 dataBlocks := (len(errs) + 1) / 2
return validMeta, notFoundMetaErrs > dataBlocks
} }
totalErrs := notFoundMetaErrs + corruptedMetaErrs + notFoundPartsErrs + corruptedPartsErrs quorum := validMeta.Erasure.DataBlocks
if validMeta.IsRemote() { if validMeta.Erasure.DataBlocks == validMeta.Erasure.ParityBlocks {
// notFoundPartsErrs is ignored since quorum++
// - transition status of complete has no parts
totalErrs = notFoundMetaErrs + corruptedMetaErrs
} }
// We have valid meta, now verify if we have enough files with parity blocks. // TODO: It is possible to replay the object via just single
return validMeta, totalErrs > validMeta.Erasure.ParityBlocks // xl.meta file, considering quorum number of data-dirs are still
// present on other drives.
//
// However this requires a bit of a rewrite, leave this up for
// future work.
if notFoundMetaErrs > 0 && notFoundMetaErrs >= quorum {
// All xl.meta is beyond data blocks missing, this is dangling
return validMeta, true
}
if notFoundPartsErrs > 0 && notFoundPartsErrs >= quorum {
// All data-dir is beyond data blocks missing, this is dangling
return validMeta, true
}
return validMeta, false
} }
// HealObject - heal the given object, automatically deletes the object if stale/corrupted if `remove` is true. // HealObject - heal the given object, automatically deletes the object if stale/corrupted if `remove` is true.

View File

@ -41,6 +41,10 @@ func TestIsObjectDangling(t *testing.T) {
fi := newFileInfo("test-object", 2, 2) fi := newFileInfo("test-object", 2, 2)
fi.Erasure.Index = 1 fi.Erasure.Index = 1
ifi := newFileInfo("test-object", 2, 2)
ifi.SetInlineData()
ifi.Erasure.Index = 1
testCases := []struct { testCases := []struct {
name string name string
metaArr []FileInfo metaArr []FileInfo
@ -129,13 +133,77 @@ func TestIsObjectDangling(t *testing.T) {
expectedMeta: FileInfo{}, expectedMeta: FileInfo{},
expectedDangling: false, expectedDangling: false,
}, },
{
name: "FileInfoUnDecided-case4",
metaArr: []FileInfo{
{},
{},
{},
ifi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
nil,
},
dataErrs: nil,
expectedMeta: ifi,
expectedDangling: false,
},
{
name: "FileInfoUnDecided-case5-(ignore errFileCorrupt error)",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
nil,
nil,
},
dataErrs: []error{
errFileCorrupt,
errFileNotFound,
nil,
errFileCorrupt,
},
expectedMeta: fi,
expectedDangling: false,
},
{
name: "FileInfoUnDecided-case6-(data-dir intact)",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileNotFound,
errFileNotFound,
nil,
},
dataErrs: []error{
errFileNotFound,
errFileCorrupt,
nil,
nil,
},
expectedMeta: fi,
expectedDangling: false,
},
{ {
name: "FileInfoDecided-case1", name: "FileInfoDecided-case1",
metaArr: []FileInfo{ metaArr: []FileInfo{
{}, {},
{}, {},
{}, {},
fi, ifi,
}, },
errs: []error{ errs: []error{
errFileNotFound, errFileNotFound,
@ -144,25 +212,7 @@ func TestIsObjectDangling(t *testing.T) {
nil, nil,
}, },
dataErrs: nil, dataErrs: nil,
expectedMeta: fi, expectedMeta: ifi,
expectedDangling: true,
},
{
name: "FileInfoDecided-case2",
metaArr: []FileInfo{
{},
{},
{},
fi,
},
errs: []error{
errFileNotFound,
errFileCorrupt,
errFileCorrupt,
nil,
},
dataErrs: nil,
expectedMeta: fi,
expectedDangling: true, expectedDangling: true,
}, },
{ {
@ -175,8 +225,8 @@ func TestIsObjectDangling(t *testing.T) {
}, },
errs: []error{ errs: []error{
errFileNotFound, errFileNotFound,
errFileCorrupt, errFileNotFound,
errFileCorrupt, errFileNotFound,
nil, nil,
}, },
dataErrs: nil, dataErrs: nil,
@ -184,26 +234,26 @@ func TestIsObjectDangling(t *testing.T) {
expectedDangling: true, expectedDangling: true,
}, },
{ {
name: "FileInfoDecided-case2-(duplicate data errors)", name: "FileInfoDecided-case3-(enough data-dir missing)",
metaArr: []FileInfo{ metaArr: []FileInfo{
{}, {},
{}, {},
{}, {},
{Deleted: true}, fi,
}, },
errs: []error{ errs: []error{
errFileNotFound, errFileNotFound,
errFileCorrupt, errFileNotFound,
errFileCorrupt, nil,
nil, nil,
}, },
dataErrs: []error{ dataErrs: []error{
errFileNotFound, errFileNotFound,
errFileCorrupt, errFileNotFound,
nil,
nil, nil,
errFileNotFound,
}, },
expectedMeta: FileInfo{Deleted: true}, expectedMeta: fi,
expectedDangling: true, expectedDangling: true,
}, },
// Add new cases as seen // Add new cases as seen