chrislu
3 years ago
17 changed files with 499 additions and 40 deletions
-
4weed/command/mount2_std.go
-
2weed/mount/directory_read.go
-
65weed/mount/inode_to_path.go
-
32weed/mount/meta_cache/cache_config.go
-
101weed/mount/meta_cache/id_mapper.go
-
160weed/mount/meta_cache/meta_cache.go
-
67weed/mount/meta_cache/meta_cache_init.go
-
68weed/mount/meta_cache/meta_cache_subscribe.go
-
13weed/mount/weedfs.go
-
4weed/mount/weedfs_dir_lookup.go
-
2weed/mount/weedfs_dir_mkrm.go
-
2weed/mount/weedfs_dir_read.go
-
4weed/mount/weedfs_file_mkrm.go
-
9weed/mount/weedfs_forget.go
-
2weed/mount/weedfs_link.go
-
2weed/mount/weedfs_rename.go
-
2weed/mount/weedfs_symlink.go
@ -0,0 +1,32 @@ |
|||
package meta_cache |
|||
|
|||
import "github.com/chrislusf/seaweedfs/weed/util" |
|||
|
|||
var ( |
|||
_ = util.Configuration(&cacheConfig{}) |
|||
) |
|||
|
|||
// implementing util.Configuraion
|
|||
type cacheConfig struct { |
|||
dir string |
|||
} |
|||
|
|||
func (c cacheConfig) GetString(key string) string { |
|||
return c.dir |
|||
} |
|||
|
|||
func (c cacheConfig) GetBool(key string) bool { |
|||
panic("implement me") |
|||
} |
|||
|
|||
func (c cacheConfig) GetInt(key string) int { |
|||
panic("implement me") |
|||
} |
|||
|
|||
func (c cacheConfig) GetStringSlice(key string) []string { |
|||
panic("implement me") |
|||
} |
|||
|
|||
func (c cacheConfig) SetDefault(key string, value interface{}) { |
|||
panic("implement me") |
|||
} |
@ -0,0 +1,101 @@ |
|||
package meta_cache |
|||
|
|||
import ( |
|||
"fmt" |
|||
"strconv" |
|||
"strings" |
|||
) |
|||
|
|||
type UidGidMapper struct { |
|||
uidMapper *IdMapper |
|||
gidMapper *IdMapper |
|||
} |
|||
|
|||
type IdMapper struct { |
|||
localToFiler map[uint32]uint32 |
|||
filerToLocal map[uint32]uint32 |
|||
} |
|||
|
|||
// UidGidMapper translates local uid/gid to filer uid/gid
|
|||
// The local storage always persists the same as the filer.
|
|||
// The local->filer translation happens when updating the filer first and later saving to meta_cache.
|
|||
// And filer->local happens when reading from the meta_cache.
|
|||
func NewUidGidMapper(uidPairsStr, gidPairStr string) (*UidGidMapper, error) { |
|||
uidMapper, err := newIdMapper(uidPairsStr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
gidMapper, err := newIdMapper(gidPairStr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &UidGidMapper{ |
|||
uidMapper: uidMapper, |
|||
gidMapper: gidMapper, |
|||
}, nil |
|||
} |
|||
|
|||
func (m *UidGidMapper) LocalToFiler(uid, gid uint32) (uint32, uint32) { |
|||
return m.uidMapper.LocalToFiler(uid), m.gidMapper.LocalToFiler(gid) |
|||
} |
|||
func (m *UidGidMapper) FilerToLocal(uid, gid uint32) (uint32, uint32) { |
|||
return m.uidMapper.FilerToLocal(uid), m.gidMapper.FilerToLocal(gid) |
|||
} |
|||
|
|||
func (m *IdMapper) LocalToFiler(id uint32) uint32 { |
|||
value, found := m.localToFiler[id] |
|||
if found { |
|||
return value |
|||
} |
|||
return id |
|||
} |
|||
func (m *IdMapper) FilerToLocal(id uint32) uint32 { |
|||
value, found := m.filerToLocal[id] |
|||
if found { |
|||
return value |
|||
} |
|||
return id |
|||
} |
|||
|
|||
func newIdMapper(pairsStr string) (*IdMapper, error) { |
|||
|
|||
localToFiler, filerToLocal, err := parseUint32Pairs(pairsStr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &IdMapper{ |
|||
localToFiler: localToFiler, |
|||
filerToLocal: filerToLocal, |
|||
}, nil |
|||
|
|||
} |
|||
|
|||
func parseUint32Pairs(pairsStr string) (localToFiler, filerToLocal map[uint32]uint32, err error) { |
|||
|
|||
if pairsStr == "" { |
|||
return |
|||
} |
|||
|
|||
localToFiler = make(map[uint32]uint32) |
|||
filerToLocal = make(map[uint32]uint32) |
|||
for _, pairStr := range strings.Split(pairsStr, ",") { |
|||
pair := strings.Split(pairStr, ":") |
|||
localUidStr, filerUidStr := pair[0], pair[1] |
|||
localUid, localUidErr := strconv.Atoi(localUidStr) |
|||
if localUidErr != nil { |
|||
err = fmt.Errorf("failed to parse local %s: %v", localUidStr, localUidErr) |
|||
return |
|||
} |
|||
filerUid, filerUidErr := strconv.Atoi(filerUidStr) |
|||
if filerUidErr != nil { |
|||
err = fmt.Errorf("failed to parse remote %s: %v", filerUidStr, filerUidErr) |
|||
return |
|||
} |
|||
localToFiler[uint32(localUid)] = uint32(filerUid) |
|||
filerToLocal[uint32(filerUid)] = uint32(localUid) |
|||
} |
|||
|
|||
return |
|||
} |
@ -0,0 +1,160 @@ |
|||
package meta_cache |
|||
|
|||
import ( |
|||
"context" |
|||
"github.com/chrislusf/seaweedfs/weed/filer" |
|||
"github.com/chrislusf/seaweedfs/weed/filer/leveldb" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
"os" |
|||
) |
|||
|
|||
// need to have logic similar to FilerStoreWrapper
|
|||
// e.g. fill fileId field for chunks
|
|||
|
|||
type MetaCache struct { |
|||
localStore filer.VirtualFilerStore |
|||
// sync.RWMutex
|
|||
uidGidMapper *UidGidMapper |
|||
markCachedFn func(fullpath util.FullPath) |
|||
isCachedFn func(fullpath util.FullPath) bool |
|||
invalidateFunc func(fullpath util.FullPath, entry *filer_pb.Entry) |
|||
} |
|||
|
|||
func NewMetaCache(dbFolder string, uidGidMapper *UidGidMapper, markCachedFn func(path util.FullPath), isCachedFn func(path util.FullPath) bool, invalidateFunc func(util.FullPath, *filer_pb.Entry)) *MetaCache { |
|||
return &MetaCache{ |
|||
localStore: openMetaStore(dbFolder), |
|||
markCachedFn: markCachedFn, |
|||
isCachedFn: isCachedFn, |
|||
uidGidMapper: uidGidMapper, |
|||
invalidateFunc: func(fullpath util.FullPath, entry *filer_pb.Entry) { |
|||
invalidateFunc(fullpath, entry) |
|||
}, |
|||
} |
|||
} |
|||
|
|||
func openMetaStore(dbFolder string) filer.VirtualFilerStore { |
|||
|
|||
os.RemoveAll(dbFolder) |
|||
os.MkdirAll(dbFolder, 0755) |
|||
|
|||
store := &leveldb.LevelDBStore{} |
|||
config := &cacheConfig{ |
|||
dir: dbFolder, |
|||
} |
|||
|
|||
if err := store.Initialize(config, ""); err != nil { |
|||
glog.Fatalf("Failed to initialize metadata cache store for %s: %+v", store.GetName(), err) |
|||
} |
|||
|
|||
return filer.NewFilerStoreWrapper(store) |
|||
|
|||
} |
|||
|
|||
func (mc *MetaCache) InsertEntry(ctx context.Context, entry *filer.Entry) error { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
return mc.doInsertEntry(ctx, entry) |
|||
} |
|||
|
|||
func (mc *MetaCache) doInsertEntry(ctx context.Context, entry *filer.Entry) error { |
|||
return mc.localStore.InsertEntry(ctx, entry) |
|||
} |
|||
|
|||
func (mc *MetaCache) AtomicUpdateEntryFromFiler(ctx context.Context, oldPath util.FullPath, newEntry *filer.Entry) error { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
|
|||
oldDir, _ := oldPath.DirAndName() |
|||
if mc.isCachedFn(util.FullPath(oldDir)) { |
|||
if oldPath != "" { |
|||
if newEntry != nil && oldPath == newEntry.FullPath { |
|||
// skip the unnecessary deletion
|
|||
// leave the update to the following InsertEntry operation
|
|||
} else { |
|||
glog.V(3).Infof("DeleteEntry %s", oldPath) |
|||
if err := mc.localStore.DeleteEntry(ctx, oldPath); err != nil { |
|||
return err |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
// println("unknown old directory:", oldDir)
|
|||
} |
|||
|
|||
if newEntry != nil { |
|||
newDir, _ := newEntry.DirAndName() |
|||
if mc.isCachedFn(util.FullPath(newDir)) { |
|||
glog.V(3).Infof("InsertEntry %s/%s", newDir, newEntry.Name()) |
|||
if err := mc.localStore.InsertEntry(ctx, newEntry); err != nil { |
|||
return err |
|||
} |
|||
} |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (mc *MetaCache) UpdateEntry(ctx context.Context, entry *filer.Entry) error { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
return mc.localStore.UpdateEntry(ctx, entry) |
|||
} |
|||
|
|||
func (mc *MetaCache) FindEntry(ctx context.Context, fp util.FullPath) (entry *filer.Entry, err error) { |
|||
//mc.RLock()
|
|||
//defer mc.RUnlock()
|
|||
entry, err = mc.localStore.FindEntry(ctx, fp) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
mc.mapIdFromFilerToLocal(entry) |
|||
return |
|||
} |
|||
|
|||
func (mc *MetaCache) DeleteEntry(ctx context.Context, fp util.FullPath) (err error) { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
return mc.localStore.DeleteEntry(ctx, fp) |
|||
} |
|||
func (mc *MetaCache) DeleteFolderChildren(ctx context.Context, fp util.FullPath) (err error) { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
return mc.localStore.DeleteFolderChildren(ctx, fp) |
|||
} |
|||
|
|||
func (mc *MetaCache) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) error { |
|||
//mc.RLock()
|
|||
//defer mc.RUnlock()
|
|||
|
|||
if !mc.isCachedFn(dirPath) { |
|||
// if this request comes after renaming, it should be fine
|
|||
glog.Warningf("unsynchronized dir: %v", dirPath) |
|||
} |
|||
|
|||
_, err := mc.localStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit, func(entry *filer.Entry) bool { |
|||
mc.mapIdFromFilerToLocal(entry) |
|||
return eachEntryFunc(entry) |
|||
}) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
return err |
|||
} |
|||
|
|||
func (mc *MetaCache) Shutdown() { |
|||
//mc.Lock()
|
|||
//defer mc.Unlock()
|
|||
mc.localStore.Shutdown() |
|||
} |
|||
|
|||
func (mc *MetaCache) mapIdFromFilerToLocal(entry *filer.Entry) { |
|||
entry.Attr.Uid, entry.Attr.Gid = mc.uidGidMapper.FilerToLocal(entry.Attr.Uid, entry.Attr.Gid) |
|||
} |
|||
|
|||
func (mc *MetaCache) Debug() { |
|||
if debuggable, ok := mc.localStore.(filer.Debuggable); ok { |
|||
println("start debugging") |
|||
debuggable.Debug(os.Stderr) |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
package meta_cache |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/filer" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
) |
|||
|
|||
func EnsureVisited(mc *MetaCache, client filer_pb.FilerClient, dirPath util.FullPath) error { |
|||
|
|||
for { |
|||
|
|||
// the directory children are already cached
|
|||
// so no need for this and upper directories
|
|||
if mc.isCachedFn(dirPath) { |
|||
return nil |
|||
} |
|||
|
|||
if err := doEnsureVisited(mc, client, dirPath); err != nil { |
|||
return err |
|||
} |
|||
|
|||
// continue to parent directory
|
|||
if dirPath != "/" { |
|||
parent, _ := dirPath.DirAndName() |
|||
dirPath = util.FullPath(parent) |
|||
} else { |
|||
break |
|||
} |
|||
} |
|||
|
|||
return nil |
|||
|
|||
} |
|||
|
|||
func doEnsureVisited(mc *MetaCache, client filer_pb.FilerClient, path util.FullPath) error { |
|||
|
|||
glog.V(4).Infof("ReadDirAllEntries %s ...", path) |
|||
|
|||
err := util.Retry("ReadDirAllEntries", func() error { |
|||
return filer_pb.ReadDirAllEntries(client, path, "", func(pbEntry *filer_pb.Entry, isLast bool) error { |
|||
entry := filer.FromPbEntry(string(path), pbEntry) |
|||
if IsHiddenSystemEntry(string(path), entry.Name()) { |
|||
return nil |
|||
} |
|||
if err := mc.doInsertEntry(context.Background(), entry); err != nil { |
|||
glog.V(0).Infof("read %s: %v", entry.FullPath, err) |
|||
return err |
|||
} |
|||
return nil |
|||
}) |
|||
}) |
|||
|
|||
if err != nil { |
|||
err = fmt.Errorf("list %s: %v", path, err) |
|||
} |
|||
mc.markCachedFn(path) |
|||
return err |
|||
} |
|||
|
|||
func IsHiddenSystemEntry(dir, name string) bool { |
|||
return dir == "/" && (name == "topics" || name == "etc") |
|||
} |
@ -0,0 +1,68 @@ |
|||
package meta_cache |
|||
|
|||
import ( |
|||
"context" |
|||
"github.com/chrislusf/seaweedfs/weed/filer" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/pb" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
) |
|||
|
|||
func SubscribeMetaEvents(mc *MetaCache, selfSignature int32, client filer_pb.FilerClient, dir string, lastTsNs int64) error { |
|||
|
|||
processEventFn := func(resp *filer_pb.SubscribeMetadataResponse) error { |
|||
message := resp.EventNotification |
|||
|
|||
for _, sig := range message.Signatures { |
|||
if sig == selfSignature && selfSignature != 0 { |
|||
return nil |
|||
} |
|||
} |
|||
|
|||
dir := resp.Directory |
|||
var oldPath util.FullPath |
|||
var newEntry *filer.Entry |
|||
if message.OldEntry != nil { |
|||
oldPath = util.NewFullPath(dir, message.OldEntry.Name) |
|||
glog.V(4).Infof("deleting %v", oldPath) |
|||
} |
|||
|
|||
if message.NewEntry != nil { |
|||
if message.NewParentPath != "" { |
|||
dir = message.NewParentPath |
|||
} |
|||
key := util.NewFullPath(dir, message.NewEntry.Name) |
|||
glog.V(4).Infof("creating %v", key) |
|||
newEntry = filer.FromPbEntry(dir, message.NewEntry) |
|||
} |
|||
err := mc.AtomicUpdateEntryFromFiler(context.Background(), oldPath, newEntry) |
|||
if err == nil { |
|||
if message.OldEntry != nil && message.NewEntry != nil { |
|||
oldKey := util.NewFullPath(resp.Directory, message.OldEntry.Name) |
|||
mc.invalidateFunc(oldKey, message.OldEntry) |
|||
if message.OldEntry.Name != message.NewEntry.Name { |
|||
newKey := util.NewFullPath(dir, message.NewEntry.Name) |
|||
mc.invalidateFunc(newKey, message.NewEntry) |
|||
} |
|||
} else if message.OldEntry == nil && message.NewEntry != nil { |
|||
// no need to invaalidate
|
|||
} else if message.OldEntry != nil && message.NewEntry == nil { |
|||
oldKey := util.NewFullPath(resp.Directory, message.OldEntry.Name) |
|||
mc.invalidateFunc(oldKey, message.OldEntry) |
|||
} |
|||
} |
|||
|
|||
return err |
|||
|
|||
} |
|||
|
|||
util.RetryForever("followMetaUpdates", func() error { |
|||
return pb.WithFilerClientFollowMetadata(client, "mount", selfSignature, dir, &lastTsNs, selfSignature, processEventFn, true) |
|||
}, func(err error) bool { |
|||
glog.Errorf("follow metadata updates: %v", err) |
|||
return true |
|||
}) |
|||
|
|||
return nil |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue