From 8541f959a93da49380e19237fd09ad2dbfd122fd Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 23 Oct 2026 20:45:46 -0700 Subject: [PATCH] filer.conf: merge fsync on presence like worm (#11953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * filer.conf: merge fsync on presence like worm fsync is a pure preference, so OR-merging it meant a nested rule could never turn a parent's fsync off. Make PathConf.fsync optional and merge it most-specific-wins, matching worm: an unset rule inherits, an explicit value overrides in either direction. readOnly and disableChunkDeletion stay OR'ed — they are fail-closed locks quota enforcement depends on. Pre-version-2 configs were written with EmitUnpopulated, so every rule carries an explicit fsync=false that meant nothing; it is dropped to unset on load so it does not read as an opt-out. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer.conf: test fsync override survives a save/reload round trip --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- other/java/client/src/main/proto/filer.proto | 3 +- weed/filer/filer.go | 2 +- weed/filer/filer_conf.go | 33 ++++++---- weed/filer/filer_conf_test.go | 69 +++++++++++++++++++- weed/pb/filer.proto | 3 +- weed/pb/filer_pb/filer.pb.go | 46 ++++++------- weed/pb/filer_pb/filer_grpc.pb.go | 2 +- weed/pb/filer_pb/filer_vtproto.pb.go | 42 ++++++++++-- weed/server/filer_server_handlers_write.go | 2 +- weed/shell/command_fs_configure.go | 26 ++++---- 10 files changed, 171 insertions(+), 57 deletions(-) diff --git a/other/java/client/src/main/proto/filer.proto b/other/java/client/src/main/proto/filer.proto index a327bdb17c..93e7e3ed32 100644 --- a/other/java/client/src/main/proto/filer.proto +++ b/other/java/client/src/main/proto/filer.proto @@ -771,7 +771,8 @@ message FilerConf { string replication = 3; string ttl = 4; string disk_type = 5; - bool fsync = 6; + // unset inherits from the enclosing path rule, set overrides it + optional bool fsync = 6; uint32 volume_growth_count = 7; bool read_only = 8; string data_center = 9; diff --git a/weed/filer/filer.go b/weed/filer/filer.go index 5a0090e61b..59e9bfc714 100644 --- a/weed/filer/filer.go +++ b/weed/filer/filer.go @@ -434,7 +434,7 @@ func SyncWrite(ctx context.Context) bool { // syncWriteContext flags ctx when the storage rule on p asks for fsync, so the // store write is held to the same durability as the data path. func (f *Filer) syncWriteContext(ctx context.Context, p util.FullPath) context.Context { - if f.FilerConf != nil && f.FilerConf.MatchStorageRule(string(p)).Fsync { + if f.FilerConf != nil && f.FilerConf.MatchStorageRule(string(p)).GetFsync() { return WithSyncWrite(ctx) } return ctx diff --git a/weed/filer/filer_conf.go b/weed/filer/filer_conf.go index 55b8d8fc4d..e38235be41 100644 --- a/weed/filer/filer_conf.go +++ b/weed/filer/filer_conf.go @@ -33,10 +33,11 @@ const ( ) // FilerConfVersion is stamped into every configuration this build writes. -// Version 0 predates worm presence: it was written with EmitUnpopulated, so every -// rule carries an explicit "worm": false that meant nothing. Reading one back as an -// override would silently lift worm off nested paths, so it is dropped to unset. -const FilerConfVersion = 1 +// Version 0 predates worm presence and version 1 predates fsync presence: both +// were written with EmitUnpopulated, so every rule carries an explicit false +// that meant nothing. Reading one back as an override would silently lift the +// setting off nested paths, so a false from an older version is dropped to unset. +const FilerConfVersion = 2 type FilerConf struct { rules ptrie.Trie[*filer_pb.FilerConf_PathConf] @@ -126,9 +127,12 @@ func (fc *FilerConf) LoadFromBytes(data []byte) (err error) { func (fc *FilerConf) doLoadConf(conf *filer_pb.FilerConf) (err error) { for _, location := range conf.Locations { - if conf.Version < FilerConfVersion && location.Worm != nil && !*location.Worm { + if conf.Version < 1 && location.Worm != nil && !*location.Worm { location.Worm = nil } + if conf.Version < 2 && location.Fsync != nil && !*location.Fsync { + location.Fsync = nil + } err = fc.SetLocationConf(location) if err != nil { // this is not recoverable @@ -227,7 +231,10 @@ func ClonePathConf(src *filer_pb.FilerConf_PathConf) *filer_pb.FilerConf_PathCon if src == nil { return &filer_pb.FilerConf_PathConf{} } - var worm *bool + var fsync, worm *bool + if src.Fsync != nil { + fsync = proto.Bool(*src.Fsync) + } if src.Worm != nil { worm = proto.Bool(*src.Worm) } @@ -237,7 +244,7 @@ func ClonePathConf(src *filer_pb.FilerConf_PathConf) *filer_pb.FilerConf_PathCon Replication: src.Replication, Ttl: src.Ttl, DiskType: src.DiskType, - Fsync: src.Fsync, + Fsync: fsync, VolumeGrowthCount: src.VolumeGrowthCount, ReadOnly: src.ReadOnly, MaxFileNameLength: src.MaxFileNameLength, @@ -484,7 +491,7 @@ func ClearBucketLifecycleDayTTLs(ctx context.Context, client filer_pb.SeaweedFil // flag, a placement pin - is an operator's, and deleting the whole rule to // retire its TTL would take their configuration with it. func isLifecycleOwnedPathConf(c *filer_pb.FilerConf_PathConf) bool { - return c.GetDiskType() == "" && !c.GetFsync() && !c.GetReadOnly() && + return c.GetDiskType() == "" && c.Fsync == nil && !c.GetReadOnly() && c.GetDataCenter() == "" && c.GetRack() == "" && c.GetDataNode() == "" && c.GetMaxFileNameLength() == 0 && !c.GetDisableChunkDeletion() && c.Worm == nil && c.GetWormGracePeriodSeconds() == 0 && c.GetWormRetentionTimeSeconds() == 0 @@ -507,7 +514,12 @@ func mergePathConf(a, b *filer_pb.FilerConf_PathConf) { a.Replication = util.Nvl(b.Replication, a.Replication) a.Ttl = util.Nvl(b.Ttl, a.Ttl) a.DiskType = util.Nvl(b.DiskType, a.DiskType) - a.Fsync = b.Fsync || a.Fsync + // fsync and worm merge on presence, so a nested rule can turn them off. + // readOnly and disableChunkDeletion stay OR'ed on purpose: a nested rule + // must not be able to lift a lock the bucket set. + if b.Fsync != nil { + a.Fsync = proto.Bool(*b.Fsync) + } if b.VolumeGrowthCount > 0 { a.VolumeGrowthCount = b.VolumeGrowthCount } @@ -519,9 +531,6 @@ func mergePathConf(a, b *filer_pb.FilerConf_PathConf) { a.Rack = util.Nvl(b.Rack, a.Rack) a.DataNode = util.Nvl(b.DataNode, a.DataNode) a.DisableChunkDeletion = b.DisableChunkDeletion || a.DisableChunkDeletion - // worm merges on presence, so a nested rule can turn it off. readOnly, fsync and - // disableChunkDeletion stay OR'ed on purpose: a nested rule must not be able to - // lift a lock the bucket set. if b.Worm != nil { // copy the value: a is often a scratch conf while b is a live trie entry a.Worm = proto.Bool(*b.Worm) diff --git a/weed/filer/filer_conf_test.go b/weed/filer/filer_conf_test.go index b71da89b68..24f2ca591b 100644 --- a/weed/filer/filer_conf_test.go +++ b/weed/filer/filer_conf_test.go @@ -102,6 +102,54 @@ func TestWormLegacyFalseIsNotAnOverride(t *testing.T) { assert.False(t, fc.MatchStorageRule("/buckets/b/sub/x").GetWorm()) } +func TestFsyncInheritance(t *testing.T) { + fc := NewFilerConf() + fc.doLoadConf(&filer_pb.FilerConf{ + Version: FilerConfVersion, + Locations: []*filer_pb.FilerConf_PathConf{ + {LocationPrefix: "/buckets/b/", Fsync: proto.Bool(true), Ttl: "7d"}, + {LocationPrefix: "/buckets/b/quiet/", Collection: "quiet"}, + {LocationPrefix: "/buckets/b/scratch/", Fsync: proto.Bool(false)}, + {LocationPrefix: "/buckets/b/scratch/keep/", Fsync: proto.Bool(true)}, + }, + }) + + // a rule that says nothing about fsync keeps inheriting it, along with the ttl + rule := fc.MatchStorageRule("/buckets/b/quiet/x") + assert.True(t, rule.GetFsync()) + assert.Equal(t, "7d", rule.Ttl) + + // an explicit false turns it off, and a deeper rule turns it back on + assert.False(t, fc.MatchStorageRule("/buckets/b/scratch/x").GetFsync()) + assert.True(t, fc.MatchStorageRule("/buckets/b/scratch/keep/x").GetFsync()) + + // paths with no rule at all are unaffected + assert.False(t, fc.MatchStorageRule("/buckets/other/x").GetFsync()) +} + +// TestFsyncLegacyFalseIsNotAnOverride pins that the explicit "fsync": false +// every pre-version-2 configuration carries does not read as a per-path +// opt-out. +func TestFsyncLegacyFalseIsNotAnOverride(t *testing.T) { + const conf = `{ + "locations": [ + {"locationPrefix": "/buckets/b/", "fsync": true}, + {"locationPrefix": "/buckets/b/sub/", "collection": "sub", "fsync": false} + ] + }` + + for _, version := range []string{"", `"version": 0,`, `"version": 1,`} { + fc := NewFilerConf() + assert.NoError(t, fc.LoadFromBytes([]byte(`{`+version+conf[1:]))) + assert.True(t, fc.MatchStorageRule("/buckets/b/sub/x").GetFsync(), version) + } + + // the same file at the current version means what it says + fc := NewFilerConf() + assert.NoError(t, fc.LoadFromBytes([]byte(`{"version": 2,`+conf[1:]))) + assert.False(t, fc.MatchStorageRule("/buckets/b/sub/x").GetFsync()) +} + // TestWormSurvivesRoundTrip guards the write side: an explicit false has to be // stamped along with a version that says to honor it. func TestWormSurvivesRoundTrip(t *testing.T) { @@ -119,6 +167,23 @@ func TestWormSurvivesRoundTrip(t *testing.T) { assert.True(t, reloaded.MatchStorageRule("/buckets/b/other/x").GetWorm()) } +// TestFsyncSurvivesRoundTrip guards the write side: an explicit false has to be +// stamped along with a version that says to honor it. +func TestFsyncSurvivesRoundTrip(t *testing.T) { + fc := NewFilerConf() + fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/", Fsync: proto.Bool(true)}) + fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/sub/", Fsync: proto.Bool(false)}) + fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/other/", Ttl: "7d"}) + + var buf bytes.Buffer + assert.NoError(t, fc.ToText(&buf)) + + reloaded := NewFilerConf() + assert.NoError(t, reloaded.LoadFromBytes(buf.Bytes())) + assert.False(t, reloaded.MatchStorageRule("/buckets/b/sub/x").GetFsync()) + assert.True(t, reloaded.MatchStorageRule("/buckets/b/other/x").GetFsync()) +} + // TestClonePathConf verifies that ClonePathConf copies all exported fields. // Uses reflection to automatically detect new fields added to the protobuf, // ensuring the test fails if ClonePathConf is not updated for new fields. @@ -130,7 +195,7 @@ func TestClonePathConf(t *testing.T) { Replication: "001", Ttl: "7d", DiskType: "ssd", - Fsync: true, + Fsync: proto.Bool(true), VolumeGrowthCount: 5, ReadOnly: true, MaxFileNameLength: 255, @@ -189,9 +254,11 @@ func TestClonePathConf(t *testing.T) { // Verify mutation of clone doesn't affect source clone.Collection = "modified" clone.ReadOnly = false + *clone.Fsync = false *clone.Worm = false assert.Equal(t, "test_collection", src.Collection, "Modifying clone should not affect source Collection") assert.Equal(t, true, src.ReadOnly, "Modifying clone should not affect source ReadOnly") + assert.Equal(t, true, src.GetFsync(), "Modifying clone should not affect source Fsync") assert.Equal(t, true, src.GetWorm(), "Modifying clone should not affect source Worm") } diff --git a/weed/pb/filer.proto b/weed/pb/filer.proto index dca77d2eca..1919108fcd 100644 --- a/weed/pb/filer.proto +++ b/weed/pb/filer.proto @@ -786,7 +786,8 @@ message FilerConf { string replication = 3; string ttl = 4; string disk_type = 5; - bool fsync = 6; + // unset inherits from the enclosing path rule, set overrides it + optional bool fsync = 6; uint32 volume_growth_count = 7; bool read_only = 8; string data_center = 9; diff --git a/weed/pb/filer_pb/filer.pb.go b/weed/pb/filer_pb/filer.pb.go index 94a21acc92..f00ed17771 100644 --- a/weed/pb/filer_pb/filer.pb.go +++ b/weed/pb/filer_pb/filer.pb.go @@ -7102,20 +7102,21 @@ func (x *LocateBrokerResponse_Resource) GetResourceCount() int32 { } type FilerConf_PathConf struct { - state protoimpl.MessageState `protogen:"open.v1"` - LocationPrefix string `protobuf:"bytes,1,opt,name=location_prefix,json=locationPrefix,proto3" json:"location_prefix,omitempty"` - Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"` - Replication string `protobuf:"bytes,3,opt,name=replication,proto3" json:"replication,omitempty"` - Ttl string `protobuf:"bytes,4,opt,name=ttl,proto3" json:"ttl,omitempty"` - DiskType string `protobuf:"bytes,5,opt,name=disk_type,json=diskType,proto3" json:"disk_type,omitempty"` - Fsync bool `protobuf:"varint,6,opt,name=fsync,proto3" json:"fsync,omitempty"` - VolumeGrowthCount uint32 `protobuf:"varint,7,opt,name=volume_growth_count,json=volumeGrowthCount,proto3" json:"volume_growth_count,omitempty"` - ReadOnly bool `protobuf:"varint,8,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` - DataCenter string `protobuf:"bytes,9,opt,name=data_center,json=dataCenter,proto3" json:"data_center,omitempty"` - Rack string `protobuf:"bytes,10,opt,name=rack,proto3" json:"rack,omitempty"` - DataNode string `protobuf:"bytes,11,opt,name=data_node,json=dataNode,proto3" json:"data_node,omitempty"` - MaxFileNameLength uint32 `protobuf:"varint,12,opt,name=max_file_name_length,json=maxFileNameLength,proto3" json:"max_file_name_length,omitempty"` - DisableChunkDeletion bool `protobuf:"varint,13,opt,name=disable_chunk_deletion,json=disableChunkDeletion,proto3" json:"disable_chunk_deletion,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + LocationPrefix string `protobuf:"bytes,1,opt,name=location_prefix,json=locationPrefix,proto3" json:"location_prefix,omitempty"` + Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"` + Replication string `protobuf:"bytes,3,opt,name=replication,proto3" json:"replication,omitempty"` + Ttl string `protobuf:"bytes,4,opt,name=ttl,proto3" json:"ttl,omitempty"` + DiskType string `protobuf:"bytes,5,opt,name=disk_type,json=diskType,proto3" json:"disk_type,omitempty"` + // unset inherits from the enclosing path rule, set overrides it + Fsync *bool `protobuf:"varint,6,opt,name=fsync,proto3,oneof" json:"fsync,omitempty"` + VolumeGrowthCount uint32 `protobuf:"varint,7,opt,name=volume_growth_count,json=volumeGrowthCount,proto3" json:"volume_growth_count,omitempty"` + ReadOnly bool `protobuf:"varint,8,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + DataCenter string `protobuf:"bytes,9,opt,name=data_center,json=dataCenter,proto3" json:"data_center,omitempty"` + Rack string `protobuf:"bytes,10,opt,name=rack,proto3" json:"rack,omitempty"` + DataNode string `protobuf:"bytes,11,opt,name=data_node,json=dataNode,proto3" json:"data_node,omitempty"` + MaxFileNameLength uint32 `protobuf:"varint,12,opt,name=max_file_name_length,json=maxFileNameLength,proto3" json:"max_file_name_length,omitempty"` + DisableChunkDeletion bool `protobuf:"varint,13,opt,name=disable_chunk_deletion,json=disableChunkDeletion,proto3" json:"disable_chunk_deletion,omitempty"` // unset inherits from the enclosing path rule, set overrides it Worm *bool `protobuf:"varint,14,opt,name=worm,proto3,oneof" json:"worm,omitempty"` WormGracePeriodSeconds uint64 `protobuf:"varint,15,opt,name=worm_grace_period_seconds,json=wormGracePeriodSeconds,proto3" json:"worm_grace_period_seconds,omitempty"` @@ -7190,8 +7191,8 @@ func (x *FilerConf_PathConf) GetDiskType() string { } func (x *FilerConf_PathConf) GetFsync() bool { - if x != nil { - return x.Fsync + if x != nil && x.Fsync != nil { + return *x.Fsync } return false } @@ -7764,10 +7765,10 @@ const file_filer_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\fR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\fR\x05value\"%\n" + "\rKvPutResponse\x12\x14\n" + - "\x05error\x18\x01 \x01(\tR\x05error\"\xc0\x05\n" + + "\x05error\x18\x01 \x01(\tR\x05error\"\xcf\x05\n" + "\tFilerConf\x12\x18\n" + "\aversion\x18\x01 \x01(\x05R\aversion\x12:\n" + - "\tlocations\x18\x02 \x03(\v2\x1c.filer_pb.FilerConf.PathConfR\tlocations\x1a\xdc\x04\n" + + "\tlocations\x18\x02 \x03(\v2\x1c.filer_pb.FilerConf.PathConfR\tlocations\x1a\xeb\x04\n" + "\bPathConf\x12'\n" + "\x0flocation_prefix\x18\x01 \x01(\tR\x0elocationPrefix\x12\x1e\n" + "\n" + @@ -7775,8 +7776,8 @@ const file_filer_proto_rawDesc = "" + "collection\x12 \n" + "\vreplication\x18\x03 \x01(\tR\vreplication\x12\x10\n" + "\x03ttl\x18\x04 \x01(\tR\x03ttl\x12\x1b\n" + - "\tdisk_type\x18\x05 \x01(\tR\bdiskType\x12\x14\n" + - "\x05fsync\x18\x06 \x01(\bR\x05fsync\x12.\n" + + "\tdisk_type\x18\x05 \x01(\tR\bdiskType\x12\x19\n" + + "\x05fsync\x18\x06 \x01(\bH\x00R\x05fsync\x88\x01\x01\x12.\n" + "\x13volume_growth_count\x18\a \x01(\rR\x11volumeGrowthCount\x12\x1b\n" + "\tread_only\x18\b \x01(\bR\breadOnly\x12\x1f\n" + "\vdata_center\x18\t \x01(\tR\n" + @@ -7786,9 +7787,10 @@ const file_filer_proto_rawDesc = "" + "\tdata_node\x18\v \x01(\tR\bdataNode\x12/\n" + "\x14max_file_name_length\x18\f \x01(\rR\x11maxFileNameLength\x124\n" + "\x16disable_chunk_deletion\x18\r \x01(\bR\x14disableChunkDeletion\x12\x17\n" + - "\x04worm\x18\x0e \x01(\bH\x00R\x04worm\x88\x01\x01\x129\n" + + "\x04worm\x18\x0e \x01(\bH\x01R\x04worm\x88\x01\x01\x129\n" + "\x19worm_grace_period_seconds\x18\x0f \x01(\x04R\x16wormGracePeriodSeconds\x12=\n" + - "\x1bworm_retention_time_seconds\x18\x10 \x01(\x04R\x18wormRetentionTimeSecondsB\a\n" + + "\x1bworm_retention_time_seconds\x18\x10 \x01(\x04R\x18wormRetentionTimeSecondsB\b\n" + + "\x06_fsyncB\a\n" + "\x05_worm\"\xba\x01\n" + "&CacheRemoteObjectToLocalClusterRequest\x12\x1c\n" + "\tdirectory\x18\x01 \x01(\tR\tdirectory\x12\x12\n" + diff --git a/weed/pb/filer_pb/filer_grpc.pb.go b/weed/pb/filer_pb/filer_grpc.pb.go index 8a330ce286..6a2b494a39 100644 --- a/weed/pb/filer_pb/filer_grpc.pb.go +++ b/weed/pb/filer_pb/filer_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.2 -// - protoc v3.21.12 +// - protoc v7.35.0 // source: filer.proto package filer_pb diff --git a/weed/pb/filer_pb/filer_vtproto.pb.go b/weed/pb/filer_pb/filer_vtproto.pb.go index cac2d01f9a..e86380465e 100644 --- a/weed/pb/filer_pb/filer_vtproto.pb.go +++ b/weed/pb/filer_pb/filer_vtproto.pb.go @@ -2659,6 +2659,16 @@ func (m *AtomicRenameEntryRequest) MarshalToSizedBufferVT(dAtA []byte) (int, err i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.FailIfTargetExists { + i-- + if m.FailIfTargetExists { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } if len(m.Signatures) > 0 { var pksize2 int for _, num := range m.Signatures { @@ -4984,9 +4994,9 @@ func (m *FilerConf_PathConf) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x38 } - if m.Fsync { + if m.Fsync != nil { i-- - if m.Fsync { + if *m.Fsync { dAtA[i] = 1 } else { dAtA[i] = 0 @@ -7599,6 +7609,9 @@ func (m *AtomicRenameEntryRequest) SizeVT() (n int) { } n += 1 + protohelpers.SizeOfVarint(uint64(l)) + l } + if m.FailIfTargetExists { + n += 2 + } n += len(m.unknownFields) return n } @@ -8501,7 +8514,7 @@ func (m *FilerConf_PathConf) SizeVT() (n int) { if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Fsync { + if m.Fsync != nil { n += 2 } if m.VolumeGrowthCount != 0 { @@ -16531,6 +16544,26 @@ func (m *AtomicRenameEntryRequest) UnmarshalVT(dAtA []byte) error { } else { return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType) } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FailIfTargetExists", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.FailIfTargetExists = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -22263,7 +22296,8 @@ func (m *FilerConf_PathConf) UnmarshalVT(dAtA []byte) error { break } } - m.Fsync = bool(v != 0) + b := bool(v != 0) + m.Fsync = &b case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field VolumeGrowthCount", wireType) diff --git a/weed/server/filer_server_handlers_write.go b/weed/server/filer_server_handlers_write.go index 177093c501..9009a0f045 100644 --- a/weed/server/filer_server_handlers_write.go +++ b/weed/server/filer_server_handlers_write.go @@ -337,7 +337,7 @@ func (fs *FilerServer) detectStorageOption(ctx context.Context, requestURI, qCol DataNode: util.Nvl(dataNode, rule.DataNode, fs.option.DataNode), TtlSeconds: ttlSeconds, DiskType: util.Nvl(diskType, overlayDisk, rule.DiskType), - Fsync: rule.Fsync, + Fsync: rule.GetFsync(), VolumeGrowthCount: rule.VolumeGrowthCount, MaxFileNameLength: maxFileNameLength, // A location that survives from a request parameter, a placement diff --git a/weed/shell/command_fs_configure.go b/weed/shell/command_fs_configure.go index 29be9b87ae..4bc29e3984 100644 --- a/weed/shell/command_fs_configure.go +++ b/weed/shell/command_fs_configure.go @@ -67,7 +67,7 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io replication := fsConfigureCommand.String("replication", "", "assign writes with this replication") ttl := fsConfigureCommand.String("ttl", "", "assign writes with this ttl (e.g., 1m, 1h, 1d, 1w, 1y)") diskType := fsConfigureCommand.String("disk", "", "[hdd|ssd|] hard drive or solid state drive or any tag") - fsync := fsConfigureCommand.Bool("fsync", false, "fsync for the writes") + fsync := fsConfigureCommand.Bool("fsync", false, "fsync for the writes; unset inherits from the parent path") isReadOnly := fsConfigureCommand.Bool("readOnly", false, "disable writes") worm := fsConfigureCommand.Bool("worm", false, "write-once-read-many, written files are readonly; unset inherits from the parent path") wormGracePeriod := fsConfigureCommand.Uint64("wormGracePeriod", 0, "grace period before worm is enforced, in seconds") @@ -101,7 +101,6 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io Collection: *collection, Replication: *replication, Ttl: *ttl, - Fsync: *fsync, MaxFileNameLength: uint32(*maxFileNameLength), DiskType: *diskType, VolumeGrowthCount: uint32(*volumeGrowthCount), @@ -113,10 +112,13 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io WormRetentionTimeSeconds: *wormRetentionTime, } - // worm is only carried when the flag is passed, so a rule that says nothing - // about it keeps inheriting from the enclosing path + // fsync and worm are only carried when the flag is passed, so a rule that + // says nothing about them keeps inheriting from the enclosing path fsConfigureCommand.Visit(func(f *flag.Flag) { - if f.Name == "worm" { + switch f.Name { + case "fsync": + locConf.Fsync = proto.Bool(*fsync) + case "worm": locConf.Worm = proto.Bool(*worm) } }) @@ -152,17 +154,15 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io fc.DeleteLocationConf(*locationPrefix) } else { fc.AddLocationConf(locConf) - // AddLocationConf merges these boolean fields with OR, which can never - // turn a flag off; let an explicitly passed false win, e.g. -readOnly=false - // to reopen a bucket that quota enforcement locked. worm does not belong - // here: it merges on presence, so the value set above already wins. + // AddLocationConf merges this boolean field with OR, which can never + // turn it off; let an explicitly passed false win, e.g. -readOnly=false + // to reopen a bucket that quota enforcement locked. fsync and worm do + // not belong here: they merge on presence, so the values set above + // already win. if mergedConf, found := fc.GetLocationConf(*locationPrefix); found { fsConfigureCommand.Visit(func(f *flag.Flag) { - switch f.Name { - case "readOnly": + if f.Name == "readOnly" { mergedConf.ReadOnly = *isReadOnly - case "fsync": - mergedConf.Fsync = *fsync } }) }