mirror of
https://github.com/minio/minio.git
synced 2025-04-21 11:04:20 -04:00
Fix DeleteObject when only free versions remain (#16289)
This commit is contained in:
parent
34167c51d5
commit
2fa35def2c
@ -997,7 +997,8 @@ func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi O
|
|||||||
|
|
||||||
// Remove this free version
|
// Remove this free version
|
||||||
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
|
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
|
InclFreeVersions: true,
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
|
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
|
||||||
|
@ -93,7 +93,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
|||||||
if srcOpts.VersionID != "" {
|
if srcOpts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
|
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true)
|
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@ -501,7 +501,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
|||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData bool) ([]FileInfo, []error) {
|
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers bool) ([]FileInfo, []error) {
|
||||||
metadataArray := make([]*xlMetaV2, len(disks))
|
metadataArray := make([]*xlMetaV2, len(disks))
|
||||||
metaFileInfos := make([]FileInfo, len(metadataArray))
|
metaFileInfos := make([]FileInfo, len(metadataArray))
|
||||||
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
|
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
|
||||||
@ -566,7 +566,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
|||||||
|
|
||||||
readQuorum := (len(disks) + 1) / 2
|
readQuorum := (len(disks) + 1) / 2
|
||||||
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
|
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
|
||||||
lfi, err := meta.ToFileInfo(bucket, object, "")
|
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
if errs[i] == nil {
|
if errs[i] == nil {
|
||||||
@ -596,7 +596,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
|||||||
|
|
||||||
// make sure to preserve this for diskmtime based healing bugfix.
|
// make sure to preserve this for diskmtime based healing bugfix.
|
||||||
diskMTime := metaFileInfos[index].DiskMTime
|
diskMTime := metaFileInfos[index].DiskMTime
|
||||||
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID)
|
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers)
|
||||||
if errs[index] != nil {
|
if errs[index] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -620,7 +620,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@ -1714,7 +1714,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
@ -1787,7 +1787,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
|||||||
if opts.VersionID != "" {
|
if opts.VersionID != "" {
|
||||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
|
||||||
} else {
|
} else {
|
||||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false)
|
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||||
|
@ -211,7 +211,7 @@ func (e *metaCacheEntry) fileInfo(bucket string) (FileInfo, error) {
|
|||||||
ModTime: timeSentinel1970,
|
ModTime: timeSentinel1970,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return e.cached.ToFileInfo(bucket, e.name, "")
|
return e.cached.ToFileInfo(bucket, e.name, "", false)
|
||||||
}
|
}
|
||||||
return getFileInfo(e.metadata, bucket, e.name, "", false)
|
return getFileInfo(e.metadata, bucket, e.name, "", false)
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,8 @@ type ObjectOptions struct {
|
|||||||
// IndexCB will return any index created but the compression.
|
// IndexCB will return any index created but the compression.
|
||||||
// Object must have been read at this point.
|
// Object must have been read at this point.
|
||||||
IndexCB func() []byte
|
IndexCB func() []byte
|
||||||
|
|
||||||
|
InclFreeVersions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpirationOptions represents object options for object expiration at objectLayer.
|
// ExpirationOptions represents object options for object expiration at objectLayer.
|
||||||
|
@ -122,7 +122,7 @@ func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, data bool) (F
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
inData = xlMeta.data
|
inData = xlMeta.data
|
||||||
fi, err = xlMeta.ToFileInfo(volume, path, versionID)
|
fi, err = xlMeta.ToFileInfo(volume, path, versionID, false)
|
||||||
}
|
}
|
||||||
if !data || err != nil {
|
if !data || err != nil {
|
||||||
return fi, err
|
return fi, err
|
||||||
|
@ -1700,7 +1700,7 @@ func (x *xlMetaV2) AddLegacy(m *xlMetaV1Object) error {
|
|||||||
|
|
||||||
// ToFileInfo converts xlMetaV2 into a common FileInfo datastructure
|
// ToFileInfo converts xlMetaV2 into a common FileInfo datastructure
|
||||||
// for consumption across callers.
|
// for consumption across callers.
|
||||||
func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err error) {
|
func (x xlMetaV2) ToFileInfo(volume, path, versionID string, inclFreeVers bool) (fi FileInfo, err error) {
|
||||||
var uv uuid.UUID
|
var uv uuid.UUID
|
||||||
if versionID != "" && versionID != nullVersionID {
|
if versionID != "" && versionID != nullVersionID {
|
||||||
uv, err = uuid.Parse(versionID)
|
uv, err = uuid.Parse(versionID)
|
||||||
@ -1712,12 +1712,29 @@ func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err e
|
|||||||
var succModTime int64
|
var succModTime int64
|
||||||
isLatest := true
|
isLatest := true
|
||||||
nonFreeVersions := len(x.versions)
|
nonFreeVersions := len(x.versions)
|
||||||
|
|
||||||
|
var (
|
||||||
|
freeFi FileInfo
|
||||||
|
freeFound bool
|
||||||
|
)
|
||||||
found := false
|
found := false
|
||||||
for _, ver := range x.versions {
|
for _, ver := range x.versions {
|
||||||
header := &ver.header
|
header := &ver.header
|
||||||
// skip listing free-version unless explicitly requested via versionID
|
// skip listing free-version unless explicitly requested via versionID
|
||||||
if header.FreeVersion() {
|
if header.FreeVersion() {
|
||||||
nonFreeVersions--
|
nonFreeVersions--
|
||||||
|
// remember the latest free version; will return this FileInfo if no non-free version remain
|
||||||
|
var freeVersion xlMetaV2Version
|
||||||
|
if inclFreeVers && !freeFound {
|
||||||
|
// ignore unmarshalling errors, will return errFileNotFound in that case
|
||||||
|
if _, err := freeVersion.unmarshalV(x.metaV, ver.meta); err == nil {
|
||||||
|
if freeFi, err = freeVersion.ToFileInfo(volume, path); err == nil {
|
||||||
|
freeFi.IsLatest = true // when this is returned, it would be the latest free version remaining.
|
||||||
|
freeFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if header.VersionID != uv {
|
if header.VersionID != uv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1749,6 +1766,11 @@ func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err e
|
|||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
if versionID == "" {
|
if versionID == "" {
|
||||||
|
if inclFreeVers && nonFreeVersions == 0 {
|
||||||
|
if freeFound {
|
||||||
|
return freeFi, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return FileInfo{}, errFileNotFound
|
return FileInfo{}, errFileNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +485,7 @@ func BenchmarkXlMetaV2Shallow(b *testing.B) {
|
|||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
// List...
|
// List...
|
||||||
_, err = xl.ToFileInfo("volume", "path", ids[rng.Intn(size)])
|
_, err = xl.ToFileInfo("volume", "path", ids[rng.Intn(size)], false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -128,6 +129,11 @@ func TestFreeVersion(t *testing.T) {
|
|||||||
report()
|
report()
|
||||||
fatalErr(err)
|
fatalErr(err)
|
||||||
|
|
||||||
|
// At this point the version stack must look as below,
|
||||||
|
// v3 --> free version 00000000-0000-0000-0000-0000000000f2 (from removal of null version)
|
||||||
|
// v2 --> free version 00000000-0000-0000-0000-0000000000f1 (from overwriting of null version )
|
||||||
|
// v1 --> non-free version 00000000-0000-0000-0000-000000000001
|
||||||
|
|
||||||
// Check number of free-versions
|
// Check number of free-versions
|
||||||
freeVersions, err := xl.listFreeVersions(newtierfi.Volume, newtierfi.Name)
|
freeVersions, err := xl.listFreeVersions(newtierfi.Volume, newtierfi.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -137,6 +143,57 @@ func TestFreeVersion(t *testing.T) {
|
|||||||
t.Fatalf("Expected two free versions but got %d", len(freeVersions))
|
t.Fatalf("Expected two free versions but got %d", len(freeVersions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
freeVersionsTests := []struct {
|
||||||
|
vol string
|
||||||
|
name string
|
||||||
|
inclFreeVers bool
|
||||||
|
afterFn func(fi FileInfo) (string, error)
|
||||||
|
expectedFree bool
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
// ToFileInfo with 'inclFreeVers = true' should return the latest
|
||||||
|
// non-free version if one is present
|
||||||
|
{
|
||||||
|
vol: newtierfi.Volume,
|
||||||
|
name: newtierfi.Name,
|
||||||
|
inclFreeVers: true,
|
||||||
|
afterFn: xl.DeleteVersion,
|
||||||
|
expectedFree: false,
|
||||||
|
},
|
||||||
|
// ToFileInfo with 'inclFreeVers = true' must return the latest free
|
||||||
|
// version when no non-free versions are present.
|
||||||
|
{
|
||||||
|
vol: newtierfi.Volume,
|
||||||
|
name: newtierfi.Name,
|
||||||
|
inclFreeVers: true,
|
||||||
|
expectedFree: true,
|
||||||
|
},
|
||||||
|
// ToFileInfo with 'inclFreeVers = false' must return errFileNotFound
|
||||||
|
// when no non-free version exist.
|
||||||
|
{
|
||||||
|
vol: newtierfi.Volume,
|
||||||
|
name: newtierfi.Name,
|
||||||
|
inclFreeVers: false,
|
||||||
|
expectedErr: errFileNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ft := range freeVersionsTests {
|
||||||
|
fi, err := xl.ToFileInfo(ft.vol, ft.name, "", ft.inclFreeVers)
|
||||||
|
if err != nil && !errors.Is(err, ft.expectedErr) {
|
||||||
|
t.Fatalf("ToFileInfo failed due to %v", err)
|
||||||
|
}
|
||||||
|
if got := fi.TierFreeVersion(); got != ft.expectedFree {
|
||||||
|
t.Fatalf("Expected free-version=%v but got free-version=%v", ft.expectedFree, got)
|
||||||
|
}
|
||||||
|
if ft.afterFn != nil {
|
||||||
|
_, err = ft.afterFn(fi)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ft.afterFn failed with err %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simulate scanner removing free-version
|
// Simulate scanner removing free-version
|
||||||
freefi := newtierfi
|
freefi := newtierfi
|
||||||
for _, fvID := range fvIDs {
|
for _, fvID := range fvIDs {
|
||||||
|
@ -2335,7 +2335,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the data of null version or any other existing version-id
|
// Replace the data of null version or any other existing version-id
|
||||||
ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, reqVID)
|
ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, reqVID, false)
|
||||||
if err == nil && !ofi.Deleted {
|
if err == nil && !ofi.Deleted {
|
||||||
if xlMeta.SharedDataDirCountStr(reqVID, ofi.DataDir) == 0 {
|
if xlMeta.SharedDataDirCountStr(reqVID, ofi.DataDir) == 0 {
|
||||||
// Purge the destination path as we are not preserving anything
|
// Purge the destination path as we are not preserving anything
|
||||||
|
Loading…
x
Reference in New Issue
Block a user