filer: evaluate a write precondition in CreateEntry

Add an optional WriteCondition to CreateEntryRequest. When set, the filer
evaluates it against the current entry while holding the per-path lock, so the
check and the write are atomic on this filer, and returns PRECONDITION_FAILED
when it does not hold.

The condition is a single primitive (exists / not-exists / etag match / etag
mismatch / [un]modified-since). The caller reduces request semantics such as
RFC 7232 precedence to one primitive; the filer just compares. ETag comparison
mirrors the S3 gateway's precedence (stored Seaweed ETag attribute, then the
Md5/chunk fallback) so results match without coupling the filer to S3 handling.
This commit is contained in:
Chris Lu
2026-05-22 22:30:26 -07:00
parent 56c51e7f50
commit 4e421400e4
5 changed files with 779 additions and 406 deletions
@@ -222,6 +222,29 @@ message CreateEntryRequest {
bool is_from_other_cluster = 4;
repeated int32 signatures = 5;
bool skip_check_parent_directory = 6;
// Optional precondition evaluated against the current entry atomically with
// the write, under the filer's per-path lock. The caller must route the
// key's writes to this entry's owner filer for the check to be authoritative.
WriteCondition condition = 7;
}
// WriteCondition is a single precondition the filer evaluates against the
// existing entry before writing. The client reduces request semantics (e.g.
// RFC 7232 precedence) to one primitive; the filer just compares. A failed
// condition returns FilerError PRECONDITION_FAILED.
message WriteCondition {
enum Kind {
NONE = 0; // unconditional
IF_NOT_EXISTS = 1; // fail if the entry exists (If-None-Match: *)
IF_EXISTS = 2; // fail if the entry is absent (If-Match: *)
IF_ETAG_MATCH = 3; // fail if absent or etag != expected (If-Match: <etag>)
IF_ETAG_NOT_MATCH = 4; // fail if present and etag == expected (If-None-Match: <etag>)
IF_UNMODIFIED_SINCE = 5; // fail if present and mtime > unix_time
IF_MODIFIED_SINCE = 6; // fail if present and mtime <= unix_time
}
Kind kind = 1;
string etag = 2; // expected ETag for IF_ETAG_* kinds
int64 unix_time = 3; // bound (unix seconds) for IF_*_SINCE kinds
}
// Structured error codes for filer entry operations.
@@ -233,6 +256,7 @@ enum FilerError {
EXISTING_IS_DIRECTORY = 3; // cannot overwrite directory with file
EXISTING_IS_FILE = 4; // cannot overwrite file with directory
ENTRY_ALREADY_EXISTS = 5; // O_EXCL and entry already exists
PRECONDITION_FAILED = 6; // WriteCondition not satisfied
}
message CreateEntryResponse {
@@ -421,6 +445,7 @@ message SubscribeMetadataRequest {
repeated string directories = 10; // exact directory to watch
bool client_supports_batching = 11; // client can unpack SubscribeMetadataResponse.events
bool client_supports_metadata_chunks = 12; // client can read log file chunks from volume servers
bool client_supports_idle_heartbeat = 13; // server may send empty responses carrying the current time while the client is caught up
}
message SubscribeMetadataResponse {
string directory = 1;