diff --git a/weed/filer/filer.go b/weed/filer/filer.go index 4595c55f4..bd349af67 100644 --- a/weed/filer/filer.go +++ b/weed/filer/filer.go @@ -56,6 +56,7 @@ type Filer struct { MetaAggregator *MetaAggregator Signature int32 FilerConf *FilerConf + placementOverlay PlacementOverlay RemoteStorage *FilerRemoteStorage lazyFetchGroup singleflight.Group lazyListGroup singleflight.Group @@ -96,6 +97,10 @@ func NewFiler(masters pb.ServerDiscovery, grpcDialOption grpc.DialOption, filerH f.metaLogCollection = collection f.metaLogReplication = replication + if newPlacementOverlay != nil { + f.placementOverlay = newPlacementOverlay(f) + } + go f.loopProcessingDeletion() return f diff --git a/weed/filer/placement_overlay.go b/weed/filer/placement_overlay.go new file mode 100644 index 000000000..9c9f80873 --- /dev/null +++ b/weed/filer/placement_overlay.go @@ -0,0 +1,36 @@ +package filer + +// PlacementOverlay resolves a write-time placement override for a collection — +// the disk type, replication, and data center new volumes of that collection +// should land on — or ok=false when the collection has none. +// +// It exists so a feature can steer where a bound collection's data lands without +// every operator setting an fs.configure rule by hand. The type is deliberately +// generic: it names no placement-policy concepts, so a downstream build +// registers whatever overlay it wants without coupling this seam to it. +type PlacementOverlay func(collection string) (diskType, replication, dataCenter string, ok bool) + +// newPlacementOverlay builds the overlay for a filer. Enterprise sets it in an +// init() pulled in by a side-effect import, the way plugin-worker handlers +// register — the setter cannot live here because this package is overwritten by +// the sync. +var newPlacementOverlay func(*Filer) PlacementOverlay + +// RegisterPlacementOverlay installs the factory. It is meant to be called from a +// package init() — before any Filer is constructed and before serving starts — +// so the unsynchronized read in NewFiler never races the write. The last +// registration wins; there is only ever one overlay. +func RegisterPlacementOverlay(factory func(*Filer) PlacementOverlay) { + newPlacementOverlay = factory +} + +// ResolvePlacement returns the overlay's placement for a collection, or +// ok=false when there is no overlay or the collection is not steered. The write +// path applies these between the explicit request value and the filer.conf +// rule, so an overlay overrides the rule but yields to an explicit request. +func (f *Filer) ResolvePlacement(collection string) (diskType, replication, dataCenter string, ok bool) { + if f == nil || f.placementOverlay == nil || collection == "" { + return "", "", "", false + } + return f.placementOverlay(collection) +} diff --git a/weed/server/filer_server_handlers_write.go b/weed/server/filer_server_handlers_write.go index f44f53004..1344c67f5 100644 --- a/weed/server/filer_server_handlers_write.go +++ b/weed/server/filer_server_handlers_write.go @@ -295,14 +295,29 @@ func (fs *FilerServer) detectStorageOption(ctx context.Context, requestURI, qCol ttlSeconds = int32(ttl.Minutes()) * 60 } + collection := util.Nvl(qCollection, rule.Collection, bucketDefaultCollection, fs.option.Collection) + + // A placement overlay steers a bound collection's new volumes onto a tier. + // It sits between the explicit request and the filer.conf rule: it overrides + // the rule but yields to a value the caller asked for outright. Only a + // steered collection contributes values; an unsteered one clears them so it + // falls straight through to the rule. + overlayDisk, overlayReplication, overlayDataCenter, overlaySteered := fs.filer.ResolvePlacement(collection) + if !overlaySteered { + overlayDisk, overlayReplication, overlayDataCenter = "", "", "" + } else { + glog.V(4).InfofCtx(ctx, "placement overlay steers collection %s: disk=%q replication=%q dataCenter=%q", + collection, overlayDisk, overlayReplication, overlayDataCenter) + } + return &operation.StorageOption{ - Replication: util.Nvl(qReplication, rule.Replication, fs.option.DefaultReplication), - Collection: util.Nvl(qCollection, rule.Collection, bucketDefaultCollection, fs.option.Collection), - DataCenter: util.Nvl(dataCenter, rule.DataCenter, fs.option.DataCenter), + Replication: util.Nvl(qReplication, overlayReplication, rule.Replication, fs.option.DefaultReplication), + Collection: collection, + DataCenter: util.Nvl(dataCenter, overlayDataCenter, rule.DataCenter, fs.option.DataCenter), Rack: util.Nvl(rack, rule.Rack, fs.option.Rack), DataNode: util.Nvl(dataNode, rule.DataNode, fs.option.DataNode), TtlSeconds: ttlSeconds, - DiskType: util.Nvl(diskType, rule.DiskType), + DiskType: util.Nvl(diskType, overlayDisk, rule.DiskType), Fsync: rule.Fsync, VolumeGrowthCount: rule.VolumeGrowthCount, MaxFileNameLength: maxFileNameLength,