s3: add --s3-use-multipart-etag provider quirk #5993

Before this change the new multipart upload ETag checking code was
failing in the integration tests with Alibaba OSS.

Apparently Alibaba calculate the ETag in a different way to AWS.

This introduces a new provider quirk with a flag to disable the
checking of the ETag for multipart uploads.

Mulpart Etag checking has been enabled for all providers that we can
test for and work, and left disabled for the others.
This commit is contained in:
Nick Craig-Wood 2022-02-27 15:47:31 +00:00
parent 71a784cfa2
commit 537b62917f

View file

@ -1548,6 +1548,14 @@ See: https://github.com/rclone/rclone/issues/4673, https://github.com/rclone/rcl
This is usually set to a CloudFront CDN URL as AWS S3 offers This is usually set to a CloudFront CDN URL as AWS S3 offers
cheaper egress for data downloaded through the CloudFront network.`, cheaper egress for data downloaded through the CloudFront network.`,
Advanced: true, Advanced: true,
}, {
Name: "use_multipart_etag",
Help: `Whether to use ETag in multipart uploads for verification
This should be true, false or left unset to use the default for the provider.
`,
Default: fs.Tristate{},
Advanced: true,
}, },
}}) }})
} }
@ -1612,6 +1620,7 @@ type Options struct {
MemoryPoolUseMmap bool `config:"memory_pool_use_mmap"` MemoryPoolUseMmap bool `config:"memory_pool_use_mmap"`
DisableHTTP2 bool `config:"disable_http2"` DisableHTTP2 bool `config:"disable_http2"`
DownloadURL string `config:"download_url"` DownloadURL string `config:"download_url"`
UseMultipartEtag fs.Tristate `config:"use_multipart_etag"`
} }
// Fs represents a remote s3 server // Fs represents a remote s3 server
@ -1915,12 +1924,13 @@ func setQuirks(opt *Options) {
listObjectsV2 = true listObjectsV2 = true
virtualHostStyle = true virtualHostStyle = true
urlEncodeListings = true urlEncodeListings = true
useMultipartEtag = true
) )
switch opt.Provider { switch opt.Provider {
case "AWS": case "AWS":
// No quirks // No quirks
case "Alibaba": case "Alibaba":
// No quirks useMultipartEtag = false // Alibaba seems to calculate multipart Etags differently from AWS
case "Ceph": case "Ceph":
listObjectsV2 = false listObjectsV2 = false
virtualHostStyle = false virtualHostStyle = false
@ -1933,13 +1943,16 @@ func setQuirks(opt *Options) {
listObjectsV2 = false // untested listObjectsV2 = false // untested
virtualHostStyle = false virtualHostStyle = false
urlEncodeListings = false urlEncodeListings = false
useMultipartEtag = false // untested
case "Minio": case "Minio":
virtualHostStyle = false virtualHostStyle = false
case "Netease": case "Netease":
listObjectsV2 = false // untested listObjectsV2 = false // untested
urlEncodeListings = false urlEncodeListings = false
useMultipartEtag = false // untested
case "RackCorp": case "RackCorp":
// No quirks // No quirks
useMultipartEtag = false // untested
case "Scaleway": case "Scaleway":
// Scaleway can only have 1000 parts in an upload // Scaleway can only have 1000 parts in an upload
if opt.MaxUploadParts > 1000 { if opt.MaxUploadParts > 1000 {
@ -1950,6 +1963,7 @@ func setQuirks(opt *Options) {
listObjectsV2 = false // untested listObjectsV2 = false // untested
virtualHostStyle = false virtualHostStyle = false
urlEncodeListings = false urlEncodeListings = false
useMultipartEtag = false // untested
case "StackPath": case "StackPath":
listObjectsV2 = false // untested listObjectsV2 = false // untested
virtualHostStyle = false virtualHostStyle = false
@ -1960,18 +1974,21 @@ func setQuirks(opt *Options) {
opt.ChunkSize = 64 * fs.Mebi opt.ChunkSize = 64 * fs.Mebi
} }
case "TencentCOS": case "TencentCOS":
listObjectsV2 = false // untested listObjectsV2 = false // untested
useMultipartEtag = false // untested
case "Wasabi": case "Wasabi":
// No quirks // No quirks
case "Other": case "Other":
listObjectsV2 = false listObjectsV2 = false
virtualHostStyle = false virtualHostStyle = false
urlEncodeListings = false urlEncodeListings = false
useMultipartEtag = false
default: default:
fs.Logf("s3", "s3 provider %q not known - please set correctly", opt.Provider) fs.Logf("s3", "s3 provider %q not known - please set correctly", opt.Provider)
listObjectsV2 = false listObjectsV2 = false
virtualHostStyle = false virtualHostStyle = false
urlEncodeListings = false urlEncodeListings = false
useMultipartEtag = false
} }
// Path Style vs Virtual Host style // Path Style vs Virtual Host style
@ -1993,6 +2010,12 @@ func setQuirks(opt *Options) {
opt.ListVersion = 1 opt.ListVersion = 1
} }
} }
// Set the correct use multipart Etag for error checking if not manually set
if !opt.UseMultipartEtag.Valid {
opt.UseMultipartEtag.Valid = true
opt.UseMultipartEtag.Value = useMultipartEtag
}
} }
// setRoot changes the root of the Fs // setRoot changes the root of the Fs
@ -3864,7 +3887,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return err return err
} }
o.setMetaData(head.ETag, head.ContentLength, head.LastModified, head.Metadata, head.ContentType, head.StorageClass) o.setMetaData(head.ETag, head.ContentLength, head.LastModified, head.Metadata, head.ContentType, head.StorageClass)
if !o.fs.etagIsNotMD5 && wantETag != "" && head.ETag != nil && *head.ETag != "" { if o.fs.opt.UseMultipartEtag.Value && !o.fs.etagIsNotMD5 && wantETag != "" && head.ETag != nil && *head.ETag != "" {
gotETag := strings.Trim(strings.ToLower(*head.ETag), `"`) gotETag := strings.Trim(strings.ToLower(*head.ETag), `"`)
if wantETag != gotETag { if wantETag != gotETag {
return fmt.Errorf("multipart upload corrupted: Etag differ: expecting %s but got %s", wantETag, gotETag) return fmt.Errorf("multipart upload corrupted: Etag differ: expecting %s but got %s", wantETag, gotETag)