|
|
|
@ -32,7 +32,9 @@ import ( |
|
|
|
|
"strconv" |
|
|
|
|
|
|
|
|
|
"github.com/gorilla/mux" |
|
|
|
|
miniogo "github.com/minio/minio-go" |
|
|
|
|
"github.com/minio/minio/cmd/logger" |
|
|
|
|
"github.com/minio/minio/pkg/dns" |
|
|
|
|
"github.com/minio/minio/pkg/event" |
|
|
|
|
"github.com/minio/minio/pkg/hash" |
|
|
|
|
"github.com/minio/minio/pkg/ioutil" |
|
|
|
@ -538,11 +540,35 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re |
|
|
|
|
|
|
|
|
|
var objInfo ObjectInfo |
|
|
|
|
|
|
|
|
|
// _, err = objectAPI.GetBucketInfo(ctx, dstBucket)
|
|
|
|
|
// if err == toObjectErr(errVolumeNotFound, dstBucket) && !cpSrcDstSame
|
|
|
|
|
// Checks if a remote putobject call is needed for CopyObject operation
|
|
|
|
|
// 1. If source and destination bucket names are same, it means no call needed to etcd to get destination info
|
|
|
|
|
// 2. If destination bucket doesn't exist locally, only then a etcd call is needed
|
|
|
|
|
var isRemoteCallRequired = func(ctx context.Context, src, dst string, objAPI ObjectLayer) bool { |
|
|
|
|
if src == dst { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
_, berr := objAPI.GetBucketInfo(ctx, dst) |
|
|
|
|
return berr == toObjectErr(errVolumeNotFound, dst) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Returns a minio-go Client configured to access remote host described by destDNSRecord
|
|
|
|
|
// Applicable only in a federated deployment
|
|
|
|
|
var getRemoteInstanceClient = func(host string, port int) (*miniogo.Core, error) { |
|
|
|
|
// In a federated deployment, all the instances share config files and hence expected to have same
|
|
|
|
|
// credentials. So, access current instances creds and use it to create client for remote instance
|
|
|
|
|
endpoint := net.JoinHostPort(host, strconv.Itoa(port)) |
|
|
|
|
accessKey := globalServerConfig.Credential.AccessKey |
|
|
|
|
secretKey := globalServerConfig.Credential.SecretKey |
|
|
|
|
return miniogo.NewCore(endpoint, accessKey, secretKey, globalIsSSL) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI) { |
|
|
|
|
if globalDNSConfig != nil { |
|
|
|
|
if dstRecord, errEtcd := globalDNSConfig.Get(dstBucket); errEtcd == nil { |
|
|
|
|
if globalDNSConfig == nil { |
|
|
|
|
writeErrorResponse(w, ErrNoSuchBucket, r.URL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
var dstRecords []dns.SrvRecord |
|
|
|
|
if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil { |
|
|
|
|
go func() { |
|
|
|
|
if gerr := objectAPI.GetObject(ctx, srcBucket, srcObject, 0, srcInfo.Size, srcInfo.Writer, srcInfo.ETag); gerr != nil { |
|
|
|
|
pipeWriter.CloseWithError(gerr) |
|
|
|
@ -550,10 +576,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
// Close writer explicitly to indicate data has been written
|
|
|
|
|
defer srcInfo.Writer.Close() |
|
|
|
|
srcInfo.Writer.Close() |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
// Send PutObject request to appropriate instance (in federated deployment)
|
|
|
|
|
client, rerr := getRemoteInstanceClient(dstRecord[0]) |
|
|
|
|
host, port := getRandomHostPort(dstRecords) |
|
|
|
|
client, rerr := getRemoteInstanceClient(host, port) |
|
|
|
|
if rerr != nil { |
|
|
|
|
pipeWriter.CloseWithError(rerr) |
|
|
|
|
writeErrorResponse(w, ErrInternalError, r.URL) |
|
|
|
@ -568,10 +596,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re |
|
|
|
|
objInfo.ETag = remoteObjInfo.ETag |
|
|
|
|
objInfo.ModTime = remoteObjInfo.LastModified |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
writeErrorResponse(w, ErrNoSuchBucket, r.URL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Copy source object to destination, if source and destination
|
|
|
|
|
// object is same then only metadata is updated.
|
|
|
|
|