| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/xml" | 
					
						
							| 
									
										
										
										
											2021-05-20 09:51:23 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-04-17 05:09:25 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | 	"sync/atomic" | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	"github.com/google/uuid" | 
					
						
							| 
									
										
										
										
											2023-06-20 08:53:08 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v3" | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	"github.com/minio/minio-go/v7/pkg/tags" | 
					
						
							| 
									
										
										
										
											2022-09-07 22:24:54 +08:00
										 |  |  | 	"github.com/minio/minio/internal/amztime" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	sse "github.com/minio/minio/internal/bucket/encryption" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/bucket/lifecycle" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/event" | 
					
						
							|  |  |  | 	xhttp "github.com/minio/minio/internal/http" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							|  |  |  | 	"github.com/minio/minio/internal/s3select" | 
					
						
							| 
									
										
										
										
											2023-09-05 03:57:37 +08:00
										 |  |  | 	"github.com/minio/pkg/v2/env" | 
					
						
							| 
									
										
										
										
											2023-11-27 14:18:09 +08:00
										 |  |  | 	xnet "github.com/minio/pkg/v2/net" | 
					
						
							| 
									
										
										
										
											2023-09-05 03:57:37 +08:00
										 |  |  | 	"github.com/minio/pkg/v2/workers" | 
					
						
							| 
									
										
										
										
											2023-11-10 18:15:13 +08:00
										 |  |  | 	"github.com/zeebo/xxh3" | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// Disabled means the lifecycle rule is inactive
 | 
					
						
							|  |  |  | 	Disabled = "Disabled" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	// TransitionStatus status of transition
 | 
					
						
							|  |  |  | 	TransitionStatus = "transition-status" | 
					
						
							|  |  |  | 	// TransitionedObjectName name of transitioned object
 | 
					
						
							|  |  |  | 	TransitionedObjectName = "transitioned-object" | 
					
						
							| 
									
										
										
										
											2021-06-04 05:26:51 +08:00
										 |  |  | 	// TransitionedVersionID is version of remote object
 | 
					
						
							|  |  |  | 	TransitionedVersionID = "transitioned-versionID" | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	// TransitionTier name of transition storage class
 | 
					
						
							|  |  |  | 	TransitionTier = "transition-tier" | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // LifecycleSys - Bucket lifecycle subsystem.
 | 
					
						
							| 
									
										
										
										
											2020-05-21 01:18:15 +08:00
										 |  |  | type LifecycleSys struct{} | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-10 01:02:41 +08:00
										 |  |  | // Get - gets lifecycle config associated to a given bucket name.
 | 
					
						
							| 
									
										
										
										
											2020-05-20 04:53:54 +08:00
										 |  |  | func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) { | 
					
						
							| 
									
										
										
										
											2023-05-25 13:52:39 +08:00
										 |  |  | 	lc, _, err = globalBucketMetadataSys.GetLifecycleConfig(bucketName) | 
					
						
							|  |  |  | 	return lc, err | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewLifecycleSys - creates new lifecycle system.
 | 
					
						
							|  |  |  | func NewLifecycleSys() *LifecycleSys { | 
					
						
							| 
									
										
										
										
											2020-05-21 01:18:15 +08:00
										 |  |  | 	return &LifecycleSys{} | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-12 10:22:32 +08:00
										 |  |  | func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo { | 
					
						
							|  |  |  | 	return madmin.TraceInfo{ | 
					
						
							|  |  |  | 		TraceType: madmin.TraceILM, | 
					
						
							|  |  |  | 		Time:      startTime, | 
					
						
							|  |  |  | 		NodeName:  globalLocalNodeName, | 
					
						
							|  |  |  | 		FuncName:  event, | 
					
						
							|  |  |  | 		Duration:  duration, | 
					
						
							|  |  |  | 		Path:      pathJoin(oi.Bucket, oi.Name), | 
					
						
							|  |  |  | 		Error:     "", | 
					
						
							|  |  |  | 		Message:   getSource(4), | 
					
						
							|  |  |  | 		Custom:    map[string]string{"version-id": oi.VersionID}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string) { | 
					
						
							|  |  |  | 	startTime := time.Now() | 
					
						
							|  |  |  | 	return func(event string) { | 
					
						
							|  |  |  | 		duration := time.Since(startTime) | 
					
						
							|  |  |  | 		if globalTrace.NumSubscribers(madmin.TraceILM) > 0 { | 
					
						
							|  |  |  | 			globalTrace.Publish(ilmTrace(startTime, duration, oi, event)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-07 08:10:33 +08:00
										 |  |  | type expiryTask struct { | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 	objInfo ObjectInfo | 
					
						
							|  |  |  | 	event   lifecycle.Event | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 	src     lcEventSrc | 
					
						
							| 
									
										
										
										
											2021-02-07 08:10:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | type expiryState struct { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | 	once                sync.Once | 
					
						
							|  |  |  | 	byDaysCh            chan expiryTask | 
					
						
							|  |  |  | 	byNewerNoncurrentCh chan newerNoncurrentTask | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | // PendingTasks returns the number of pending ILM expiry tasks.
 | 
					
						
							|  |  |  | func (es *expiryState) PendingTasks() int { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | 	return len(es.byDaysCh) + len(es.byNewerNoncurrentCh) | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | // close closes work channels exactly once.
 | 
					
						
							|  |  |  | func (es *expiryState) close() { | 
					
						
							|  |  |  | 	es.once.Do(func() { | 
					
						
							|  |  |  | 		close(es.byDaysCh) | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | 		close(es.byNewerNoncurrentCh) | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // enqueueByDays enqueues object versions expired by days for expiry.
 | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) { | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	select { | 
					
						
							| 
									
										
										
										
											2021-04-17 05:09:25 +08:00
										 |  |  | 	case <-GlobalContext.Done(): | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 		es.close() | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 	case es.byDaysCh <- expiryTask{objInfo: oi, event: event, src: src}: | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | // enqueueByNewerNoncurrent enqueues object versions expired by
 | 
					
						
							|  |  |  | // NewerNoncurrentVersions limit for expiry.
 | 
					
						
							| 
									
										
										
										
											2023-05-03 03:56:33 +08:00
										 |  |  | func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete, lcEvent lifecycle.Event) { | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	select { | 
					
						
							|  |  |  | 	case <-GlobalContext.Done(): | 
					
						
							|  |  |  | 		es.close() | 
					
						
							| 
									
										
										
										
											2023-05-03 03:56:33 +08:00
										 |  |  | 	case es.byNewerNoncurrentCh <- newerNoncurrentTask{bucket: bucket, versions: versions, event: lcEvent}: | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var globalExpiryState *expiryState | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func newExpiryState() *expiryState { | 
					
						
							| 
									
										
										
										
											2021-04-17 05:09:25 +08:00
										 |  |  | 	return &expiryState{ | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 		byDaysCh:            make(chan expiryTask, 100000), | 
					
						
							|  |  |  | 		byNewerNoncurrentCh: make(chan newerNoncurrentTask, 100000), | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func initBackgroundExpiry(ctx context.Context, objectAPI ObjectLayer) { | 
					
						
							|  |  |  | 	globalExpiryState = newExpiryState() | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	workerSize, _ := strconv.Atoi(env.Get("_MINIO_ILM_EXPIRY_WORKERS", strconv.Itoa((runtime.GOMAXPROCS(0)+1)/2))) | 
					
						
							| 
									
										
										
										
											2023-05-16 23:08:00 +08:00
										 |  |  | 	if workerSize == 0 { | 
					
						
							|  |  |  | 		workerSize = 4 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 	ewk, err := workers.New(workerSize) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nwk, err := workers.New(workerSize) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 		for t := range globalExpiryState.byDaysCh { | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 			ewk.Take() | 
					
						
							|  |  |  | 			go func(t expiryTask) { | 
					
						
							|  |  |  | 				defer ewk.Give() | 
					
						
							|  |  |  | 				if t.objInfo.TransitionedObject.Status != "" { | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 					applyExpiryOnTransitionedObject(ctx, objectAPI, t.objInfo, t.event, t.src) | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 					applyExpiryOnNonTransitionedObjects(ctx, objectAPI, t.objInfo, t.event, t.src) | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}(t) | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 		ewk.Wait() | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | 		for t := range globalExpiryState.byNewerNoncurrentCh { | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 			nwk.Take() | 
					
						
							|  |  |  | 			go func(t newerNoncurrentTask) { | 
					
						
							|  |  |  | 				defer nwk.Give() | 
					
						
							| 
									
										
										
										
											2023-05-03 03:56:33 +08:00
										 |  |  | 				deleteObjectVersions(ctx, objectAPI, t.bucket, t.versions, t.event) | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 			}(t) | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-03-31 01:47:15 +08:00
										 |  |  | 		nwk.Wait() | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	}() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-15 01:41:44 +08:00
										 |  |  | // newerNoncurrentTask encapsulates arguments required by worker to expire objects
 | 
					
						
							|  |  |  | // by NewerNoncurrentVersions
 | 
					
						
							|  |  |  | type newerNoncurrentTask struct { | 
					
						
							| 
									
										
										
										
											2021-11-20 09:54:10 +08:00
										 |  |  | 	bucket   string | 
					
						
							|  |  |  | 	versions []ObjectToDelete | 
					
						
							| 
									
										
										
										
											2023-05-03 03:56:33 +08:00
										 |  |  | 	event    lifecycle.Event | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | type transitionTask struct { | 
					
						
							|  |  |  | 	objInfo ObjectInfo | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 	src     lcEventSrc | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 	event   lifecycle.Event | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | type transitionState struct { | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | 	transitionCh chan transitionTask | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ctx        context.Context | 
					
						
							|  |  |  | 	objAPI     ObjectLayer | 
					
						
							|  |  |  | 	mu         sync.Mutex | 
					
						
							|  |  |  | 	numWorkers int | 
					
						
							|  |  |  | 	killCh     chan struct{} | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | 	activeTasks          atomic.Int64 | 
					
						
							|  |  |  | 	missedImmediateTasks atomic.Int64 | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	lastDayMu    sync.RWMutex | 
					
						
							|  |  |  | 	lastDayStats map[string]*lastDayTierStats | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | func (t *transitionState) queueTransitionTask(oi ObjectInfo, event lifecycle.Event, src lcEventSrc) { | 
					
						
							| 
									
										
										
										
											2023-11-18 08:16:46 +08:00
										 |  |  | 	task := transitionTask{objInfo: oi, event: event, src: src} | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | 	select { | 
					
						
							|  |  |  | 	case <-t.ctx.Done(): | 
					
						
							|  |  |  | 	case t.transitionCh <- task: | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		switch src { | 
					
						
							|  |  |  | 		case lcEventSrc_s3PutObject, lcEventSrc_s3CopyObject, lcEventSrc_s3CompleteMultipartUpload: | 
					
						
							|  |  |  | 			// Update missed immediate tasks only for incoming requests.
 | 
					
						
							|  |  |  | 			t.missedImmediateTasks.Add(1) | 
					
						
							| 
									
										
										
										
											2023-11-18 08:16:46 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | var globalTransitionState *transitionState | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 15:11:42 +08:00
										 |  |  | // newTransitionState returns a transitionState object ready to be initialized
 | 
					
						
							|  |  |  | // via its Init method.
 | 
					
						
							|  |  |  | func newTransitionState(ctx context.Context) *transitionState { | 
					
						
							| 
									
										
										
										
											2021-04-17 05:09:25 +08:00
										 |  |  | 	return &transitionState{ | 
					
						
							| 
									
										
										
										
											2023-11-18 08:16:46 +08:00
										 |  |  | 		transitionCh: make(chan transitionTask, 100000), | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 		ctx:          ctx, | 
					
						
							|  |  |  | 		killCh:       make(chan struct{}), | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | 		lastDayStats: make(map[string]*lastDayTierStats), | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-08 15:11:42 +08:00
										 |  |  | // Init initializes t with given objAPI and instantiates the configured number
 | 
					
						
							|  |  |  | // of transition workers.
 | 
					
						
							|  |  |  | func (t *transitionState) Init(objAPI ObjectLayer) { | 
					
						
							|  |  |  | 	n := globalAPIConfig.getTransitionWorkers() | 
					
						
							|  |  |  | 	t.mu.Lock() | 
					
						
							|  |  |  | 	defer t.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.objAPI = objAPI | 
					
						
							|  |  |  | 	t.updateWorkers(n) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | // PendingTasks returns the number of ILM transition tasks waiting for a worker
 | 
					
						
							|  |  |  | // goroutine.
 | 
					
						
							|  |  |  | func (t *transitionState) PendingTasks() int { | 
					
						
							| 
									
										
										
										
											2023-02-08 15:11:42 +08:00
										 |  |  | 	return len(t.transitionCh) | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ActiveTasks returns the number of active (ongoing) ILM transition tasks.
 | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | func (t *transitionState) ActiveTasks() int64 { | 
					
						
							|  |  |  | 	return t.activeTasks.Load() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MissedImmediateTasks returns the number of tasks - deferred to scanner due
 | 
					
						
							|  |  |  | // to tasks channel being backlogged.
 | 
					
						
							|  |  |  | func (t *transitionState) MissedImmediateTasks() int64 { | 
					
						
							|  |  |  | 	return t.missedImmediateTasks.Load() | 
					
						
							| 
									
										
										
										
											2021-08-18 01:21:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | // worker waits for transition tasks
 | 
					
						
							| 
									
										
										
										
											2023-02-16 14:09:25 +08:00
										 |  |  | func (t *transitionState) worker(objectAPI ObjectLayer) { | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-t.killCh: | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2023-02-16 14:09:25 +08:00
										 |  |  | 		case <-t.ctx.Done(): | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 			return | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | 		case task, ok := <-t.transitionCh: | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 			if !ok { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 				return | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | 			t.activeTasks.Add(1) | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 			if err := transitionObject(t.ctx, objectAPI, task.objInfo, newLifecycleAuditEvent(task.src, task.event)); err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-27 14:18:09 +08:00
										 |  |  | 				if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !xnet.IsNetworkOrHostDown(err, false) { | 
					
						
							|  |  |  | 					if !strings.Contains(err.Error(), "use of closed network connection") { | 
					
						
							|  |  |  | 						logger.LogIf(t.ctx, fmt.Errorf("Transition to %s failed for %s/%s version:%s with %w", | 
					
						
							|  |  |  | 							task.event.StorageClass, task.objInfo.Bucket, task.objInfo.Name, task.objInfo.VersionID, err)) | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2023-08-26 14:34:16 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-02-17 09:29:12 +08:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				ts := tierStats{ | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | 					TotalSize:   uint64(task.objInfo.Size), | 
					
						
							| 
									
										
										
										
											2022-02-17 09:29:12 +08:00
										 |  |  | 					NumVersions: 1, | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | 				if task.objInfo.IsLatest { | 
					
						
							| 
									
										
										
										
											2022-02-17 09:29:12 +08:00
										 |  |  | 					ts.NumObjects = 1 | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 				t.addLastDayStats(task.event.StorageClass, ts) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | 			t.activeTasks.Add(-1) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | func (t *transitionState) addLastDayStats(tier string, ts tierStats) { | 
					
						
							|  |  |  | 	t.lastDayMu.Lock() | 
					
						
							|  |  |  | 	defer t.lastDayMu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, ok := t.lastDayStats[tier]; !ok { | 
					
						
							|  |  |  | 		t.lastDayStats[tier] = &lastDayTierStats{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.lastDayStats[tier].addStats(ts) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 05:21:55 +08:00
										 |  |  | func (t *transitionState) getDailyAllTierStats() DailyAllTierStats { | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | 	t.lastDayMu.RLock() | 
					
						
							|  |  |  | 	defer t.lastDayMu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 05:21:55 +08:00
										 |  |  | 	res := make(DailyAllTierStats, len(t.lastDayStats)) | 
					
						
							| 
									
										
										
										
											2022-01-27 06:33:10 +08:00
										 |  |  | 	for tier, st := range t.lastDayStats { | 
					
						
							|  |  |  | 		res[tier] = st.clone() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return res | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | // UpdateWorkers at the end of this function leaves n goroutines waiting for
 | 
					
						
							|  |  |  | // transition tasks
 | 
					
						
							|  |  |  | func (t *transitionState) UpdateWorkers(n int) { | 
					
						
							|  |  |  | 	t.mu.Lock() | 
					
						
							|  |  |  | 	defer t.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2023-02-09 11:13:34 +08:00
										 |  |  | 	if t.objAPI == nil { // Init hasn't been called yet.
 | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-08 15:11:42 +08:00
										 |  |  | 	t.updateWorkers(n) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *transitionState) updateWorkers(n int) { | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 	for t.numWorkers < n { | 
					
						
							| 
									
										
										
										
											2023-02-16 14:09:25 +08:00
										 |  |  | 		go t.worker(t.objAPI) | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 		t.numWorkers++ | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 13:23:56 +08:00
										 |  |  | 	for t.numWorkers > n { | 
					
						
							|  |  |  | 		go func() { t.killCh <- struct{}{} }() | 
					
						
							|  |  |  | 		t.numWorkers-- | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 09:51:23 +08:00
										 |  |  | var errInvalidStorageClass = errors.New("invalid storage class") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 10:12:44 +08:00
										 |  |  | func validateTransitionTier(lc *lifecycle.Lifecycle) error { | 
					
						
							|  |  |  | 	for _, rule := range lc.Rules { | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		if rule.Transition.StorageClass != "" { | 
					
						
							|  |  |  | 			if valid := globalTierConfigMgr.IsTierValid(rule.Transition.StorageClass); !valid { | 
					
						
							|  |  |  | 				return errInvalidStorageClass | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-05-20 09:51:23 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		if rule.NoncurrentVersionTransition.StorageClass != "" { | 
					
						
							|  |  |  | 			if valid := globalTierConfigMgr.IsTierValid(rule.NoncurrentVersionTransition.StorageClass); !valid { | 
					
						
							|  |  |  | 				return errInvalidStorageClass | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | // enqueueTransitionImmediate enqueues obj for transition if eligible.
 | 
					
						
							|  |  |  | // This is to be called after a successful upload of an object (version).
 | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) { | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 	if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil { | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 		switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action { | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		case lifecycle.TransitionAction, lifecycle.TransitionVersionAction: | 
					
						
							| 
									
										
										
										
											2023-12-03 05:02:12 +08:00
										 |  |  | 			globalTransitionState.queueTransitionTask(obj, event, src) | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // expireTransitionedObject handles expiry of transitioned/restored objects
 | 
					
						
							|  |  |  | // (versions) in one of the following situations:
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 1. when a restored (via PostRestoreObject API) object expires.
 | 
					
						
							|  |  |  | // 2. when a transitioned object expires (based on an ILM rule).
 | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, lcEvent lifecycle.Event, src lcEventSrc) error { | 
					
						
							| 
									
										
										
										
											2023-04-12 10:22:32 +08:00
										 |  |  | 	traceFn := globalLifecycleSys.trace(*oi) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	var opts ObjectOptions | 
					
						
							| 
									
										
										
										
											2022-05-07 10:05:28 +08:00
										 |  |  | 	opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	opts.VersionID = lcOpts.VersionID | 
					
						
							| 
									
										
										
										
											2021-08-28 08:06:47 +08:00
										 |  |  | 	opts.Expiration = ExpirationOptions{Expire: true} | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 	tags := newLifecycleAuditEvent(src, lcEvent).Tags() | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 	if lcEvent.Action.DeleteRestored() { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		// delete locally restored copy of object or object version
 | 
					
						
							|  |  |  | 		// from the source, while leaving metadata behind. The data on
 | 
					
						
							|  |  |  | 		// transitioned tier lies untouched and still accessible
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		opts.Transition.ExpireRestored = true | 
					
						
							| 
									
										
										
										
											2021-06-04 05:26:51 +08:00
										 |  |  | 		_, err := objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts) | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			// TODO consider including expiry of restored object to events we
 | 
					
						
							|  |  |  | 			// notify.
 | 
					
						
							|  |  |  | 			auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// When an object is past expiry or when a transitioned object is being
 | 
					
						
							|  |  |  | 	// deleted, 'mark' the data in the remote tier for delete.
 | 
					
						
							|  |  |  | 	entry := jentry{ | 
					
						
							|  |  |  | 		ObjName:   oi.TransitionedObject.Name, | 
					
						
							|  |  |  | 		VersionID: oi.TransitionedObject.VersionID, | 
					
						
							|  |  |  | 		TierName:  oi.TransitionedObject.Tier, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := globalTierJournal.AddEntry(entry); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Delete metadata on source, now that data in remote tier has been
 | 
					
						
							|  |  |  | 	// marked for deletion.
 | 
					
						
							|  |  |  | 	if _, err := objectAPI.DeleteObject(ctx, oi.Bucket, oi.Name, opts); err != nil { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-01-26 06:04:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-27 08:49:00 +08:00
										 |  |  | 	// Send audit for the lifecycle delete operation
 | 
					
						
							|  |  |  | 	defer auditLogLifecycle(ctx, *oi, ILMExpiry, tags, traceFn) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	eventName := event.ObjectRemovedDelete | 
					
						
							|  |  |  | 	if lcOpts.DeleteMarker { | 
					
						
							|  |  |  | 		eventName = event.ObjectRemovedDeleteMarkerCreated | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	objInfo := ObjectInfo{ | 
					
						
							|  |  |  | 		Name:         oi.Name, | 
					
						
							|  |  |  | 		VersionID:    lcOpts.VersionID, | 
					
						
							|  |  |  | 		DeleteMarker: lcOpts.DeleteMarker, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Notify object deleted event.
 | 
					
						
							|  |  |  | 	sendEvent(eventArgs{ | 
					
						
							|  |  |  | 		EventName:  eventName, | 
					
						
							|  |  |  | 		BucketName: oi.Bucket, | 
					
						
							|  |  |  | 		Object:     objInfo, | 
					
						
							|  |  |  | 		UserAgent:  "Internal: [ILM-Expiry]", | 
					
						
							|  |  |  | 		Host:       globalLocalNodeName, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-01-26 06:04:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // generate an object name for transitioned object
 | 
					
						
							| 
									
										
										
										
											2021-07-21 01:49:52 +08:00
										 |  |  | func genTransitionObjName(bucket string) (string, error) { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	u, err := uuid.NewRandom() | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		return "", err | 
					
						
							| 
									
										
										
										
											2021-02-02 01:52:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	us := u.String() | 
					
						
							| 
									
										
										
										
											2023-11-10 18:15:13 +08:00
										 |  |  | 	hash := xxh3.HashString(pathJoin(globalDeploymentID(), bucket)) | 
					
						
							|  |  |  | 	obj := fmt.Sprintf("%s/%s/%s/%s", strconv.FormatUint(hash, 16), us[0:2], us[2:4], us) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return obj, nil | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // transition object to target specified by the transition ARN. When an object is transitioned to another
 | 
					
						
							|  |  |  | // storage specified by the transition ARN, the metadata is left behind on source cluster and original content
 | 
					
						
							|  |  |  | // is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
 | 
					
						
							|  |  |  | // to the transition tier without decrypting or re-encrypting.
 | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) error { | 
					
						
							| 
									
										
										
										
											2021-06-04 05:26:51 +08:00
										 |  |  | 	opts := ObjectOptions{ | 
					
						
							|  |  |  | 		Transition: TransitionOptions{ | 
					
						
							|  |  |  | 			Status: lifecycle.TransitionPending, | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 			Tier:   lae.StorageClass, | 
					
						
							| 
									
										
										
										
											2021-06-04 05:26:51 +08:00
										 |  |  | 			ETag:   oi.ETag, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-05-23 06:28:56 +08:00
										 |  |  | 		LifecycleAuditEvent: lae, | 
					
						
							|  |  |  | 		VersionID:           oi.VersionID, | 
					
						
							|  |  |  | 		Versioned:           globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name), | 
					
						
							|  |  |  | 		VersionSuspended:    globalBucketVersioningSys.PrefixSuspended(oi.Bucket, oi.Name), | 
					
						
							|  |  |  | 		MTime:               oi.ModTime, | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-10-22 01:46:53 +08:00
										 |  |  | 	return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 12:33:59 +08:00
										 |  |  | type auditTierOp struct { | 
					
						
							|  |  |  | 	Tier             string `json:"tier"` | 
					
						
							|  |  |  | 	TimeToResponseNS int64  `json:"timeToResponseNS"` | 
					
						
							|  |  |  | 	OutputBytes      int64  `json:"tx,omitempty"` | 
					
						
							|  |  |  | 	Error            string `json:"error,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func auditTierActions(ctx context.Context, tier string, bytes int64) func(err error) { | 
					
						
							|  |  |  | 	startTime := time.Now() | 
					
						
							|  |  |  | 	return func(err error) { | 
					
						
							|  |  |  | 		// Record only when audit targets configured.
 | 
					
						
							|  |  |  | 		if len(logger.AuditTargets()) == 0 { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		op := auditTierOp{ | 
					
						
							|  |  |  | 			Tier:        tier, | 
					
						
							|  |  |  | 			OutputBytes: bytes, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			op.TimeToResponseNS = time.Since(startTime).Nanoseconds() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			op.Error = err.Error() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		logger.GetReqInfo(ctx).AppendTags("tierStats", op) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | // getTransitionedObjectReader returns a reader from the transitioned tier.
 | 
					
						
							|  |  |  | func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) { | 
					
						
							| 
									
										
										
										
											2021-08-17 22:50:00 +08:00
										 |  |  | 	tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionedObject.Tier) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		return nil, fmt.Errorf("transition storage class not configured") | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-01 09:37:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	fn, off, length, err := NewGetObjectReader(rs, oi, opts) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, ErrorRespToObjectError(err, bucket, object) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	gopts := WarmBackendGetOpts{} | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	// get correct offsets for object
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	if off >= 0 && length >= 0 { | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		gopts.startOffset = off | 
					
						
							|  |  |  | 		gopts.length = length | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 12:33:59 +08:00
										 |  |  | 	timeTierAction := auditTierActions(ctx, oi.TransitionedObject.Tier, length) | 
					
						
							| 
									
										
										
										
											2021-08-17 22:50:00 +08:00
										 |  |  | 	reader, err := tgtClient.Get(ctx, oi.TransitionedObject.Name, remoteVersionID(oi.TransitionedObject.VersionID), gopts) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-28 13:42:18 +08:00
										 |  |  | 	closer := func() { | 
					
						
							| 
									
										
										
										
											2022-08-30 12:33:59 +08:00
										 |  |  | 		timeTierAction(reader.Close()) | 
					
						
							| 
									
										
										
										
											2021-04-28 13:42:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-25 00:44:00 +08:00
										 |  |  | 	return fn(reader, h, closer) | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RestoreRequestType represents type of restore.
 | 
					
						
							|  |  |  | type RestoreRequestType string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// SelectRestoreRequest specifies select request. This is the only valid value
 | 
					
						
							|  |  |  | 	SelectRestoreRequest RestoreRequestType = "SELECT" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Encryption specifies encryption setting on restored bucket
 | 
					
						
							|  |  |  | type Encryption struct { | 
					
						
							| 
									
										
										
										
											2021-09-22 00:02:15 +08:00
										 |  |  | 	EncryptionType sse.Algorithm `xml:"EncryptionType"` | 
					
						
							|  |  |  | 	KMSContext     string        `xml:"KMSContext,omitempty"` | 
					
						
							|  |  |  | 	KMSKeyID       string        `xml:"KMSKeyId,omitempty"` | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MetadataEntry denotes name and value.
 | 
					
						
							|  |  |  | type MetadataEntry struct { | 
					
						
							|  |  |  | 	Name  string `xml:"Name"` | 
					
						
							|  |  |  | 	Value string `xml:"Value"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // S3Location specifies s3 location that receives result of a restore object request
 | 
					
						
							|  |  |  | type S3Location struct { | 
					
						
							|  |  |  | 	BucketName   string          `xml:"BucketName,omitempty"` | 
					
						
							|  |  |  | 	Encryption   Encryption      `xml:"Encryption,omitempty"` | 
					
						
							|  |  |  | 	Prefix       string          `xml:"Prefix,omitempty"` | 
					
						
							|  |  |  | 	StorageClass string          `xml:"StorageClass,omitempty"` | 
					
						
							|  |  |  | 	Tagging      *tags.Tags      `xml:"Tagging,omitempty"` | 
					
						
							|  |  |  | 	UserMetadata []MetadataEntry `xml:"UserMetadata"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // OutputLocation specifies bucket where object needs to be restored
 | 
					
						
							|  |  |  | type OutputLocation struct { | 
					
						
							|  |  |  | 	S3 S3Location `xml:"S3,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsEmpty returns true if output location not specified.
 | 
					
						
							|  |  |  | func (o *OutputLocation) IsEmpty() bool { | 
					
						
							|  |  |  | 	return o.S3.BucketName == "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SelectParameters specifies sql select parameters
 | 
					
						
							|  |  |  | type SelectParameters struct { | 
					
						
							|  |  |  | 	s3select.S3Select | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsEmpty returns true if no select parameters set
 | 
					
						
							|  |  |  | func (sp *SelectParameters) IsEmpty() bool { | 
					
						
							| 
									
										
										
										
											2021-04-08 04:29:27 +08:00
										 |  |  | 	return sp == nil | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | var selectParamsXMLName = "SelectParameters" | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalXML - decodes XML data.
 | 
					
						
							|  |  |  | func (sp *SelectParameters) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { | 
					
						
							|  |  |  | 	// Essentially the same as S3Select barring the xml name.
 | 
					
						
							|  |  |  | 	if start.Name.Local == selectParamsXMLName { | 
					
						
							|  |  |  | 		start.Name = xml.Name{Space: "", Local: "SelectRequest"} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return sp.S3Select.UnmarshalXML(d, start) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RestoreObjectRequest - xml to restore a transitioned object
 | 
					
						
							|  |  |  | type RestoreObjectRequest struct { | 
					
						
							|  |  |  | 	XMLName          xml.Name           `xml:"http://s3.amazonaws.com/doc/2006-03-01/ RestoreRequest" json:"-"` | 
					
						
							|  |  |  | 	Days             int                `xml:"Days,omitempty"` | 
					
						
							|  |  |  | 	Type             RestoreRequestType `xml:"Type,omitempty"` | 
					
						
							| 
									
										
										
										
											2022-12-06 03:18:50 +08:00
										 |  |  | 	Tier             string             `xml:"Tier"` | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	Description      string             `xml:"Description,omitempty"` | 
					
						
							|  |  |  | 	SelectParameters *SelectParameters  `xml:"SelectParameters,omitempty"` | 
					
						
							|  |  |  | 	OutputLocation   OutputLocation     `xml:"OutputLocation,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Maximum 2MiB size per restore object request.
 | 
					
						
							|  |  |  | const maxRestoreObjectRequestSize = 2 << 20 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // parseRestoreRequest parses RestoreObjectRequest from xml
 | 
					
						
							|  |  |  | func parseRestoreRequest(reader io.Reader) (*RestoreObjectRequest, error) { | 
					
						
							|  |  |  | 	req := RestoreObjectRequest{} | 
					
						
							|  |  |  | 	if err := xml.NewDecoder(io.LimitReader(reader, maxRestoreObjectRequestSize)).Decode(&req); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return &req, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validate a RestoreObjectRequest as per AWS S3 spec https://docs.aws.amazon.com/AmazonS3/latest/API/API_RestoreObject.html
 | 
					
						
							|  |  |  | func (r *RestoreObjectRequest) validate(ctx context.Context, objAPI ObjectLayer) error { | 
					
						
							|  |  |  | 	if r.Type != SelectRestoreRequest && !r.SelectParameters.IsEmpty() { | 
					
						
							|  |  |  | 		return fmt.Errorf("Select parameters can only be specified with SELECT request type") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if r.Type == SelectRestoreRequest && r.SelectParameters.IsEmpty() { | 
					
						
							|  |  |  | 		return fmt.Errorf("SELECT restore request requires select parameters to be specified") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r.Type != SelectRestoreRequest && !r.OutputLocation.IsEmpty() { | 
					
						
							|  |  |  | 		return fmt.Errorf("OutputLocation required only for SELECT request type") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if r.Type == SelectRestoreRequest && r.OutputLocation.IsEmpty() { | 
					
						
							|  |  |  | 		return fmt.Errorf("OutputLocation required for SELECT requests") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r.Days != 0 && r.Type == SelectRestoreRequest { | 
					
						
							|  |  |  | 		return fmt.Errorf("Days cannot be specified with SELECT restore request") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if r.Days == 0 && r.Type != SelectRestoreRequest { | 
					
						
							|  |  |  | 		return fmt.Errorf("restoration days should be at least 1") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Check if bucket exists.
 | 
					
						
							|  |  |  | 	if !r.OutputLocation.IsEmpty() { | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 		if _, err := objAPI.GetBucketInfo(ctx, r.OutputLocation.S3.BucketName, BucketOptions{}); err != nil { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if r.OutputLocation.S3.Prefix == "" { | 
					
						
							|  |  |  | 			return fmt.Errorf("Prefix is a required parameter in OutputLocation") | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-12-23 01:19:32 +08:00
										 |  |  | 		if r.OutputLocation.S3.Encryption.EncryptionType != xhttp.AmzEncryptionAES { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 			return NotImplemented{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // postRestoreOpts returns ObjectOptions with version-id from the POST restore object request for a given bucket and object.
 | 
					
						
							|  |  |  | func postRestoreOpts(ctx context.Context, r *http.Request, bucket, object string) (opts ObjectOptions, err error) { | 
					
						
							| 
									
										
										
										
											2022-05-07 10:05:28 +08:00
										 |  |  | 	versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object) | 
					
						
							|  |  |  | 	versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object) | 
					
						
							| 
									
										
										
										
											2021-08-08 13:43:01 +08:00
										 |  |  | 	vid := strings.TrimSpace(r.Form.Get(xhttp.VersionID)) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if vid != "" && vid != nullVersionID { | 
					
						
							|  |  |  | 		_, err := uuid.Parse(vid) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 			return opts, InvalidVersionID{ | 
					
						
							|  |  |  | 				Bucket:    bucket, | 
					
						
							|  |  |  | 				Object:    object, | 
					
						
							|  |  |  | 				VersionID: vid, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !versioned && !versionSuspended { | 
					
						
							|  |  |  | 			return opts, InvalidArgument{ | 
					
						
							|  |  |  | 				Bucket: bucket, | 
					
						
							|  |  |  | 				Object: object, | 
					
						
							|  |  |  | 				Err:    fmt.Errorf("version-id specified %s but versioning is not enabled on %s", opts.VersionID, bucket), | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ObjectOptions{ | 
					
						
							|  |  |  | 		Versioned:        versioned, | 
					
						
							|  |  |  | 		VersionSuspended: versionSuspended, | 
					
						
							|  |  |  | 		VersionID:        vid, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | // set ObjectOptions for PUT call to restore temporary copy of transitioned data
 | 
					
						
							|  |  |  | func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo ObjectInfo) (putOpts ObjectOptions) { | 
					
						
							|  |  |  | 	meta := make(map[string]string) | 
					
						
							|  |  |  | 	sc := rreq.OutputLocation.S3.StorageClass | 
					
						
							|  |  |  | 	if sc == "" { | 
					
						
							|  |  |  | 		sc = objInfo.StorageClass | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta[strings.ToLower(xhttp.AmzStorageClass)] = sc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if rreq.Type == SelectRestoreRequest { | 
					
						
							|  |  |  | 		for _, v := range rreq.OutputLocation.S3.UserMetadata { | 
					
						
							| 
									
										
										
										
											2023-07-07 07:02:08 +08:00
										 |  |  | 			if !stringsHasPrefixFold(v.Name, "x-amz-meta") { | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 				meta["x-amz-meta-"+v.Name] = v.Value | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			meta[v.Name] = v.Value | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-11 00:13:12 +08:00
										 |  |  | 		if tags := rreq.OutputLocation.S3.Tagging.String(); tags != "" { | 
					
						
							|  |  |  | 			meta[xhttp.AmzObjectTagging] = tags | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		if rreq.OutputLocation.S3.Encryption.EncryptionType != "" { | 
					
						
							| 
									
										
										
										
											2020-12-23 01:19:32 +08:00
										 |  |  | 			meta[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return ObjectOptions{ | 
					
						
							| 
									
										
										
										
											2022-05-07 10:05:28 +08:00
										 |  |  | 			Versioned:        globalBucketVersioningSys.PrefixEnabled(bucket, object), | 
					
						
							|  |  |  | 			VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object), | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 			UserDefined:      meta, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for k, v := range objInfo.UserDefined { | 
					
						
							|  |  |  | 		meta[k] = v | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-11 00:13:12 +08:00
										 |  |  | 	if len(objInfo.UserTags) != 0 { | 
					
						
							|  |  |  | 		meta[xhttp.AmzObjectTagging] = objInfo.UserTags | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-31 10:23:45 +08:00
										 |  |  | 	// Set restore object status
 | 
					
						
							|  |  |  | 	restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now().UTC(), rreq.Days) | 
					
						
							|  |  |  | 	meta[xhttp.AmzRestore] = completedRestoreObj(restoreExpiry).String() | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	return ObjectOptions{ | 
					
						
							| 
									
										
										
										
											2022-05-07 10:05:28 +08:00
										 |  |  | 		Versioned:        globalBucketVersioningSys.PrefixEnabled(bucket, object), | 
					
						
							|  |  |  | 		VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object), | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 		UserDefined:      meta, | 
					
						
							|  |  |  | 		VersionID:        objInfo.VersionID, | 
					
						
							|  |  |  | 		MTime:            objInfo.ModTime, | 
					
						
							|  |  |  | 		Expires:          objInfo.Expires, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | var errRestoreHDRMalformed = fmt.Errorf("x-amz-restore header malformed") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-04 23:40:42 +08:00
										 |  |  | // IsRemote returns true if this object version's contents are in its remote
 | 
					
						
							|  |  |  | // tier.
 | 
					
						
							|  |  |  | func (fi FileInfo) IsRemote() bool { | 
					
						
							|  |  |  | 	if fi.TransitionStatus != lifecycle.TransitionComplete { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return !isRestoredObjectOnDisk(fi.Metadata) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsRemote returns true if this object version's contents are in its remote
 | 
					
						
							|  |  |  | // tier.
 | 
					
						
							|  |  |  | func (oi ObjectInfo) IsRemote() bool { | 
					
						
							| 
									
										
										
										
											2021-08-17 22:50:00 +08:00
										 |  |  | 	if oi.TransitionedObject.Status != lifecycle.TransitionComplete { | 
					
						
							| 
									
										
										
										
											2021-05-04 23:40:42 +08:00
										 |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return !isRestoredObjectOnDisk(oi.UserDefined) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // restoreObjStatus represents a restore-object's status. It can be either
 | 
					
						
							|  |  |  | // ongoing or completed.
 | 
					
						
							|  |  |  | type restoreObjStatus struct { | 
					
						
							|  |  |  | 	ongoing bool | 
					
						
							|  |  |  | 	expiry  time.Time | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // ongoingRestoreObj constructs restoreObjStatus for an ongoing restore-object.
 | 
					
						
							|  |  |  | func ongoingRestoreObj() restoreObjStatus { | 
					
						
							|  |  |  | 	return restoreObjStatus{ | 
					
						
							|  |  |  | 		ongoing: true, | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // completeRestoreObj constructs restoreObjStatus for a completed restore-object with given expiry.
 | 
					
						
							|  |  |  | func completedRestoreObj(expiry time.Time) restoreObjStatus { | 
					
						
							|  |  |  | 	return restoreObjStatus{ | 
					
						
							|  |  |  | 		ongoing: false, | 
					
						
							|  |  |  | 		expiry:  expiry.UTC(), | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // String returns x-amz-restore compatible representation of r.
 | 
					
						
							|  |  |  | func (r restoreObjStatus) String() string { | 
					
						
							|  |  |  | 	if r.Ongoing() { | 
					
						
							| 
									
										
										
										
											2022-02-10 05:17:41 +08:00
										 |  |  | 		return `ongoing-request="true"` | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-02-10 05:17:41 +08:00
										 |  |  | 	return fmt.Sprintf(`ongoing-request="false", expiry-date="%s"`, r.expiry.Format(http.TimeFormat)) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Expiry returns expiry of restored object and true if restore-object has completed.
 | 
					
						
							|  |  |  | // Otherwise returns zero value of time.Time and false.
 | 
					
						
							|  |  |  | func (r restoreObjStatus) Expiry() (time.Time, bool) { | 
					
						
							|  |  |  | 	if r.Ongoing() { | 
					
						
							|  |  |  | 		return time.Time{}, false | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return r.expiry, true | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // Ongoing returns true if restore-object is ongoing.
 | 
					
						
							|  |  |  | func (r restoreObjStatus) Ongoing() bool { | 
					
						
							|  |  |  | 	return r.ongoing | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // OnDisk returns true if restored object contents exist in MinIO. Otherwise returns false.
 | 
					
						
							|  |  |  | // The restore operation could be in one of the following states,
 | 
					
						
							|  |  |  | // - in progress (no content on MinIO's disks yet)
 | 
					
						
							|  |  |  | // - completed
 | 
					
						
							|  |  |  | // - completed but expired (again, no content on MinIO's disks)
 | 
					
						
							|  |  |  | func (r restoreObjStatus) OnDisk() bool { | 
					
						
							|  |  |  | 	if expiry, ok := r.Expiry(); ok && time.Now().UTC().Before(expiry) { | 
					
						
							|  |  |  | 		// completed
 | 
					
						
							|  |  |  | 		return true | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return false // in progress or completed but expired
 | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // parseRestoreObjStatus parses restoreHdr from AmzRestore header. If the value is valid it returns a
 | 
					
						
							|  |  |  | // restoreObjStatus value with the status and expiry (if any). Otherwise returns
 | 
					
						
							|  |  |  | // the empty value and an error indicating the parse failure.
 | 
					
						
							|  |  |  | func parseRestoreObjStatus(restoreHdr string) (restoreObjStatus, error) { | 
					
						
							|  |  |  | 	tokens := strings.SplitN(restoreHdr, ",", 2) | 
					
						
							|  |  |  | 	progressTokens := strings.SplitN(tokens[0], "=", 2) | 
					
						
							|  |  |  | 	if len(progressTokens) != 2 { | 
					
						
							|  |  |  | 		return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	if strings.TrimSpace(progressTokens[0]) != "ongoing-request" { | 
					
						
							|  |  |  | 		return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	switch progressTokens[1] { | 
					
						
							| 
									
										
										
										
											2022-02-10 05:17:41 +08:00
										 |  |  | 	case "true", `"true"`: // true without double quotes is deprecated in Feb 2022
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		if len(tokens) == 1 { | 
					
						
							|  |  |  | 			return ongoingRestoreObj(), nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-02-10 05:17:41 +08:00
										 |  |  | 	case "false", `"false"`: // false without double quotes is deprecated in Feb 2022
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		if len(tokens) != 2 { | 
					
						
							|  |  |  | 			return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		expiryTokens := strings.SplitN(tokens[1], "=", 2) | 
					
						
							|  |  |  | 		if len(expiryTokens) != 2 { | 
					
						
							|  |  |  | 			return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if strings.TrimSpace(expiryTokens[0]) != "expiry-date" { | 
					
						
							|  |  |  | 			return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-07 22:24:54 +08:00
										 |  |  | 		expiry, err := amztime.ParseHeader(strings.Trim(expiryTokens[1], `"`)) | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return completedRestoreObj(expiry), nil | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | 	return restoreObjStatus{}, errRestoreHDRMalformed | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-20 01:30:42 +08:00
										 |  |  | // isRestoredObjectOnDisk returns true if the restored object is on disk. Note
 | 
					
						
							|  |  |  | // this function must be called only if object version's transition status is
 | 
					
						
							|  |  |  | // complete.
 | 
					
						
							|  |  |  | func isRestoredObjectOnDisk(meta map[string]string) (onDisk bool) { | 
					
						
							|  |  |  | 	if restoreHdr, ok := meta[xhttp.AmzRestore]; ok { | 
					
						
							|  |  |  | 		if restoreStatus, err := parseRestoreObjStatus(restoreHdr); err == nil { | 
					
						
							|  |  |  | 			return restoreStatus.OnDisk() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return onDisk | 
					
						
							| 
									
										
										
										
											2020-11-13 04:12:09 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-07-21 08:36:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ToLifecycleOpts returns lifecycle.ObjectOpts value for oi.
 | 
					
						
							|  |  |  | func (oi ObjectInfo) ToLifecycleOpts() lifecycle.ObjectOpts { | 
					
						
							|  |  |  | 	return lifecycle.ObjectOpts{ | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		Name:             oi.Name, | 
					
						
							|  |  |  | 		UserTags:         oi.UserTags, | 
					
						
							|  |  |  | 		VersionID:        oi.VersionID, | 
					
						
							|  |  |  | 		ModTime:          oi.ModTime, | 
					
						
							| 
									
										
										
										
											2023-11-23 05:42:39 +08:00
										 |  |  | 		Size:             oi.Size, | 
					
						
							| 
									
										
										
										
											2021-10-02 02:58:17 +08:00
										 |  |  | 		IsLatest:         oi.IsLatest, | 
					
						
							|  |  |  | 		NumVersions:      oi.NumVersions, | 
					
						
							|  |  |  | 		DeleteMarker:     oi.DeleteMarker, | 
					
						
							|  |  |  | 		SuccessorModTime: oi.SuccessorModTime, | 
					
						
							|  |  |  | 		RestoreOngoing:   oi.RestoreOngoing, | 
					
						
							|  |  |  | 		RestoreExpires:   oi.RestoreExpires, | 
					
						
							|  |  |  | 		TransitionStatus: oi.TransitionedObject.Status, | 
					
						
							| 
									
										
										
										
											2021-07-21 08:36:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |