diff --git a/cmd/data-scanner.go b/cmd/data-scanner.go index 092db88b9..9aa01f655 100644 --- a/cmd/data-scanner.go +++ b/cmd/data-scanner.go @@ -1199,17 +1199,15 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", event.Action) } - if event.Action == lifecycle.NoneAction { - return event - } - - if obj.IsLatest && event.Action == lifecycle.DeleteAllVersionsAction { - if lr.LockEnabled && enforceRetentionForDeletion(ctx, obj) { + switch event.Action { + case lifecycle.DeleteAllVersionsAction, lifecycle.DelMarkerDeleteAllVersionsAction: + // Skip if bucket has object locking enabled; To prevent the + // possibility of violating an object retention on one of the + // noncurrent versions of this object. + if lr.LockEnabled { return lifecycle.Event{Action: lifecycle.NoneAction} } - } - switch event.Action { case lifecycle.DeleteVersionAction, lifecycle.DeleteRestoredVersionAction: // Defensive code, should never happen if obj.VersionID == "" { diff --git a/cmd/data-scanner_test.go b/cmd/data-scanner_test.go index a55007a67..325131e49 100644 --- a/cmd/data-scanner_test.go +++ b/cmd/data-scanner_test.go @@ -20,12 +20,15 @@ package cmd import ( "context" "encoding/xml" + "fmt" + "strings" "sync" "testing" "time" "github.com/google/uuid" "github.com/minio/minio/internal/bucket/lifecycle" + "github.com/minio/minio/internal/bucket/object/lock" "github.com/minio/minio/internal/bucket/versioning" ) @@ -141,3 +144,96 @@ func TestApplyNewerNoncurrentVersionsLimit(t *testing.T) { } } } + +func TestEvalActionFromLifecycle(t *testing.T) { + // Tests cover only ExpiredObjectDeleteAllVersions and DelMarkerExpiration actions + obj := ObjectInfo{ + Name: "foo", + ModTime: time.Now().Add(-31 * 24 * time.Hour), + Size: 100 << 20, + VersionID: uuid.New().String(), + IsLatest: true, + NumVersions: 4, + } + delMarker := ObjectInfo{ + Name: "foo-deleted", + ModTime: time.Now().Add(-61 * 24 * time.Hour), + Size: 0, + VersionID: uuid.New().String(), + IsLatest: true, + DeleteMarker: true, + NumVersions: 4, + } + deleteAllILM := ` + + + 30 + true + + + Enabled + DeleteAllVersions + + ` + delMarkerILM := ` + + DelMarkerExpiration + + Enabled + + 60 + + + ` + deleteAllLc, err := lifecycle.ParseLifecycleConfig(strings.NewReader(deleteAllILM)) + if err != nil { + t.Fatalf("Failed to parse deleteAllILM test ILM policy %v", err) + } + delMarkerLc, err := lifecycle.ParseLifecycleConfig(strings.NewReader(delMarkerILM)) + if err != nil { + t.Fatalf("Failed to parse delMarkerILM test ILM policy %v", err) + } + tests := []struct { + ilm lifecycle.Lifecycle + retention lock.Retention + obj ObjectInfo + want lifecycle.Action + }{ + { + // with object locking + ilm: *deleteAllLc, + retention: lock.Retention{LockEnabled: true}, + obj: obj, + want: lifecycle.NoneAction, + }, + { + // without object locking + ilm: *deleteAllLc, + retention: lock.Retention{}, + obj: obj, + want: lifecycle.DeleteAllVersionsAction, + }, + { + // with object locking + ilm: *delMarkerLc, + retention: lock.Retention{LockEnabled: true}, + obj: delMarker, + want: lifecycle.NoneAction, + }, + { + // without object locking + ilm: *delMarkerLc, + retention: lock.Retention{}, + obj: delMarker, + want: lifecycle.DelMarkerDeleteAllVersionsAction, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("TestEvalAction-%d", i), func(t *testing.T) { + if got := evalActionFromLifecycle(context.TODO(), test.ilm, test.retention, nil, test.obj); got.Action != test.want { + t.Fatalf("Expected %v but got %v", test.want, got) + } + }) + } +}