mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-30 11:45:42 +00:00
filer: compare IF_ENTRY_EQUAL chunks by fid, not file_id (#11514)
The stored entry came through FindEntry, which restores chunk file ids from their fid form, while an expected entry built from a metadata-log event still carries the serialized form (file_id moved into fid). The proto.Equal saw file_id "" against the restored id and refused every stamp, so remote.sync re-uploaded each entry and the RemoteEntry stamp never landed. Clone both sides and run BeforeEntrySerialization before comparing, so chunks match on their fid and the file_id spelling is ignored; the stored entry and the request's ExpectedEntry are left untouched. Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
67b19b2db5
commit
a901c1a5e2
@@ -87,10 +87,14 @@ func clauseSatisfied(c *filer_pb.WriteCondition_Clause, current *filer.Entry) bo
|
||||
if !exists || c.ExpectedEntry == nil {
|
||||
return !exists && c.ExpectedEntry == nil
|
||||
}
|
||||
// Normalize the expected entry the way FindEntry normalizes the stored
|
||||
// one (e.g. FileSize grows to the chunk extent), or an unchanged entry
|
||||
// can compare unequal.
|
||||
return proto.Equal(current.ToProtoEntry(), filer.FromPbEntry("", c.ExpectedEntry).ToProtoEntry())
|
||||
// Compare both sides in serialized form on clones: chunks are matched
|
||||
// by their fid only — the stored entry may carry the restored file_id
|
||||
// while an expected one built from a metadata event does not.
|
||||
expected := proto.Clone(c.ExpectedEntry).(*filer_pb.Entry)
|
||||
filer_pb.BeforeEntrySerialization(expected.Chunks)
|
||||
actual := proto.Clone(current.ToProtoEntry()).(*filer_pb.Entry)
|
||||
filer_pb.BeforeEntrySerialization(actual.Chunks)
|
||||
return proto.Equal(actual, filer.FromPbEntry("", expected).ToProtoEntry())
|
||||
default:
|
||||
// An unrecognized clause kind (e.g. from a newer client) must not be
|
||||
// treated as satisfied, which would silently bypass the guard. Fail
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func entryWithETag(etag string, mtime time.Time) *filer.Entry {
|
||||
@@ -221,6 +222,30 @@ func TestIfEntryEqualNormalizesExpected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A stamp built from the metadata-log event carries chunks in serialized
|
||||
// form (file_id moved into fid), while the stored entry came through
|
||||
// FindEntry which restores file_id. The comparison must still match.
|
||||
func TestIfEntryEqualSerializedExpected(t *testing.T) {
|
||||
serialized := &filer_pb.Entry{
|
||||
Name: "f",
|
||||
Attributes: &filer_pb.FuseAttributes{Mtime: 42},
|
||||
Chunks: []*filer_pb.FileChunk{
|
||||
{Fid: &filer_pb.FileId{VolumeId: 3, FileKey: 1, Cookie: 2}, Size: 100},
|
||||
},
|
||||
}
|
||||
storedProto := proto.Clone(serialized).(*filer_pb.Entry)
|
||||
filer_pb.AfterEntryDeserialization(storedProto.Chunks)
|
||||
stored := filer.FromPbEntry("/d", storedProto)
|
||||
|
||||
cond := one(&filer_pb.WriteCondition_Clause{
|
||||
Kind: filer_pb.WriteCondition_IF_ENTRY_EQUAL,
|
||||
ExpectedEntry: serialized,
|
||||
})
|
||||
if !writeConditionSatisfied(cond, stored) {
|
||||
t.Error("serialized expected entry must equal the deserialized stored entry")
|
||||
}
|
||||
}
|
||||
|
||||
// storedEntryETag prefers the stored Seaweed ETag attribute and falls back to
|
||||
// the Md5-derived ETag, matching the S3 gateway.
|
||||
func TestStoredEntryETag(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user