| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * MinIO Cloud Storage, (C) 2019 MinIO, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/xml" | 
					
						
							| 
									
										
										
										
											2019-09-06 06:51:27 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | 	"path" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio-go/pkg/set" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/lifecycle" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Disabled means the lifecycle rule is inactive
 | 
					
						
							|  |  |  | 	Disabled = "Disabled" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // LifecycleSys - Bucket lifecycle subsystem.
 | 
					
						
							|  |  |  | type LifecycleSys struct { | 
					
						
							|  |  |  | 	sync.RWMutex | 
					
						
							|  |  |  | 	bucketLifecycleMap map[string]lifecycle.Lifecycle | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Set - sets lifecycle config to given bucket name.
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) Set(bucketName string, lifecycle lifecycle.Lifecycle) { | 
					
						
							|  |  |  | 	sys.Lock() | 
					
						
							|  |  |  | 	defer sys.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sys.bucketLifecycleMap[bucketName] = lifecycle | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-10 01:02:41 +08:00
										 |  |  | // Get - gets lifecycle config associated to a given bucket name.
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) Get(bucketName string) (lifecycle lifecycle.Lifecycle, ok bool) { | 
					
						
							|  |  |  | 	sys.Lock() | 
					
						
							|  |  |  | 	defer sys.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	l, ok := sys.bucketLifecycleMap[bucketName] | 
					
						
							|  |  |  | 	return l, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | func saveLifecycleConfig(ctx context.Context, objAPI ObjectLayer, bucketName string, bucketLifecycle *lifecycle.Lifecycle) error { | 
					
						
							|  |  |  | 	data, err := xml.Marshal(bucketLifecycle) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Construct path to lifecycle.xml for the given bucket.
 | 
					
						
							|  |  |  | 	configFile := path.Join(bucketConfigPrefix, bucketName, bucketLifecycleConfig) | 
					
						
							|  |  |  | 	return saveConfig(ctx, objAPI, configFile, data) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // getLifecycleConfig - get lifecycle config for given bucket name.
 | 
					
						
							|  |  |  | func getLifecycleConfig(objAPI ObjectLayer, bucketName string) (*lifecycle.Lifecycle, error) { | 
					
						
							|  |  |  | 	// Construct path to lifecycle.xml for the given bucket.
 | 
					
						
							|  |  |  | 	configFile := path.Join(bucketConfigPrefix, bucketName, bucketLifecycleConfig) | 
					
						
							|  |  |  | 	configData, err := readConfig(context.Background(), objAPI, configFile) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if err == errConfigNotFound { | 
					
						
							|  |  |  | 			err = BucketLifecycleNotFound{Bucket: bucketName} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return lifecycle.ParseLifecycleConfig(bytes.NewReader(configData)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func removeLifecycleConfig(ctx context.Context, objAPI ObjectLayer, bucketName string) error { | 
					
						
							|  |  |  | 	// Construct path to lifecycle.xml for the given bucket.
 | 
					
						
							|  |  |  | 	configFile := path.Join(bucketConfigPrefix, bucketName, bucketLifecycleConfig) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := objAPI.DeleteObject(ctx, minioMetaBucket, configFile); err != nil { | 
					
						
							|  |  |  | 		if _, ok := err.(ObjectNotFound); ok { | 
					
						
							|  |  |  | 			return BucketLifecycleNotFound{Bucket: bucketName} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewLifecycleSys - creates new lifecycle system.
 | 
					
						
							|  |  |  | func NewLifecycleSys() *LifecycleSys { | 
					
						
							|  |  |  | 	return &LifecycleSys{ | 
					
						
							|  |  |  | 		bucketLifecycleMap: make(map[string]lifecycle.Lifecycle), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Init - initializes lifecycle system from lifecycle.xml of all buckets.
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) Init(objAPI ObjectLayer) error { | 
					
						
							|  |  |  | 	if objAPI == nil { | 
					
						
							|  |  |  | 		return errServerNotInitialized | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		// Refresh LifecycleSys in background.
 | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			ticker := time.NewTicker(globalRefreshBucketLifecycleInterval) | 
					
						
							|  |  |  | 			defer ticker.Stop() | 
					
						
							|  |  |  | 			for { | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case <-GlobalServiceDoneCh: | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				case <-ticker.C: | 
					
						
							|  |  |  | 					sys.refresh(objAPI) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	doneCh := make(chan struct{}) | 
					
						
							|  |  |  | 	defer close(doneCh) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Initializing lifecycle needs a retry mechanism for
 | 
					
						
							|  |  |  | 	// the following reasons:
 | 
					
						
							|  |  |  | 	//  - Read quorum is lost just after the initialization
 | 
					
						
							|  |  |  | 	//    of the object layer.
 | 
					
						
							| 
									
										
										
										
											2019-09-06 06:51:27 +08:00
										 |  |  | 	retryTimerCh := newRetryTimerSimple(doneCh) | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-retryTimerCh: | 
					
						
							|  |  |  | 			// Load LifecycleSys once during boot.
 | 
					
						
							|  |  |  | 			if err := sys.refresh(objAPI); err != nil { | 
					
						
							|  |  |  | 				if err == errDiskNotFound || | 
					
						
							|  |  |  | 					strings.Contains(err.Error(), InsufficientReadQuorum{}.Error()) || | 
					
						
							|  |  |  | 					strings.Contains(err.Error(), InsufficientWriteQuorum{}.Error()) { | 
					
						
							|  |  |  | 					logger.Info("Waiting for lifecycle subsystem to be initialized..") | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return err | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-09-06 06:51:27 +08:00
										 |  |  | 			return nil | 
					
						
							|  |  |  | 		case <-globalOSSignalCh: | 
					
						
							|  |  |  | 			return fmt.Errorf("Initializing Lifecycle sub-system gracefully stopped") | 
					
						
							| 
									
										
										
										
											2019-07-20 04:20:33 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Refresh LifecycleSys.
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) refresh(objAPI ObjectLayer) error { | 
					
						
							|  |  |  | 	buckets, err := objAPI.ListBuckets(context.Background()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(context.Background(), err) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sys.removeDeletedBuckets(buckets) | 
					
						
							|  |  |  | 	for _, bucket := range buckets { | 
					
						
							|  |  |  | 		config, err := objAPI.GetBucketLifecycle(context.Background(), bucket.Name) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if _, ok := err.(BucketLifecycleNotFound); ok { | 
					
						
							|  |  |  | 				sys.Remove(bucket.Name) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sys.Set(bucket.Name, *config) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // removeDeletedBuckets - to handle a corner case where we have cached the lifecycle for a deleted
 | 
					
						
							|  |  |  | // bucket. i.e if we miss a delete-bucket notification we should delete the corresponding
 | 
					
						
							|  |  |  | // bucket policy during sys.refresh()
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) removeDeletedBuckets(bucketInfos []BucketInfo) { | 
					
						
							|  |  |  | 	buckets := set.NewStringSet() | 
					
						
							|  |  |  | 	for _, info := range bucketInfos { | 
					
						
							|  |  |  | 		buckets.Add(info.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sys.Lock() | 
					
						
							|  |  |  | 	defer sys.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for bucket := range sys.bucketLifecycleMap { | 
					
						
							|  |  |  | 		if !buckets.Contains(bucket) { | 
					
						
							|  |  |  | 			delete(sys.bucketLifecycleMap, bucket) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Remove - removes policy for given bucket name.
 | 
					
						
							|  |  |  | func (sys *LifecycleSys) Remove(bucketName string) { | 
					
						
							|  |  |  | 	sys.Lock() | 
					
						
							|  |  |  | 	defer sys.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	delete(sys.bucketLifecycleMap, bucketName) | 
					
						
							|  |  |  | } |