diff --git a/weed/server/filer_grpc_server_condition.go b/weed/server/filer_grpc_server_condition.go index f51b0dcef..dfb803932 100644 --- a/weed/server/filer_grpc_server_condition.go +++ b/weed/server/filer_grpc_server_condition.go @@ -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 diff --git a/weed/server/filer_grpc_server_condition_test.go b/weed/server/filer_grpc_server_condition_test.go index 2946e9614..89086a564 100644 --- a/weed/server/filer_grpc_server_condition_test.go +++ b/weed/server/filer_grpc_server_condition_test.go @@ -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) {