diff --git a/weed/server/filer_grpc_server_condition.go b/weed/server/filer_grpc_server_condition.go index 7addf586c..f51b0dcef 100644 --- a/weed/server/filer_grpc_server_condition.go +++ b/weed/server/filer_grpc_server_condition.go @@ -84,10 +84,13 @@ func clauseSatisfied(c *filer_pb.WriteCondition_Clause, current *filer.Entry) bo case filer_pb.WriteCondition_IF_CHUNKS_EQUAL: return chunkFidsEqual(current, c.Fids) case filer_pb.WriteCondition_IF_ENTRY_EQUAL: - if !exists { - return c.ExpectedEntry == nil + if !exists || c.ExpectedEntry == nil { + return !exists && c.ExpectedEntry == nil } - return proto.Equal(current.ToProtoEntry(), c.ExpectedEntry) + // 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()) 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 66906fafb..2946e9614 100644 --- a/weed/server/filer_grpc_server_condition_test.go +++ b/weed/server/filer_grpc_server_condition_test.go @@ -193,6 +193,34 @@ func TestWriteConditionUnknownKindFailsClosed(t *testing.T) { } } +// IF_ENTRY_EQUAL compares the expected entry after the same normalization +// FindEntry applied to the stored one: a raw event entry whose FileSize is +// still zero must match a stored entry grown to its chunk extent. +func TestIfEntryEqualNormalizesExpected(t *testing.T) { + raw := &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}, + }, + } + stored := filer.FromPbEntry("/d", raw) + if stored.FileSize != 100 { + t.Fatalf("stored FileSize = %d, want chunk extent 100", stored.FileSize) + } + cond := one(&filer_pb.WriteCondition_Clause{ + Kind: filer_pb.WriteCondition_IF_ENTRY_EQUAL, + ExpectedEntry: raw, + }) + if !writeConditionSatisfied(cond, stored) { + t.Error("raw expected entry must equal its normalized stored form") + } + raw.Attributes.Mtime = 43 + if writeConditionSatisfied(cond, stored) { + t.Error("changed expected entry must not equal the 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) {