allow expiration of all versions during Listing() (#16757)

This commit is contained in:
Harshavardhana 2023-03-09 15:15:30 -08:00 committed by GitHub
parent 18f9cccfa7
commit b984bf8d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 93 deletions

View File

@ -30,15 +30,15 @@ getdeps: ## fetch necessary dependencies
crosscompile: ## cross compile minio crosscompile: ## cross compile minio
@(env bash $(PWD)/buildscripts/cross-compile.sh) @(env bash $(PWD)/buildscripts/cross-compile.sh)
verifiers: getdeps lint check-gen verifiers: lint check-gen
check-gen: ## check for updated autogenerated files check-gen: ## check for updated autogenerated files
@go generate ./... >/dev/null @go generate ./... >/dev/null
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false) @(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
lint: ## runs golangci-lint suite of linters lint: getdeps ## runs golangci-lint suite of linters
@echo "Running $@ check" @echo "Running $@ check"
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml @$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
check: test check: test
test: verifiers build ## builds minio, runs linters, tests test: verifiers build ## builds minio, runs linters, tests

View File

@ -32,7 +32,6 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/madmin-go/v2" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/console" "github.com/minio/pkg/console"
@ -735,17 +734,15 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
} }
versioned := vc != nil && vc.Versioned(object) versioned := vc != nil && vc.Versioned(object)
objInfo := fi.ToObjectInfo(bucket, object, versioned) objInfo := fi.ToObjectInfo(bucket, object, versioned)
evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo) evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
switch evt.Action { if evt.Action.Delete() {
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: globalExpiryState.enqueueByDays(objInfo, evt.Action.DeleteRestored(), evt.Action.DeleteVersioned())
globalExpiryState.enqueueByDays(objInfo, false, evt.Action == lifecycle.DeleteVersionAction) if !evt.Action.DeleteRestored() {
// Skip this entry. return true
return true }
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
globalExpiryState.enqueueByDays(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
// Skip this entry.
return true
} }
return false return false
} }

View File

@ -32,7 +32,6 @@ import (
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/minio/madmin-go/v2" "github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env" "github.com/minio/pkg/env"
@ -463,17 +462,13 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
} }
versioned := vc != nil && vc.Versioned(object) versioned := vc != nil && vc.Versioned(object)
objInfo := fi.ToObjectInfo(bucket, object, versioned) objInfo := fi.ToObjectInfo(bucket, object, versioned)
event := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
switch action := event.Action; action { evt := evalActionFromLifecycle(ctx, *lc, lr, objInfo)
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: if evt.Action.Delete() {
globalExpiryState.enqueueByDays(objInfo, false, action == lifecycle.DeleteVersionAction) globalExpiryState.enqueueByDays(objInfo, evt.Action.DeleteRestored(), evt.Action.DeleteVersioned())
// Skip this entry.
return true
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
globalExpiryState.enqueueByDays(objInfo, true, action == lifecycle.DeleteRestoredVersionAction)
// Skip this entry.
return true return true
} }
return false return false
} }

View File

@ -37,7 +37,6 @@ import (
"github.com/minio/minio-go/v7/pkg/s3utils" "github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags" "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/sync/errgroup" "github.com/minio/minio/internal/sync/errgroup"
@ -1305,11 +1304,12 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
if err == nil { if err == nil {
if opts.Lifecycle != nil { if opts.Lifecycle != nil {
evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo) evt := evalActionFromLifecycle(ctx, *opts.Lifecycle, opts.Retention, objInfo)
switch evt.Action { if evt.Action.Delete() {
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: globalExpiryState.enqueueByDays(objInfo, evt.Action.DeleteRestored(), evt.Action.DeleteVersioned())
fallthrough if !evt.Action.DeleteRestored() {
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: // Skip entry if ILM action was DeleteVersionAction or DeleteAction
return loi, nil return loi, nil
}
} }
} }
loi.Objects = append(loi.Objects, objInfo) loi.Objects = append(loi.Objects, objInfo)

View File

@ -28,7 +28,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
) )
@ -341,7 +340,6 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCacheEntry, out chan<- metaCacheEntry) { func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCacheEntry, out chan<- metaCacheEntry) {
defer close(out) defer close(out)
vcfg, _ := globalBucketVersioningSys.Get(o.Bucket)
for { for {
var obj metaCacheEntry var obj metaCacheEntry
var ok bool var ok bool
@ -354,37 +352,61 @@ func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCa
} }
} }
fi, err := obj.fileInfo(o.Bucket) var skip bool
versioned := o.Versioning != nil && o.Versioning.Versioned(obj.name)
// skip latest object from listing only for regular
// listObjects calls, versioned based listing cannot
// filter out between versions 'obj' cannot be truncated
// in such a manner, so look for skipping an object only
// for regular ListObjects() call only.
if !o.Versioned {
fi, err := obj.fileInfo(o.Bucket)
if err != nil {
continue
}
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
if o.Lifecycle != nil {
act := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo).Action
skip = act.Delete()
if act.DeleteRestored() {
// do not skip DeleteRestored* actions
skip = false
}
}
}
// Skip entry only if needed via ILM, skipping is never true for versioned listing.
if !skip {
select {
case <-ctx.Done():
return
case out <- obj:
}
}
fiv, err := obj.fileInfoVersions(o.Bucket)
if err != nil { if err != nil {
continue continue
} }
versioned := vcfg != nil && vcfg.Versioned(obj.name) // Expire all versions if needed, if not attempt to queue for replication.
for _, version := range fiv.Versions {
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned) if o.Lifecycle != nil {
if o.Lifecycle != nil { evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo)
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, objInfo) if evt.Action.Delete() {
switch evt.Action { globalExpiryState.enqueueByDays(objInfo, evt.Action.DeleteRestored(), evt.Action.DeleteVersioned())
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: if !evt.Action.DeleteRestored() {
globalExpiryState.enqueueByDays(objInfo, false, evt.Action == lifecycle.DeleteVersionAction) continue
// Skip this entry. } // queue version for replication upon expired restored copies if needed.
continue
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction:
globalExpiryState.enqueueByDays(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction)
// Skip this entry.
continue
}
}
select {
case <-ctx.Done():
return
case out <- obj:
if fiv, err := obj.fileInfoVersions(o.Bucket); err == nil {
for _, version := range fiv.Versions {
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication)
} }
} }
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication)
} }
} }
} }

View File

@ -34,6 +34,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/bucket/object/lock" "github.com/minio/minio/internal/bucket/object/lock"
"github.com/minio/minio/internal/bucket/versioning"
"github.com/minio/minio/internal/color" "github.com/minio/minio/internal/color"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
@ -95,6 +96,10 @@ type listPathOptions struct {
// Versioned is this a ListObjectVersions call. // Versioned is this a ListObjectVersions call.
Versioned bool Versioned bool
// Versioning config is used for if the path
// has versioning enabled.
Versioning *versioning.Versioning
// Lifecycle performs filtering based on lifecycle. // Lifecycle performs filtering based on lifecycle.
// This will filter out objects if the most recent version should be deleted by lifecycle. // This will filter out objects if the most recent version should be deleted by lifecycle.
// Is not transferred across request calls. // Is not transferred across request calls.
@ -119,12 +124,14 @@ func init() {
func (o *listPathOptions) setBucketMeta(ctx context.Context) { func (o *listPathOptions) setBucketMeta(ctx context.Context) {
lc, _ := globalLifecycleSys.Get(o.Bucket) lc, _ := globalLifecycleSys.Get(o.Bucket)
vc, _ := globalBucketVersioningSys.Get(o.Bucket)
// Check if bucket is object locked. // Check if bucket is object locked.
rcfg, _ := globalBucketObjectLockSys.Get(o.Bucket) rcfg, _ := globalBucketObjectLockSys.Get(o.Bucket)
replCfg, _, _ := globalBucketMetadataSys.GetReplicationConfig(ctx, o.Bucket) replCfg, _, _ := globalBucketMetadataSys.GetReplicationConfig(ctx, o.Bucket)
tgts, _ := globalBucketTargetSys.ListBucketTargets(ctx, o.Bucket) tgts, _ := globalBucketTargetSys.ListBucketTargets(ctx, o.Bucket)
o.Lifecycle = lc o.Lifecycle = lc
o.Versioning = vc
o.Replication = replicationConfig{ o.Replication = replicationConfig{
Config: replCfg, Config: replCfg,
remotes: tgts, remotes: tgts,

View File

@ -472,22 +472,18 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
} }
if !proxy.Proxy { // apply lifecycle rules only for local requests if !proxy.Proxy { // apply lifecycle rules only for local requests
// Automatically remove the object/version is an expiry lifecycle rule can be applied // Automatically remove the object/version if an expiry lifecycle rule can be applied
if lc, err := globalLifecycleSys.Get(bucket); err == nil { if lc, err := globalLifecycleSys.Get(bucket); err == nil {
rcfg, _ := globalBucketObjectLockSys.Get(bucket) rcfg, _ := globalBucketObjectLockSys.Get(bucket)
evt := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo) act := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo).Action
var success bool if act.Delete() {
switch evt.Action { // apply whatever the expiry rule is.
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: applyExpiryRule(objInfo, act.DeleteRestored(), act.DeleteVersioned())
success = applyExpiryRule(objInfo, false, evt.Action == lifecycle.DeleteVersionAction) if !act.DeleteRestored() {
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: // If the ILM action is not on restored object return error.
// Restored object delete would be still allowed to proceed as success writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
// since transition behavior is slightly different. return
applyExpiryRule(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction) }
}
if success {
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
return
} }
} }
@ -729,22 +725,18 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
} }
if !proxy.Proxy { // apply lifecycle rules only locally not for proxied requests if !proxy.Proxy { // apply lifecycle rules only locally not for proxied requests
// Automatically remove the object/version is an expiry lifecycle rule can be applied // Automatically remove the object/version if an expiry lifecycle rule can be applied
if lc, err := globalLifecycleSys.Get(bucket); err == nil { if lc, err := globalLifecycleSys.Get(bucket); err == nil {
rcfg, _ := globalBucketObjectLockSys.Get(bucket) rcfg, _ := globalBucketObjectLockSys.Get(bucket)
evt := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo) act := evalActionFromLifecycle(ctx, *lc, rcfg, objInfo).Action
var success bool if act.Delete() {
switch evt.Action { // apply whatever the expiry rule is.
case lifecycle.DeleteVersionAction, lifecycle.DeleteAction: applyExpiryRule(objInfo, act.DeleteRestored(), act.DeleteVersioned())
success = applyExpiryRule(objInfo, false, evt.Action == lifecycle.DeleteVersionAction) if !act.DeleteRestored() {
case lifecycle.DeleteRestoredAction, lifecycle.DeleteRestoredVersionAction: // If the ILM action is not on restored object return error.
// Restored object delete would be still allowed to proceed as success writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
// since transition behavior is slightly different. return
applyExpiryRule(objInfo, true, evt.Action == lifecycle.DeleteRestoredVersionAction) }
}
if success {
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchKey))
return
} }
} }
QueueReplicationHeal(ctx, bucket, objInfo) QueueReplicationHeal(ctx, bucket, objInfo)

View File

@ -1285,11 +1285,9 @@ func (s *xlStorage) renameLegacyMetadata(volumeDir, path string) (err error) {
// Renaming xl.json to xl.meta should be fully synced to disk. // Renaming xl.json to xl.meta should be fully synced to disk.
defer func() { defer func() {
if err == nil { if err == nil && s.globalSync {
if s.globalSync { // Sync to disk only upon success.
// Sync to disk only upon success. globalSync()
globalSync()
}
} }
}() }()
@ -2140,17 +2138,22 @@ func skipAccessChecks(volume string) (ok bool) {
// RenameData - rename source path to destination path atomically, metadata and data directory. // RenameData - rename source path to destination path atomically, metadata and data directory.
func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (sign uint64, err error) { func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (sign uint64, err error) {
defer func() { defer func() {
if err != nil && !contextCanceled(ctx) && !errors.Is(err, errFileNotFound) { ignoredErrs := []error{
errFileNotFound,
errVolumeNotFound,
errFileVersionNotFound,
errDiskNotFound,
errUnformattedDisk,
}
if err != nil && !IsErr(err, ignoredErrs...) && !contextCanceled(ctx) {
// Only log these errors if context is not yet canceled. // Only log these errors if context is not yet canceled.
logger.LogIf(ctx, fmt.Errorf("srcVolume: %s, srcPath: %s, dstVolume: %s:, dstPath: %s - error %v", logger.LogIf(ctx, fmt.Errorf("srcVolume: %s, srcPath: %s, dstVolume: %s:, dstPath: %s - error %v",
srcVolume, srcPath, srcVolume, srcPath,
dstVolume, dstPath, dstVolume, dstPath,
err)) err))
} }
if err == nil { if err == nil && s.globalSync {
if s.globalSync { globalSync()
globalSync()
}
} }
}() }()

View File

@ -70,6 +70,24 @@ const (
ActionCount ActionCount
) )
// DeleteRestored - Returns true if action demands delete on restored objects
func (a Action) DeleteRestored() bool {
return a == DeleteRestoredAction || a == DeleteRestoredVersionAction
}
// DeleteVersioned - Returns true if action demands delete on a versioned object
func (a Action) DeleteVersioned() bool {
return a == DeleteVersionAction || a == DeleteRestoredVersionAction
}
// Delete - Returns true if action demands delete on all objects (including restored)
func (a Action) Delete() bool {
if a.DeleteRestored() {
return true
}
return a == DeleteVersionAction || a == DeleteAction
}
// Lifecycle - Configuration for bucket lifecycle. // Lifecycle - Configuration for bucket lifecycle.
type Lifecycle struct { type Lifecycle struct {
XMLName xml.Name `xml:"LifecycleConfiguration"` XMLName xml.Name `xml:"LifecycleConfiguration"`