s3api: preserve remote object ETags across cache fills (#11704)

* s3api: preserve remote object ETags across cache fills

* s3api: align remote copy validators and reject stale ETags

* s3api: keep remote ETag through metadata touches, drop it after resizes

The remote-ETag check required Mtime to match RemoteMtime, so a utimens
on a cold remote object returned an empty ETag until a later cache fill
restored the remote value. It also trusted LastLocalSyncTsNs alone, so a
setattr resize that leaves chunks and the stamp untouched kept serving
the old remote ETag for different local bytes.

Require FileSize to match RemoteSize for both the synced and remote-only
paths and stop comparing modification times: content changes already
surface through cleared sync stamps, chunks, or inline content, while a
metadata-only touch leaves the remote bytes authoritative. Regression
tests cover touched cold and cached entries and resized cached entries.

---------

Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
This commit is contained in:
Zhe ChenandChris Lu authored and GitHub committed 2026-10-10 23:58:22 +08:00
1 parent 17ba8b3c7c
commit 547befc070
3 files changed
+200 -22

No files matched your search

+1 -21
View File
@@ -601,27 +601,7 @@ func copyEntryETag(entry *filer_pb.Entry) string {
if entry == nil {
return ""
}
if entry.Extended != nil {
if etag, ok := entry.Extended[s3_constants.ExtETagKey]; ok && len(etag) > 0 {
return string(etag)
}
}
attr := filer.Attr{}
if entry.Attributes != nil {
attr = filer.Attr{
FileSize: entry.Attributes.FileSize,
Mtime: time.Unix(entry.Attributes.Mtime, 0),
Crtime: time.Unix(entry.Attributes.Crtime, 0),
Mime: entry.Attributes.Mime,
Md5: entry.Attributes.Md5,
}
}
return filer.ETagEntry(&filer.Entry{
Attr: attr,
Chunks: entry.Chunks,
Content: entry.Content,
Remote: entry.RemoteEntry,
})
return strings.Trim(objectETag(entry), `"`)
}
func copyEntryToTarget(dst, src *filer_pb.Entry) {