pkg/lifecycle: Add SetPredictionHeaders method (#12755)

This method is used to add expected expiration and transition time 
for an object in GET/HEAD Object response headers.

Also fixed bugs in lifecycle.PredictTransitionTime and
getLifecycleTransitionTier in handling current and 
non-current versions.
This commit is contained in:
Krishnan Parthasarathi
2021-07-20 17:36:55 -07:00
committed by GitHub
parent 6ea083d197
commit d0963974a5
8 changed files with 204 additions and 59 deletions

View File

@@ -28,7 +28,6 @@ import (
"strings"
"time"
"github.com/minio/minio/internal/bucket/lifecycle"
"github.com/minio/minio/internal/crypto"
xhttp "github.com/minio/minio/internal/http"
)
@@ -186,42 +185,12 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
}
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
if opts.VersionID == "" {
if ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
Name: objInfo.Name,
UserTags: objInfo.UserTags,
VersionID: objInfo.VersionID,
ModTime: objInfo.ModTime,
IsLatest: objInfo.IsLatest,
DeleteMarker: objInfo.DeleteMarker,
SuccessorModTime: objInfo.SuccessorModTime,
}); !expiryTime.IsZero() {
w.Header()[xhttp.AmzExpiration] = []string{
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
}
}
}
if objInfo.IsRemote() {
// Check if object is being restored. For more information on x-amz-restore header see
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionTier}
}
ruleID, transitionTime := lc.PredictTransitionTime(lifecycle.ObjectOpts{
Name: objInfo.Name,
UserTags: objInfo.UserTags,
VersionID: objInfo.VersionID,
ModTime: objInfo.ModTime,
IsLatest: objInfo.IsLatest,
DeleteMarker: objInfo.DeleteMarker,
TransitionStatus: objInfo.TransitionStatus,
})
if !transitionTime.IsZero() {
// This header is a MinIO centric extension to show expected transition date in a similar spirit as x-amz-expiration
w.Header()[xhttp.MinIOTransition] = []string{
fmt.Sprintf(`transition-date="%s", rule-id="%s"`, transitionTime.Format(http.TimeFormat), ruleID),
}
}
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
}
return nil

View File

@@ -309,9 +309,12 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
// getLifeCycleTransitionTier returns storage class for transition target
func getLifeCycleTransitionTier(ctx context.Context, lc *lifecycle.Lifecycle, bucket string, obj lifecycle.ObjectOpts) string {
for _, rule := range lc.FilterActionableRules(obj) {
if rule.Transition.StorageClass != "" {
if obj.IsLatest && rule.Transition.StorageClass != "" {
return rule.Transition.StorageClass
}
if !obj.IsLatest && rule.NoncurrentVersionTransition.StorageClass != "" {
return rule.NoncurrentVersionTransition.StorageClass
}
}
return ""
}
@@ -673,3 +676,21 @@ func isRestoredObjectOnDisk(meta map[string]string) (onDisk bool) {
}
return onDisk
}
// ToLifecycleOpts returns lifecycle.ObjectOpts value for oi.
func (oi ObjectInfo) ToLifecycleOpts() lifecycle.ObjectOpts {
return lifecycle.ObjectOpts{
Name: oi.Name,
UserTags: oi.UserTags,
VersionID: oi.VersionID,
ModTime: oi.ModTime,
IsLatest: oi.IsLatest,
NumVersions: oi.NumVersions,
DeleteMarker: oi.DeleteMarker,
SuccessorModTime: oi.SuccessorModTime,
RestoreOngoing: oi.RestoreOngoing,
RestoreExpires: oi.RestoreExpires,
TransitionStatus: oi.TransitionStatus,
RemoteTiersImmediately: globalDebugRemoteTiersImmediately,
}
}

View File

@@ -246,3 +246,40 @@ func TestValidateTransitionTier(t *testing.T) {
}
}
}
func TestGetLifecycleTransitionTier(t *testing.T) {
lc := lifecycle.Lifecycle{
Rules: []lifecycle.Rule{
{
ID: "rule-1",
Status: "Enabled",
Transition: lifecycle.Transition{
Days: lifecycle.TransitionDays(3),
StorageClass: "TIER-1",
},
},
{
ID: "rule-2",
Status: "Enabled",
NoncurrentVersionTransition: lifecycle.NoncurrentVersionTransition{
NoncurrentDays: lifecycle.ExpirationDays(3),
StorageClass: "TIER-2",
},
},
},
}
obj1 := lifecycle.ObjectOpts{
Name: "obj1",
IsLatest: true,
}
obj2 := lifecycle.ObjectOpts{
Name: "obj2",
}
if got := getLifeCycleTransitionTier(context.TODO(), &lc, "bucket", obj1); got != "TIER-1" {
t.Fatalf("Expected TIER-1 but got %s", got)
}
if got := getLifeCycleTransitionTier(context.TODO(), &lc, "bucket", obj2); got != "TIER-2" {
t.Fatalf("Expected TIER-2 but got %s", got)
}
}

View File

@@ -19,13 +19,11 @@ package cmd
import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"time"
"github.com/minio/minio/internal/bucket/lifecycle"
xhttp "github.com/minio/minio/internal/http"
)
@@ -262,19 +260,7 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool) {
if objInfo.Bucket != "" && objInfo.Name != "" {
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete {
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
Name: objInfo.Name,
UserTags: objInfo.UserTags,
VersionID: objInfo.VersionID,
ModTime: objInfo.ModTime,
IsLatest: objInfo.IsLatest,
DeleteMarker: objInfo.DeleteMarker,
})
if !expiryTime.IsZero() {
w.Header()[xhttp.AmzExpiration] = []string{
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
}
}
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
}
}
}