mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
* filer: add filer.options.disable_remote_storage_deletion for cache-only deletes Deleting a filer entry under a remote.mount path also deletes the backing object from the remote store (maybeDeleteFromRemote). Deployments that use a remote mount as a read-through cache in front of an authoritative, externally-managed object store cannot allow this: the filer typically holds read-only credentials, so the remote delete fails and the entire delete errors out; and even where it would succeed, it destroys data the filer does not own. Add filer.options.disable_remote_storage_deletion (default false, so existing behaviour is unchanged). When enabled, maybeDeleteFromRemote is skipped for both single-entry and recursive folder deletes: local metadata and cached chunks are still removed, but the remote object is left intact. * filer: assert local removal in cache-only recursive delete test The recursive cache-only delete test only checked that no remote delete happened; it did not verify the local child and directory entries were removed. Add FindEntry assertions so a regression that skips local recursive deletion is caught. * filer: reload the remote mount mapping when /etc/remote changes The mapping was only read at startup, so remote.unmount left the mount live in the filer: the purge that follows the mapping delete then went to the remote store and wiped every object under the mount. Rebuild the rules trie and the conf map from scratch on each load, since ptrie cannot drop a key, and swap them under a lock. * filer: drop the filer-wide remote deletion switch With the mapping reloaded on unmount, the purge no longer reaches the remote store, so there is nothing left for the switch to protect against. --------- Co-authored-by: Chris Lu <chris.lu@gmail.com>
147 lines
4.3 KiB
Go
147 lines
4.3 KiB
Go
package filer
|
|
|
|
import (
|
|
"bytes"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
// onMetadataChangeEvent is triggered after filer processed change events from local or remote filers
|
|
func (f *Filer) onMetadataChangeEvent(event *filer_pb.SubscribeMetadataResponse) {
|
|
f.maybeReloadFilerConfiguration(event)
|
|
f.maybeReloadRemoteStorageConfigurationAndMapping(event)
|
|
f.onBucketEvents(event)
|
|
f.onEmptyFolderCleanupEvents(event)
|
|
}
|
|
|
|
func (f *Filer) onBucketEvents(event *filer_pb.SubscribeMetadataResponse) {
|
|
message := event.EventNotification
|
|
oldDir := event.Directory
|
|
newDir := filer_pb.MetadataEventTargetDirectory(event)
|
|
|
|
if filer_pb.IsCreate(event) {
|
|
if newDir == f.DirBucketsPath && message.NewEntry.IsDirectory {
|
|
f.Store.OnBucketCreation(message.NewEntry.Name)
|
|
}
|
|
}
|
|
if filer_pb.IsDelete(event) {
|
|
if oldDir == f.DirBucketsPath && message.OldEntry.IsDirectory {
|
|
f.Store.OnBucketDeletion(message.OldEntry.Name)
|
|
}
|
|
}
|
|
if filer_pb.IsRename(event) {
|
|
if oldDir == f.DirBucketsPath && message.OldEntry.IsDirectory {
|
|
f.Store.OnBucketDeletion(message.OldEntry.Name)
|
|
}
|
|
if newDir == f.DirBucketsPath && message.NewEntry.IsDirectory {
|
|
f.Store.OnBucketCreation(message.NewEntry.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// onEmptyFolderCleanupEvents handles create/delete events for empty folder cleanup
|
|
func (f *Filer) onEmptyFolderCleanupEvents(event *filer_pb.SubscribeMetadataResponse) {
|
|
if f.EmptyFolderCleaner == nil || !f.EmptyFolderCleaner.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
message := event.EventNotification
|
|
directory := event.Directory
|
|
eventTime := time.Unix(0, event.TsNs)
|
|
|
|
// Handle delete events - trigger folder cleanup check
|
|
if filer_pb.IsDelete(event) && message.OldEntry != nil {
|
|
f.EmptyFolderCleaner.OnDeleteEvent(directory, message.OldEntry.Name, message.OldEntry.IsDirectory, eventTime)
|
|
}
|
|
|
|
// Handle create events - cancel pending cleanup for the folder
|
|
if filer_pb.IsCreate(event) && message.NewEntry != nil {
|
|
f.EmptyFolderCleaner.OnCreateEvent(directory, message.NewEntry.Name, message.NewEntry.IsDirectory)
|
|
}
|
|
|
|
// Handle rename/move events
|
|
if filer_pb.IsRename(event) {
|
|
// Treat the old location as a delete
|
|
if message.OldEntry != nil {
|
|
f.EmptyFolderCleaner.OnDeleteEvent(directory, message.OldEntry.Name, message.OldEntry.IsDirectory, eventTime)
|
|
}
|
|
// Treat the new location as a create
|
|
if message.NewEntry != nil {
|
|
newDir := message.NewParentPath
|
|
if newDir == "" {
|
|
newDir = directory
|
|
}
|
|
f.EmptyFolderCleaner.OnCreateEvent(newDir, message.NewEntry.Name, message.NewEntry.IsDirectory)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Filer) maybeReloadFilerConfiguration(event *filer_pb.SubscribeMetadataResponse) {
|
|
if !filer_pb.MetadataEventTouchesDirectory(event, DirectoryEtcSeaweedFS) {
|
|
return
|
|
}
|
|
|
|
entry := event.EventNotification.NewEntry
|
|
if entry == nil {
|
|
return
|
|
}
|
|
|
|
glog.V(0).Infof("processing %v", event)
|
|
if entry.Name == FilerConfName {
|
|
f.reloadFilerConfiguration(entry)
|
|
}
|
|
}
|
|
|
|
func (f *Filer) readEntry(chunks []*filer_pb.FileChunk, size uint64) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
err := StreamContent(f.MasterClient, &buf, chunks, 0, int64(size))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (f *Filer) reloadFilerConfiguration(entry *filer_pb.Entry) {
|
|
fc := NewFilerConf()
|
|
err := fc.loadFromChunks(f, entry.Content, entry.GetChunks(), FileSize(entry))
|
|
if err != nil {
|
|
glog.Errorf("read filer conf chunks: %v", err)
|
|
return
|
|
}
|
|
f.FilerConf = fc
|
|
}
|
|
|
|
func (f *Filer) LoadFilerConf() {
|
|
fc := NewFilerConf()
|
|
err := util.Retry("loadFilerConf", func() error {
|
|
return fc.loadFromFiler(f)
|
|
})
|
|
if err != nil {
|
|
glog.Errorf("read filer conf: %v", err)
|
|
return
|
|
}
|
|
f.FilerConf = fc
|
|
}
|
|
|
|
// //////////////////////////////////
|
|
// load and maintain remote storages
|
|
// //////////////////////////////////
|
|
func (f *Filer) LoadRemoteStorageConfAndMapping() {
|
|
if err := f.RemoteStorage.LoadRemoteStorageConfigurationsAndMapping(f); err != nil {
|
|
glog.Errorf("read remote conf and mapping: %v", err)
|
|
return
|
|
}
|
|
}
|
|
func (f *Filer) maybeReloadRemoteStorageConfigurationAndMapping(event *filer_pb.SubscribeMetadataResponse) {
|
|
if !filer_pb.MetadataEventTouchesDirectory(event, DirectoryEtcRemote) {
|
|
return
|
|
}
|
|
|
|
// a mount left behind after remote.unmount would still send deletes to a
|
|
// remote the filer no longer owns
|
|
f.LoadRemoteStorageConfAndMapping()
|
|
}
|