mirror of
https://github.com/pgsty/minio.git
synced 2026-10-31 21:02:06 +03:00
fix: return and persist checksum on federated CopyObject (#99)
The legacy etcd federation branch of CopyObjectHandler forwards the copied bytes with minio-go Core.PutObject but never asked the remote for a checksum and discarded any it returned, so a cross-deployment whole-object copy that requested a checksum returned 200 with an empty checksum, and a checksum-less source did not gain the S3 default CRC-64NVME that the local path assigns. The request was neither honored nor rejected. This is the whole-object counterpart of #72, which repaired the same class of defect for federated UploadPartCopy. When a server-side checksum is wanted -- explicitly requested, inherited from a multipart source, or the CRC-64NVME default for a checksum-less object, all already captured in dstOpts.WantServerSideChecksumType -- the forwarded PutObject now streams a trailing checksum of that type, so the remote computes and persists it and echoes it in the response. The value the remote reports for that exact write is bound into objInfo.Checksum, matching how the local CopyObject path carries checksums into the CopyObjectResult. Reading the value from the same UploadInfo that produced the ETag keeps the pair bound to one write. Only the requested algorithm is returned; a malformed or absent remote value leaves objInfo.Checksum unset, so an ordinary copy that wanted no checksum still returns none. Two small mapping helpers convert between the server's hash.ChecksumType and the minio-go request type and response field. New end-to-end tests drive the real federation branch through getRemoteInstanceClient and minio-go into a second in-process deployment and assert that CRC32/CRC32C/SHA256/CRC64NVME and the no-algorithm default are all returned in the CopyObjectResult and persisted on the destination, and that a requested algorithm never leaks other algorithms into the response. Fixes #99 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L7qJqWwy8oFA6aCXWRzXQe Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -1217,6 +1217,43 @@ var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core,
|
||||
return core, nil
|
||||
}
|
||||
|
||||
// federatedChecksumType maps a requested server-side checksum type to the
|
||||
// minio-go checksum the federation proxy asks the remote deployment to compute
|
||||
// on the forwarded PutObject. Returns ChecksumNone for an unset/unknown type.
|
||||
func federatedChecksumType(t hash.ChecksumType) miniogo.ChecksumType {
|
||||
switch t.Base() {
|
||||
case hash.ChecksumCRC32:
|
||||
return miniogo.ChecksumCRC32
|
||||
case hash.ChecksumCRC32C:
|
||||
return miniogo.ChecksumCRC32C
|
||||
case hash.ChecksumSHA1:
|
||||
return miniogo.ChecksumSHA1
|
||||
case hash.ChecksumSHA256:
|
||||
return miniogo.ChecksumSHA256
|
||||
case hash.ChecksumCRC64NVME:
|
||||
return miniogo.ChecksumCRC64NVME
|
||||
}
|
||||
return miniogo.ChecksumNone
|
||||
}
|
||||
|
||||
// federatedChecksumValue returns the base64 checksum the remote deployment
|
||||
// reported for the requested type on the forwarded PutObject.
|
||||
func federatedChecksumValue(t hash.ChecksumType, info miniogo.UploadInfo) string {
|
||||
switch t.Base() {
|
||||
case hash.ChecksumCRC32:
|
||||
return info.ChecksumCRC32
|
||||
case hash.ChecksumCRC32C:
|
||||
return info.ChecksumCRC32C
|
||||
case hash.ChecksumSHA1:
|
||||
return info.ChecksumSHA1
|
||||
case hash.ChecksumSHA256:
|
||||
return info.ChecksumSHA256
|
||||
case hash.ChecksumCRC64NVME:
|
||||
return info.ChecksumCRC64NVME
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if the destination bucket is on a remote site, this code only gets executed
|
||||
// when federation is enabled, ie when globalDNSConfig is non 'nil'.
|
||||
//
|
||||
@@ -1901,6 +1938,17 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserTags: tag.ToMap(),
|
||||
}
|
||||
// When a server-side checksum was requested (explicitly, inherited from
|
||||
// the source, or the S3 default for a checksum-less object), the local
|
||||
// path computes, persists and returns it; the federated path must do the
|
||||
// same. Ask the remote to compute and persist that checksum by streaming
|
||||
// it as a trailing checksum, so the forwarded write's response carries
|
||||
// the value back to us. Without this the federated copy silently returns
|
||||
// an empty checksum (#99).
|
||||
wantChecksumType := dstOpts.WantServerSideChecksumType
|
||||
if wantChecksumType.IsSet() {
|
||||
opts.Checksum = federatedChecksumType(wantChecksumType)
|
||||
}
|
||||
remoteObjInfo, rerr := core.PutObject(ctx, dstBucket, dstObject, srcInfo.Reader,
|
||||
srcInfo.Size, "", "", opts)
|
||||
if rerr != nil {
|
||||
@@ -1910,6 +1958,16 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
objInfo.UserDefined = cloneMSS(opts.UserMetadata)
|
||||
objInfo.ETag = remoteObjInfo.ETag
|
||||
objInfo.ModTime = remoteObjInfo.LastModified
|
||||
// Bind the checksum the remote computed for this exact write into the
|
||||
// response, matching the local CopyObject path and the federated
|
||||
// UploadPartCopy repair in #72. A malformed or absent remote value
|
||||
// leaves objInfo.Checksum unset, so an ordinary copy returns none.
|
||||
if wantChecksumType.IsSet() {
|
||||
if cs := hash.NewChecksumWithType(wantChecksumType,
|
||||
federatedChecksumValue(wantChecksumType, remoteObjInfo)); cs != nil {
|
||||
objInfo.Checksum = cs.AppendTo(nil, nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
os = newObjSweeper(dstBucket, dstObject).WithVersioning(dstOpts.Versioned, dstOpts.VersionSuspended)
|
||||
// Get appropriate object info to identify the remote object to delete
|
||||
|
||||
Reference in New Issue
Block a user