mirror of
https://github.com/minio/minio.git
synced 2025-04-16 08:58:11 -04:00
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.
This commit is contained in:
parent
1debd722b5
commit
e9ac7b0fb7
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,9 @@ 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
|
||||||
end bool
|
isEmptyDir bool
|
||||||
|
end bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return entries that have prefix prefixEntry.
|
// Return entries that have prefix prefixEntry.
|
||||||
@ -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}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,8 +811,9 @@ func (s *xlStorage) WalkVersions(ctx context.Context, volume, dirPath, marker st
|
|||||||
var fiv FileInfoVersions
|
var fiv FileInfoVersions
|
||||||
if HasSuffix(walkResult.entry, SlashSeparator) {
|
if HasSuffix(walkResult.entry, SlashSeparator) {
|
||||||
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…
x
Reference in New Issue
Block a user