| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * MinIO Cloud Storage, (C) 2020 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 ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2020-09-22 04:43:29 +08:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2020-08-13 08:32:24 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	miniogo "github.com/minio/minio-go/v7" | 
					
						
							|  |  |  | 	"github.com/minio/minio-go/v7/pkg/encrypt" | 
					
						
							|  |  |  | 	"github.com/minio/minio-go/v7/pkg/tags" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/crypto" | 
					
						
							|  |  |  | 	xhttp "github.com/minio/minio/cmd/http" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/bucket/bandwidth" | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/bucket/replication" | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/event" | 
					
						
							|  |  |  | 	iampolicy "github.com/minio/minio/pkg/iam/policy" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | // gets replication config associated to a given bucket name.
 | 
					
						
							|  |  |  | func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) { | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if globalIsGateway { | 
					
						
							| 
									
										
										
										
											2020-10-10 00:59:52 +08:00
										 |  |  | 		objAPI := newObjectLayerFn() | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		if objAPI == nil { | 
					
						
							|  |  |  | 			return nil, errServerNotInitialized | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil, BucketReplicationConfigNotFound{Bucket: bucketName} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | // validateReplicationDestination returns error if replication destination bucket missing or not configured
 | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | // It also returns true if replication destination is same as this server.
 | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | func validateReplicationDestination(ctx context.Context, bucket string, rCfg *replication.Config) (bool, error) { | 
					
						
							| 
									
										
										
										
											2020-10-09 01:54:11 +08:00
										 |  |  | 	clnt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rCfg.RoleArn) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if clnt == nil { | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | 		return false, BucketRemoteTargetNotFound{Bucket: bucket} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if found, _ := clnt.BucketExists(ctx, rCfg.GetDestination().Bucket); !found { | 
					
						
							| 
									
										
										
										
											2020-10-09 01:54:11 +08:00
										 |  |  | 		return false, BucketRemoteDestinationNotFound{Bucket: rCfg.GetDestination().Bucket} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-08-05 14:02:27 +08:00
										 |  |  | 	if ret, err := globalBucketObjectLockSys.Get(bucket); err == nil { | 
					
						
							|  |  |  | 		if ret.LockEnabled { | 
					
						
							|  |  |  | 			lock, _, _, _, err := clnt.GetObjectLockConfig(ctx, rCfg.GetDestination().Bucket) | 
					
						
							|  |  |  | 			if err != nil || lock != "Enabled" { | 
					
						
							|  |  |  | 				return false, BucketReplicationDestinationMissingLock{Bucket: rCfg.GetDestination().Bucket} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	// validate replication ARN against target endpoint
 | 
					
						
							| 
									
										
										
										
											2020-08-07 08:10:21 +08:00
										 |  |  | 	c, ok := globalBucketTargetSys.arnRemotesMap[rCfg.RoleArn] | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | 	if ok { | 
					
						
							|  |  |  | 		if c.EndpointURL().String() == clnt.EndpointURL().String() { | 
					
						
							|  |  |  | 			sameTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort) | 
					
						
							|  |  |  | 			return sameTarget, nil | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | 	return false, BucketRemoteTargetNotFound{Bucket: bucket} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 08:32:24 +08:00
										 |  |  | func mustReplicateWeb(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string, permErr APIErrorCode) bool { | 
					
						
							|  |  |  | 	if permErr != ErrNone { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return mustReplicater(ctx, r, bucket, object, meta, replStatus) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | // mustReplicate returns true if object meets replication criteria.
 | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | func mustReplicate(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool { | 
					
						
							| 
									
										
										
										
											2020-09-23 03:47:24 +08:00
										 |  |  | 	if s3Err := isPutActionAllowed(getRequestAuthType(r), bucket, "", r, iampolicy.GetReplicationConfigurationAction); s3Err != ErrNone { | 
					
						
							| 
									
										
										
										
											2020-08-13 08:32:24 +08:00
										 |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return mustReplicater(ctx, r, bucket, object, meta, replStatus) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // mustReplicater returns true if object meets replication criteria.
 | 
					
						
							|  |  |  | func mustReplicater(ctx context.Context, r *http.Request, bucket, object string, meta map[string]string, replStatus string) bool { | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if globalIsGateway { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if rs, ok := meta[xhttp.AmzBucketReplicationStatus]; ok { | 
					
						
							|  |  |  | 		replStatus = rs | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if replication.StatusType(replStatus) == replication.Replica { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | 	cfg, err := getReplicationConfig(ctx, bucket) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	opts := replication.ObjectOpts{ | 
					
						
							|  |  |  | 		Name: object, | 
					
						
							|  |  |  | 		SSEC: crypto.SSEC.IsEncrypted(meta), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tagStr, ok := meta[xhttp.AmzObjectTagging] | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 		opts.UserTags = tagStr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return cfg.Replicate(opts) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | func putReplicationOpts(ctx context.Context, dest replication.Destination, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions) { | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	meta := make(map[string]string) | 
					
						
							|  |  |  | 	for k, v := range objInfo.UserDefined { | 
					
						
							|  |  |  | 		if k == xhttp.AmzBucketReplicationStatus { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-08-13 08:32:24 +08:00
										 |  |  | 		if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		meta[k] = v | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tag, err := tags.ParseObjectTags(objInfo.UserTags) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-08-06 11:01:20 +08:00
										 |  |  | 	sc := dest.StorageClass | 
					
						
							|  |  |  | 	if sc == "" { | 
					
						
							|  |  |  | 		sc = objInfo.StorageClass | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	putOpts = miniogo.PutObjectOptions{ | 
					
						
							| 
									
										
										
										
											2020-10-06 23:37:09 +08:00
										 |  |  | 		UserMetadata:    meta, | 
					
						
							|  |  |  | 		UserTags:        tag.ToMap(), | 
					
						
							|  |  |  | 		ContentType:     objInfo.ContentType, | 
					
						
							|  |  |  | 		ContentEncoding: objInfo.ContentEncoding, | 
					
						
							|  |  |  | 		StorageClass:    sc, | 
					
						
							|  |  |  | 		Internal: miniogo.AdvancedPutOptions{ | 
					
						
							|  |  |  | 			SourceVersionID:   objInfo.VersionID, | 
					
						
							|  |  |  | 			ReplicationStatus: miniogo.ReplicationStatusReplica, | 
					
						
							|  |  |  | 			SourceMTime:       objInfo.ModTime, | 
					
						
							|  |  |  | 			SourceETag:        objInfo.ETag, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if mode, ok := objInfo.UserDefined[xhttp.AmzObjectLockMode]; ok { | 
					
						
							|  |  |  | 		rmode := miniogo.RetentionMode(mode) | 
					
						
							|  |  |  | 		putOpts.Mode = rmode | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if retainDateStr, ok := objInfo.UserDefined[xhttp.AmzObjectLockRetainUntilDate]; ok { | 
					
						
							|  |  |  | 		rdate, err := time.Parse(time.RFC3339, retainDateStr) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		putOpts.RetainUntilDate = rdate | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if lhold, ok := objInfo.UserDefined[xhttp.AmzObjectLockLegalHold]; ok { | 
					
						
							|  |  |  | 		putOpts.LegalHold = miniogo.LegalHoldStatus(lhold) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if crypto.S3.IsEncrypted(objInfo.UserDefined) { | 
					
						
							|  |  |  | 		putOpts.ServerSideEncryption = encrypt.NewSSE() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // replicateObject replicates the specified version of the object to destination bucket
 | 
					
						
							|  |  |  | // The source object is then updated to reflect the replication status.
 | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLayer) { | 
					
						
							|  |  |  | 	bucket := objInfo.Bucket | 
					
						
							|  |  |  | 	object := objInfo.Name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-31 10:55:22 +08:00
										 |  |  | 	cfg, err := getReplicationConfig(ctx, bucket) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-09 01:54:11 +08:00
										 |  |  | 	tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, cfg.RoleArn) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if tgt == nil { | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 		logger.LogIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, cfg.RoleArn)) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 	gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{ | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 		VersionID: objInfo.VersionID, | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 	objInfo = gr.ObjInfo | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	size, err := objInfo.GetActualSize() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 		gr.Close() | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dest := cfg.GetDestination() | 
					
						
							|  |  |  | 	if dest.Bucket == "" { | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 		gr.Close() | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// if heal encounters a pending replication status, either replication
 | 
					
						
							|  |  |  | 	// has failed due to server shutdown or crawler and PutObject replication are in contention.
 | 
					
						
							|  |  |  | 	healPending := objInfo.ReplicationStatus == replication.Pending | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	// In the rare event that replication is in pending state either due to
 | 
					
						
							|  |  |  | 	// server shut down/crash before replication completed or healing and PutObject
 | 
					
						
							|  |  |  | 	// race - do an additional stat to see if the version ID exists
 | 
					
						
							|  |  |  | 	if healPending { | 
					
						
							|  |  |  | 		_, err := tgt.StatObject(ctx, dest.Bucket, object, miniogo.StatObjectOptions{VersionID: objInfo.VersionID}) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 			gr.Close() | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 			// object with same VersionID already exists, replication kicked off by
 | 
					
						
							|  |  |  | 			// PutObject might have completed.
 | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 	target, err := globalBucketMetadataSys.GetBucketTarget(bucket, cfg.RoleArn) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s cfg:%s err:%s", bucket, cfg.RoleArn, err)) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	putOpts := putReplicationOpts(ctx, dest, objInfo) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	replicationStatus := replication.Complete | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Setup bandwidth throttling
 | 
					
						
							| 
									
										
										
										
											2020-10-13 00:04:55 +08:00
										 |  |  | 	totalNodesCount := len(GetRemotePeers(globalEndpoints)) + 1 | 
					
						
							|  |  |  | 	b := target.BandwidthLimit / int64(totalNodesCount) | 
					
						
							| 
									
										
										
										
											2020-10-10 11:36:00 +08:00
										 |  |  | 	var headerSize int | 
					
						
							|  |  |  | 	for k, v := range putOpts.Header() { | 
					
						
							|  |  |  | 		headerSize += len(k) + len(v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts) | 
					
						
							|  |  |  | 	r.Close() | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		replicationStatus = replication.Failed | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	objInfo.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String() | 
					
						
							|  |  |  | 	if objInfo.UserTags != "" { | 
					
						
							|  |  |  | 		objInfo.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// FIXME: add support for missing replication events
 | 
					
						
							|  |  |  | 	// - event.ObjectReplicationNotTracked
 | 
					
						
							|  |  |  | 	// - event.ObjectReplicationMissedThreshold
 | 
					
						
							|  |  |  | 	// - event.ObjectReplicationReplicatedAfterThreshold
 | 
					
						
							|  |  |  | 	if replicationStatus == replication.Failed { | 
					
						
							|  |  |  | 		sendEvent(eventArgs{ | 
					
						
							|  |  |  | 			EventName:  event.ObjectReplicationFailed, | 
					
						
							|  |  |  | 			BucketName: bucket, | 
					
						
							|  |  |  | 			Object:     objInfo, | 
					
						
							|  |  |  | 			Host:       "Internal: [Replication]", | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	objInfo.metadataOnly = true // Perform only metadata updates.
 | 
					
						
							|  |  |  | 	if _, err = objectAPI.CopyObject(ctx, bucket, object, bucket, object, objInfo, ObjectOptions{ | 
					
						
							|  |  |  | 		VersionID: objInfo.VersionID, | 
					
						
							| 
									
										
										
										
											2020-09-16 11:44:48 +08:00
										 |  |  | 	}, ObjectOptions{ | 
					
						
							|  |  |  | 		VersionID: objInfo.VersionID, | 
					
						
							|  |  |  | 	}); err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, fmt.Errorf("Unable to update replication metadata for %s: %s", objInfo.VersionID, err)) | 
					
						
							| 
									
										
										
										
											2020-07-22 08:49:56 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-08-13 08:32:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // filterReplicationStatusMetadata filters replication status metadata for COPY
 | 
					
						
							|  |  |  | func filterReplicationStatusMetadata(metadata map[string]string) map[string]string { | 
					
						
							|  |  |  | 	// Copy on write
 | 
					
						
							|  |  |  | 	dst := metadata | 
					
						
							|  |  |  | 	var copied bool | 
					
						
							|  |  |  | 	delKey := func(key string) { | 
					
						
							|  |  |  | 		if _, ok := metadata[key]; !ok { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !copied { | 
					
						
							|  |  |  | 			dst = make(map[string]string, len(metadata)) | 
					
						
							|  |  |  | 			for k, v := range metadata { | 
					
						
							|  |  |  | 				dst[k] = v | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			copied = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		delete(dst, key) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	delKey(xhttp.AmzBucketReplicationStatus) | 
					
						
							|  |  |  | 	return dst | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | type replicationState struct { | 
					
						
							|  |  |  | 	// add future metrics here
 | 
					
						
							|  |  |  | 	replicaCh chan ObjectInfo | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *replicationState) queueReplicaTask(oi ObjectInfo) { | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case r.replicaCh <- oi: | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 04:43:29 +08:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	globalReplicationState *replicationState | 
					
						
							|  |  |  | 	// TODO: currently keeping it conservative
 | 
					
						
							|  |  |  | 	// but eventually can be tuned in future,
 | 
					
						
							|  |  |  | 	// take only half the CPUs for replication
 | 
					
						
							|  |  |  | 	// conservatively.
 | 
					
						
							|  |  |  | 	globalReplicationConcurrent = runtime.GOMAXPROCS(0) / 2 | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func newReplicationState() *replicationState { | 
					
						
							| 
									
										
										
										
											2020-09-25 03:25:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// fix minimum concurrent replication to 1 for single CPU setup
 | 
					
						
							|  |  |  | 	if globalReplicationConcurrent == 0 { | 
					
						
							|  |  |  | 		globalReplicationConcurrent = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-23 03:47:24 +08:00
										 |  |  | 	rs := &replicationState{ | 
					
						
							| 
									
										
										
										
											2020-10-06 05:45:42 +08:00
										 |  |  | 		replicaCh: make(chan ObjectInfo, 10000), | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-23 03:47:24 +08:00
										 |  |  | 	go func() { | 
					
						
							|  |  |  | 		<-GlobalContext.Done() | 
					
						
							|  |  |  | 		close(rs.replicaCh) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	return rs | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-22 04:43:29 +08:00
										 |  |  | // addWorker creates a new worker to process tasks
 | 
					
						
							|  |  |  | func (r *replicationState) addWorker(ctx context.Context, objectAPI ObjectLayer) { | 
					
						
							|  |  |  | 	// Add a new worker.
 | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 	go func() { | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2020-09-22 04:43:29 +08:00
										 |  |  | 			case oi, ok := <-r.replicaCh: | 
					
						
							|  |  |  | 				if !ok { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-09-17 07:04:55 +08:00
										 |  |  | 				replicateObject(ctx, oi, objectAPI) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-22 04:43:29 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) { | 
					
						
							|  |  |  | 	if globalReplicationState == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start with globalReplicationConcurrent.
 | 
					
						
							|  |  |  | 	for i := 0; i < globalReplicationConcurrent; i++ { | 
					
						
							|  |  |  | 		globalReplicationState.addWorker(ctx, objectAPI) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |