heal: Remove empty directories (#11354)

Since the introduction of __XLDIR__, an empty directory does not have a
meaning anymore in erasure mode. Make healing removes it wherever it
finds it.
master
Anis Elleuch 4 years ago committed by GitHub
parent 1debd722b5
commit e9ac7b0fb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      cmd/erasure-object.go
  2. 8
      cmd/erasure-server-pool.go
  3. 2
      cmd/storage-datatypes.go
  4. 35
      cmd/storage-datatypes_gen.go
  5. 3
      cmd/tree-walk.go
  6. 1
      cmd/xl-storage.go

@ -786,6 +786,31 @@ func (er erasureObjects) deleteObjectVersion(ctx context.Context, bucket, object
return reduceWriteQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, writeQuorum) return reduceWriteQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, writeQuorum)
} }
// deleteEmptyDir knows only how to remove an empty directory (not the empty object with a
// trailing slash), this is called for the healing code to remove such directories.
func (er erasureObjects) deleteEmptyDir(ctx context.Context, bucket, object string) error {
defer ObjectPathUpdated(pathJoin(bucket, object))
if bucket == minioMetaTmpBucket {
return nil
}
disks := er.getDisks()
g := errgroup.WithNErrs(len(disks))
for index := range disks {
index := index
g.Go(func() error {
if disks[index] == nil {
return errDiskNotFound
}
return disks[index].Delete(ctx, bucket, object, false)
}, index)
}
// return errors if any during deletion
return reduceWriteQuorumErrs(ctx, g.Wait(), objectOpIgnoredErrs, len(disks)/2+1)
}
// deleteObject - wrapper for delete object, deletes an object from // deleteObject - wrapper for delete object, deletes an object from
// all the disks in parallel, including `xl.meta` associated with the // all the disks in parallel, including `xl.meta` associated with the
// object. // object.

@ -1359,6 +1359,14 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
break break
} }
// Remove empty directories if found - they have no meaning.
// Can be left over from highly concurrent put/remove.
if quorumCount > set.setDriveCount/2 && entry.IsEmptyDir {
if !opts.DryRun && opts.Remove {
set.deleteEmptyDir(ctx, bucket, entry.Name)
}
}
// Indicate that first attempt was a success and subsequent loop // Indicate that first attempt was a success and subsequent loop
// knows that its not our first attempt at 'prefix' // knows that its not our first attempt at 'prefix'
err = nil err = nil

@ -76,6 +76,8 @@ type FileInfoVersions struct {
// Name of the file. // Name of the file.
Name string Name string
IsEmptyDir bool
// Represents the latest mod time of the // Represents the latest mod time of the
// latest version. // latest version.
LatestModTime time.Time LatestModTime time.Time

@ -737,6 +737,12 @@ func (z *FileInfoVersions) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Name") err = msgp.WrapError(err, "Name")
return return
} }
case "IsEmptyDir":
z.IsEmptyDir, err = dc.ReadBool()
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
case "LatestModTime": case "LatestModTime":
z.LatestModTime, err = dc.ReadTime() z.LatestModTime, err = dc.ReadTime()
if err != nil { if err != nil {
@ -775,9 +781,9 @@ func (z *FileInfoVersions) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) { func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 4 // map header, size 5
// write "Volume" // write "Volume"
err = en.Append(0x84, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65) err = en.Append(0x85, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65)
if err != nil { if err != nil {
return return
} }
@ -796,6 +802,16 @@ func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Name") err = msgp.WrapError(err, "Name")
return return
} }
// write "IsEmptyDir"
err = en.Append(0xaa, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x44, 0x69, 0x72)
if err != nil {
return
}
err = en.WriteBool(z.IsEmptyDir)
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
// write "LatestModTime" // write "LatestModTime"
err = en.Append(0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65) err = en.Append(0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65)
if err != nil { if err != nil {
@ -829,13 +845,16 @@ func (z *FileInfoVersions) EncodeMsg(en *msgp.Writer) (err error) {
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *FileInfoVersions) MarshalMsg(b []byte) (o []byte, err error) { func (z *FileInfoVersions) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 4 // map header, size 5
// string "Volume" // string "Volume"
o = append(o, 0x84, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65) o = append(o, 0x85, 0xa6, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65)
o = msgp.AppendString(o, z.Volume) o = msgp.AppendString(o, z.Volume)
// string "Name" // string "Name"
o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65) o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.Name)
// string "IsEmptyDir"
o = append(o, 0xaa, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x44, 0x69, 0x72)
o = msgp.AppendBool(o, z.IsEmptyDir)
// string "LatestModTime" // string "LatestModTime"
o = append(o, 0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65) o = append(o, 0xad, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65)
o = msgp.AppendTime(o, z.LatestModTime) o = msgp.AppendTime(o, z.LatestModTime)
@ -882,6 +901,12 @@ func (z *FileInfoVersions) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Name") err = msgp.WrapError(err, "Name")
return return
} }
case "IsEmptyDir":
z.IsEmptyDir, bts, err = msgp.ReadBoolBytes(bts)
if err != nil {
err = msgp.WrapError(err, "IsEmptyDir")
return
}
case "LatestModTime": case "LatestModTime":
z.LatestModTime, bts, err = msgp.ReadTimeBytes(bts) z.LatestModTime, bts, err = msgp.ReadTimeBytes(bts)
if err != nil { if err != nil {
@ -921,7 +946,7 @@ func (z *FileInfoVersions) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *FileInfoVersions) Msgsize() (s int) { func (z *FileInfoVersions) Msgsize() (s int) {
s = 1 + 7 + msgp.StringPrefixSize + len(z.Volume) + 5 + msgp.StringPrefixSize + len(z.Name) + 14 + msgp.TimeSize + 9 + msgp.ArrayHeaderSize s = 1 + 7 + msgp.StringPrefixSize + len(z.Volume) + 5 + msgp.StringPrefixSize + len(z.Name) + 11 + msgp.BoolSize + 14 + msgp.TimeSize + 9 + msgp.ArrayHeaderSize
for za0001 := range z.Versions { for za0001 := range z.Versions {
s += z.Versions[za0001].Msgsize() s += z.Versions[za0001].Msgsize()
} }

@ -25,6 +25,7 @@ import (
// TreeWalkResult - Tree walk result carries results of tree walking. // TreeWalkResult - Tree walk result carries results of tree walking.
type TreeWalkResult struct { type TreeWalkResult struct {
entry string entry string
isEmptyDir bool
end bool end bool
} }
@ -254,7 +255,7 @@ func doTreeWalk(ctx context.Context, bucket, prefixDir, entryPrefixMatch, marker
select { select {
case <-endWalkCh: case <-endWalkCh:
return false, errWalkAbort return false, errWalkAbort
case resultCh <- TreeWalkResult{entry: pathJoin(prefixDir, entry), end: isEOF}: case resultCh <- TreeWalkResult{entry: pathJoin(prefixDir, entry), isEmptyDir: leafDir, end: isEOF}:
} }
} }

@ -813,6 +813,7 @@ func (s *xlStorage) WalkVersions(ctx context.Context, volume, dirPath, marker st
fiv = FileInfoVersions{ fiv = FileInfoVersions{
Volume: volume, Volume: volume,
Name: walkResult.entry, Name: walkResult.entry,
IsEmptyDir: walkResult.isEmptyDir,
Versions: []FileInfo{ Versions: []FileInfo{
{ {
Volume: volume, Volume: volume,

Loading…
Cancel
Save