mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-02 20:53:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3914c98d05 |
@@ -52,8 +52,6 @@ Table of Contents
|
||||
=================
|
||||
|
||||
* [Quick Start](#quick-start)
|
||||
* [Quick Start for S3 API on Docker](#quick-start-for-s3-api-on-docker)
|
||||
* [Quick Start with Single Binary](#quick-start-with-single-binary)
|
||||
* [Introduction](#introduction)
|
||||
* [Features](#features)
|
||||
* [Additional Features](#additional-features)
|
||||
@@ -76,7 +74,7 @@ Table of Contents
|
||||
|
||||
`docker run -p 8333:8333 chrislusf/seaweedfs server -s3`
|
||||
|
||||
## Quick Start with Single Binary ##
|
||||
## Quick Start with single binary ##
|
||||
* Download the latest binary from https://github.com/chrislusf/seaweedfs/releases and unzip a single binary file `weed` or `weed.exe`
|
||||
* Run `weed server -dir=/some/data/dir -s3` to start one master, one volume server, one filer, and one S3 gateway.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
appVersion: "2.90"
|
||||
version: "2.90"
|
||||
appVersion: "2.88"
|
||||
version: "2.88"
|
||||
|
||||
@@ -96,7 +96,7 @@ func init() {
|
||||
filerWebDavOptions.tlsPrivateKey = cmdFiler.Flag.String("webdav.key.file", "", "path to the TLS private key file")
|
||||
filerWebDavOptions.tlsCertificate = cmdFiler.Flag.String("webdav.cert.file", "", "path to the TLS certificate file")
|
||||
filerWebDavOptions.cacheDir = cmdFiler.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks")
|
||||
filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 0, "local cache capacity in MB")
|
||||
filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB")
|
||||
|
||||
// start iam on filer
|
||||
filerStartIam = cmdFiler.Flag.Bool("iam", false, "whether to start IAM service")
|
||||
|
||||
+2
-15
@@ -44,8 +44,6 @@ type MasterOptions struct {
|
||||
metricsIntervalSec *int
|
||||
raftResumeState *bool
|
||||
metricsHttpPort *int
|
||||
heartbeatInterval *time.Duration
|
||||
electionTimeout *time.Duration
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -67,8 +65,6 @@ func init() {
|
||||
m.metricsIntervalSec = cmdMaster.Flag.Int("metrics.intervalSeconds", 15, "Prometheus push interval in seconds")
|
||||
m.metricsHttpPort = cmdMaster.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
m.raftResumeState = cmdMaster.Flag.Bool("resumeState", false, "resume previous state on start master server")
|
||||
m.heartbeatInterval = cmdMaster.Flag.Duration("heartbeatInterval", 300*time.Millisecond, "heartbeat interval of master servers, and will be randomly multiplied by [1, 1.25)")
|
||||
m.electionTimeout = cmdMaster.Flag.Duration("electionTimeout", 10*time.Second, "election timeout of master servers")
|
||||
}
|
||||
|
||||
var cmdMaster = &Command{
|
||||
@@ -136,17 +132,8 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) {
|
||||
glog.Fatalf("Master startup error: %v", e)
|
||||
}
|
||||
// start raftServer
|
||||
raftServerOption := &weed_server.RaftServerOption{
|
||||
GrpcDialOption: security.LoadClientTLS(util.GetViper(), "grpc.master"),
|
||||
Peers: peers,
|
||||
ServerAddr: myMasterAddress,
|
||||
DataDir: util.ResolvePath(*masterOption.metaFolder),
|
||||
Topo: ms.Topo,
|
||||
RaftResumeState: *masterOption.raftResumeState,
|
||||
HeartbeatInterval: *masterOption.heartbeatInterval,
|
||||
ElectionTimeout: *masterOption.electionTimeout,
|
||||
}
|
||||
raftServer, err := weed_server.NewRaftServer(raftServerOption)
|
||||
raftServer, err := weed_server.NewRaftServer(security.LoadClientTLS(util.GetViper(), "grpc.master"),
|
||||
peers, myMasterAddress, util.ResolvePath(*masterOption.metaFolder), ms.Topo, *masterOption.raftResumeState)
|
||||
if raftServer == nil {
|
||||
glog.Fatalf("please verify %s is writable, see https://github.com/chrislusf/seaweedfs/issues/717: %s", *masterOption.metaFolder, err)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func init() {
|
||||
mountOptions.chunkSizeLimitMB = cmdMount.Flag.Int("chunkSizeLimitMB", 2, "local write buffer size, also chunk large files")
|
||||
mountOptions.concurrentWriters = cmdMount.Flag.Int("concurrentWriters", 32, "limit concurrent goroutine writers if not 0")
|
||||
mountOptions.cacheDir = cmdMount.Flag.String("cacheDir", os.TempDir(), "local cache directory for file chunks and meta data")
|
||||
mountOptions.cacheSizeMB = cmdMount.Flag.Int64("cacheCapacityMB", 0, "local file chunk cache capacity in MB")
|
||||
mountOptions.cacheSizeMB = cmdMount.Flag.Int64("cacheCapacityMB", 1000, "local file chunk cache capacity in MB (0 will disable cache)")
|
||||
mountOptions.dataCenter = cmdMount.Flag.String("dataCenter", "", "prefer to write to the data center")
|
||||
mountOptions.allowOthers = cmdMount.Flag.Bool("allowOthers", true, "allows other users to access the file system")
|
||||
mountOptions.umaskString = cmdMount.Flag.String("umask", "022", "octal umask, e.g., 022, 0111")
|
||||
|
||||
@@ -47,7 +47,7 @@ func init() {
|
||||
mount2Options.chunkSizeLimitMB = cmdMount2.Flag.Int("chunkSizeLimitMB", 2, "local write buffer size, also chunk large files")
|
||||
mount2Options.concurrentWriters = cmdMount2.Flag.Int("concurrentWriters", 32, "limit concurrent goroutine writers if not 0")
|
||||
mount2Options.cacheDir = cmdMount2.Flag.String("cacheDir", os.TempDir(), "local cache directory for file chunks and meta data")
|
||||
mount2Options.cacheSizeMB = cmdMount2.Flag.Int64("cacheCapacityMB", 0, "local file chunk cache capacity in MB")
|
||||
mount2Options.cacheSizeMB = cmdMount2.Flag.Int64("cacheCapacityMB", 1000, "local file chunk cache capacity in MB (0 will disable cache)")
|
||||
mount2Options.dataCenter = cmdMount2.Flag.String("dataCenter", "", "prefer to write to the data center")
|
||||
mount2Options.allowOthers = cmdMount2.Flag.Bool("allowOthers", true, "allows other users to access the file system")
|
||||
mount2Options.umaskString = cmdMount2.Flag.String("umask", "022", "octal umask, e.g., 022, 0111")
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build !linux && !darwin
|
||||
// +build !linux,!darwin
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func runMount2(cmd *Command, args []string) bool {
|
||||
fmt.Printf("Mount is not supported on %s %s\n", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
@@ -155,7 +152,7 @@ func RunMount2(option *Mount2Options, umask os.FileMode) bool {
|
||||
Name: "seaweedfs",
|
||||
SingleThreaded: false,
|
||||
DisableXAttrs: false,
|
||||
Debug: *option.debug,
|
||||
Debug: true,
|
||||
EnableLocks: false,
|
||||
ExplicitDataCacheControl: false,
|
||||
// SyncRead: false, // set to false to enable the FUSE_CAP_ASYNC_READ capability
|
||||
|
||||
@@ -195,40 +195,6 @@ routeByLatency = false
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[redis_lua]
|
||||
enabled = false
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 0
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[redis_lua_sentinel]
|
||||
enabled = false
|
||||
addresses = ["172.22.12.7:26379","172.22.12.8:26379","172.22.12.9:26379"]
|
||||
masterName = "master"
|
||||
username = ""
|
||||
password = ""
|
||||
database = 0
|
||||
|
||||
[redis_lua_cluster]
|
||||
enabled = false
|
||||
addresses = [
|
||||
"localhost:30001",
|
||||
"localhost:30002",
|
||||
"localhost:30003",
|
||||
"localhost:30004",
|
||||
"localhost:30005",
|
||||
"localhost:30006",
|
||||
]
|
||||
password = ""
|
||||
# allows reads from slave servers or the master, but all writes still go to the master
|
||||
readOnly = false
|
||||
# automatically use the closest Redis server for reads
|
||||
routeByLatency = false
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[redis3] # beta
|
||||
enabled = false
|
||||
address = "localhost:6379"
|
||||
|
||||
@@ -98,8 +98,6 @@ func init() {
|
||||
masterOptions.metricsAddress = cmdServer.Flag.String("metrics.address", "", "Prometheus gateway address")
|
||||
masterOptions.metricsIntervalSec = cmdServer.Flag.Int("metrics.intervalSeconds", 15, "Prometheus push interval in seconds")
|
||||
masterOptions.raftResumeState = cmdServer.Flag.Bool("resumeState", false, "resume previous state on start master server")
|
||||
masterOptions.heartbeatInterval = cmdServer.Flag.Duration("master.heartbeatInterval", 300*time.Millisecond, "heartbeat interval of master servers, and will be randomly multiplied by [1, 1.25)")
|
||||
masterOptions.electionTimeout = cmdServer.Flag.Duration("master.electionTimeout", 10*time.Second, "election timeout of master servers")
|
||||
|
||||
filerOptions.collection = cmdServer.Flag.String("filer.collection", "", "all data will be stored in this collection")
|
||||
filerOptions.port = cmdServer.Flag.Int("filer.port", 8888, "filer server http listen port")
|
||||
@@ -147,7 +145,7 @@ func init() {
|
||||
webdavOptions.tlsPrivateKey = cmdServer.Flag.String("webdav.key.file", "", "path to the TLS private key file")
|
||||
webdavOptions.tlsCertificate = cmdServer.Flag.String("webdav.cert.file", "", "path to the TLS certificate file")
|
||||
webdavOptions.cacheDir = cmdServer.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks")
|
||||
webdavOptions.cacheSizeMB = cmdServer.Flag.Int64("webdav.cacheCapacityMB", 0, "local cache capacity in MB")
|
||||
webdavOptions.cacheSizeMB = cmdServer.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB")
|
||||
|
||||
msgBrokerOptions.port = cmdServer.Flag.Int("msgBroker.port", 17777, "broker gRPC listen port")
|
||||
|
||||
|
||||
@@ -364,8 +364,8 @@ func (v VolumeServerOptions) startClusterHttpService(handler http.Handler) httpd
|
||||
}
|
||||
|
||||
httpDown := httpdown.HTTP{
|
||||
KillTimeout: time.Minute,
|
||||
StopTimeout: 30 * time.Second,
|
||||
KillTimeout: 5 * time.Minute,
|
||||
StopTimeout: 5 * time.Minute,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile}
|
||||
clusterHttpServer := httpDown.Serve(&http.Server{Handler: handler}, listener)
|
||||
|
||||
@@ -43,7 +43,7 @@ func init() {
|
||||
webDavStandaloneOptions.tlsPrivateKey = cmdWebDav.Flag.String("key.file", "", "path to the TLS private key file")
|
||||
webDavStandaloneOptions.tlsCertificate = cmdWebDav.Flag.String("cert.file", "", "path to the TLS certificate file")
|
||||
webDavStandaloneOptions.cacheDir = cmdWebDav.Flag.String("cacheDir", os.TempDir(), "local cache directory for file chunks")
|
||||
webDavStandaloneOptions.cacheSizeMB = cmdWebDav.Flag.Int64("cacheCapacityMB", 0, "local cache capacity in MB")
|
||||
webDavStandaloneOptions.cacheSizeMB = cmdWebDav.Flag.Int64("cacheCapacityMB", 1000, "local cache capacity in MB")
|
||||
}
|
||||
|
||||
var cmdWebDav = &Command{
|
||||
|
||||
@@ -285,10 +285,8 @@ func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath u
|
||||
|
||||
count := int64(0)
|
||||
for count < limit && len(notPrefixed) > 0 {
|
||||
var isLastItemHasPrefix bool
|
||||
for _, entry := range notPrefixed {
|
||||
if strings.HasPrefix(entry.Name(), prefix) {
|
||||
isLastItemHasPrefix = true
|
||||
count++
|
||||
if !eachEntryFunc(entry) {
|
||||
return
|
||||
@@ -296,11 +294,9 @@ func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath u
|
||||
if count >= limit {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
isLastItemHasPrefix = false
|
||||
}
|
||||
}
|
||||
if count < limit && isLastItemHasPrefix && len(notPrefixed) == int(limit) {
|
||||
if count < limit {
|
||||
notPrefixed = notPrefixed[:0]
|
||||
lastFileName, err = actualStore.ListDirectoryEntries(ctx, dirPath, lastFileName, false, limit, func(entry *Entry) bool {
|
||||
notPrefixed = append(notPrefixed, entry)
|
||||
@@ -309,8 +305,6 @@ func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath u
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
@@ -66,7 +67,8 @@ func TestCreateAndFind(t *testing.T) {
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
@@ -88,7 +90,8 @@ func TestEmptyRoot(t *testing.T) {
|
||||
|
||||
func BenchmarkInsertEntry(b *testing.B) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := b.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_bench")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
|
||||
@@ -2,6 +2,7 @@ package leveldb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB2Store{}
|
||||
store.initialize(dir, 2)
|
||||
testFiler.SetStore(store)
|
||||
@@ -63,7 +65,8 @@ func TestCreateAndFind(t *testing.T) {
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB2Store{}
|
||||
store.initialize(dir, 2)
|
||||
testFiler.SetStore(store)
|
||||
|
||||
@@ -72,8 +72,8 @@ func (store *LevelDB3Store) loadDB(name string) (*leveldb.DB, error) {
|
||||
}
|
||||
if name != DEFAULT {
|
||||
opts = &opt.Options{
|
||||
BlockCacheCapacity: 16 * 1024 * 1024, // default value is 8MiB
|
||||
WriteBuffer: 8 * 1024 * 1024, // default value is 4MiB
|
||||
BlockCacheCapacity: 4 * 1024 * 1024, // default value is 8MiB
|
||||
WriteBuffer: 2 * 1024 * 1024, // default value is 4MiB
|
||||
Filter: bloom,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package leveldb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB3Store{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
@@ -63,7 +65,8 @@ func TestCreateAndFind(t *testing.T) {
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB3Store{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
|
||||
@@ -159,7 +159,7 @@ func (store *MongodbStore) DeleteEntry(ctx context.Context, fullpath util.FullPa
|
||||
dir, name := fullpath.DirAndName()
|
||||
|
||||
where := bson.M{"directory": dir, "name": name}
|
||||
_, err := store.connect.Database(store.database).Collection(store.collectionName).DeleteMany(ctx, where)
|
||||
_, err := store.connect.Database(store.database).Collection(store.collectionName).DeleteOne(ctx, where)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s : %v", fullpath, err)
|
||||
}
|
||||
@@ -199,9 +199,9 @@ func (store *MongodbStore) ListDirectoryEntries(ctx context.Context, dirPath uti
|
||||
|
||||
for cur.Next(ctx) {
|
||||
var data Model
|
||||
err = cur.Decode(&data)
|
||||
if err != nil {
|
||||
break
|
||||
err := cur.Decode(&data)
|
||||
if err != nil && err != mongo.ErrNoDocuments {
|
||||
return lastFileName, err
|
||||
}
|
||||
|
||||
entry := &filer.Entry{
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package redis_lua
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &RedisLuaClusterStore{})
|
||||
}
|
||||
|
||||
type RedisLuaClusterStore struct {
|
||||
UniversalRedisLuaStore
|
||||
}
|
||||
|
||||
func (store *RedisLuaClusterStore) GetName() string {
|
||||
return "redis_lua_cluster"
|
||||
}
|
||||
|
||||
func (store *RedisLuaClusterStore) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
|
||||
configuration.SetDefault(prefix+"useReadOnly", false)
|
||||
configuration.SetDefault(prefix+"routeByLatency", false)
|
||||
|
||||
return store.initialize(
|
||||
configuration.GetStringSlice(prefix+"addresses"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetBool(prefix+"useReadOnly"),
|
||||
configuration.GetBool(prefix+"routeByLatency"),
|
||||
configuration.GetStringSlice(prefix+"superLargeDirectories"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *RedisLuaClusterStore) initialize(addresses []string, password string, readOnly, routeByLatency bool, superLargeDirectories []string) (err error) {
|
||||
store.Client = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: addresses,
|
||||
Password: password,
|
||||
ReadOnly: readOnly,
|
||||
RouteByLatency: routeByLatency,
|
||||
})
|
||||
store.loadSuperLargeDirectories(superLargeDirectories)
|
||||
return
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package redis_lua
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &RedisLuaSentinelStore{})
|
||||
}
|
||||
|
||||
type RedisLuaSentinelStore struct {
|
||||
UniversalRedisLuaStore
|
||||
}
|
||||
|
||||
func (store *RedisLuaSentinelStore) GetName() string {
|
||||
return "redis_lua_sentinel"
|
||||
}
|
||||
|
||||
func (store *RedisLuaSentinelStore) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
return store.initialize(
|
||||
configuration.GetStringSlice(prefix+"addresses"),
|
||||
configuration.GetString(prefix+"masterName"),
|
||||
configuration.GetString(prefix+"username"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetInt(prefix+"database"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *RedisLuaSentinelStore) initialize(addresses []string, masterName string, username string, password string, database int) (err error) {
|
||||
store.Client = redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: masterName,
|
||||
SentinelAddrs: addresses,
|
||||
Username: username,
|
||||
Password: password,
|
||||
DB: database,
|
||||
MinRetryBackoff: time.Millisecond * 100,
|
||||
MaxRetryBackoff: time.Minute * 1,
|
||||
ReadTimeout: time.Second * 30,
|
||||
WriteTimeout: time.Second * 5,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package redis_lua
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &RedisLuaStore{})
|
||||
}
|
||||
|
||||
type RedisLuaStore struct {
|
||||
UniversalRedisLuaStore
|
||||
}
|
||||
|
||||
func (store *RedisLuaStore) GetName() string {
|
||||
return "redis_lua"
|
||||
}
|
||||
|
||||
func (store *RedisLuaStore) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
return store.initialize(
|
||||
configuration.GetString(prefix+"address"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetInt(prefix+"database"),
|
||||
configuration.GetStringSlice(prefix+"superLargeDirectories"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *RedisLuaStore) initialize(hostPort string, password string, database int, superLargeDirectories []string) (err error) {
|
||||
store.Client = redis.NewClient(&redis.Options{
|
||||
Addr: hostPort,
|
||||
Password: password,
|
||||
DB: database,
|
||||
})
|
||||
store.loadSuperLargeDirectories(superLargeDirectories)
|
||||
return
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
-- KEYS[1]: full path of entry
|
||||
local fullpath = KEYS[1]
|
||||
-- KEYS[2]: full path of entry
|
||||
local fullpath_list_key = KEYS[2]
|
||||
-- KEYS[3]: dir of the entry
|
||||
local dir_list_key = KEYS[3]
|
||||
|
||||
-- ARGV[1]: isSuperLargeDirectory
|
||||
local isSuperLargeDirectory = ARGV[1] == "1"
|
||||
-- ARGV[2]: name of the entry
|
||||
local name = ARGV[2]
|
||||
|
||||
redis.call("DEL", fullpath, fullpath_list_key)
|
||||
|
||||
if not isSuperLargeDirectory and name ~= "" then
|
||||
redis.call("ZREM", dir_list_key, name)
|
||||
end
|
||||
|
||||
return 0
|
||||
@@ -1,15 +0,0 @@
|
||||
-- KEYS[1]: full path of entry
|
||||
local fullpath = KEYS[1]
|
||||
|
||||
if fullpath ~= "" and string.sub(fullpath, -1) == "/" then
|
||||
fullpath = string.sub(fullpath, 0, -2)
|
||||
end
|
||||
|
||||
local files = redis.call("ZRANGE", fullpath .. "\0", "0", "-1")
|
||||
|
||||
for _, name in ipairs(files) do
|
||||
local file_path = fullpath .. "/" .. name
|
||||
redis.call("DEL", file_path, file_path .. "\0")
|
||||
end
|
||||
|
||||
return 0
|
||||
@@ -1,24 +0,0 @@
|
||||
package stored_procedure
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InsertEntryScript = redis.NewScript(insertEntry)
|
||||
DeleteEntryScript = redis.NewScript(deleteEntry)
|
||||
DeleteFolderChildrenScript = redis.NewScript(deleteFolderChildren)
|
||||
}
|
||||
|
||||
//go:embed insert_entry.lua
|
||||
var insertEntry string
|
||||
var InsertEntryScript *redis.Script
|
||||
|
||||
//go:embed delete_entry.lua
|
||||
var deleteEntry string
|
||||
var DeleteEntryScript *redis.Script
|
||||
|
||||
//go:embed delete_folder_children.lua
|
||||
var deleteFolderChildren string
|
||||
var DeleteFolderChildrenScript *redis.Script
|
||||
@@ -1,27 +0,0 @@
|
||||
-- KEYS[1]: full path of entry
|
||||
local full_path = KEYS[1]
|
||||
-- KEYS[2]: dir of the entry
|
||||
local dir_list_key = KEYS[2]
|
||||
|
||||
-- ARGV[1]: content of the entry
|
||||
local entry = ARGV[1]
|
||||
-- ARGV[2]: TTL of the entry
|
||||
local ttlSec = tonumber(ARGV[2])
|
||||
-- ARGV[3]: isSuperLargeDirectory
|
||||
local isSuperLargeDirectory = ARGV[3] == "1"
|
||||
-- ARGV[4]: zscore of the entry in zset
|
||||
local zscore = tonumber(ARGV[4])
|
||||
-- ARGV[5]: name of the entry
|
||||
local name = ARGV[5]
|
||||
|
||||
if ttlSec > 0 then
|
||||
redis.call("SET", full_path, entry, "EX", ttlSec)
|
||||
else
|
||||
redis.call("SET", full_path, entry)
|
||||
end
|
||||
|
||||
if not isSuperLargeDirectory and name ~= "" then
|
||||
redis.call("ZADD", dir_list_key, "NX", zscore, name)
|
||||
end
|
||||
|
||||
return 0
|
||||
@@ -1,191 +0,0 @@
|
||||
package redis_lua
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer/redis_lua/stored_procedure"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
const (
|
||||
DIR_LIST_MARKER = "\x00"
|
||||
)
|
||||
|
||||
type UniversalRedisLuaStore struct {
|
||||
Client redis.UniversalClient
|
||||
superLargeDirectoryHash map[string]bool
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) isSuperLargeDirectory(dir string) (isSuperLargeDirectory bool) {
|
||||
_, isSuperLargeDirectory = store.superLargeDirectoryHash[dir]
|
||||
return
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) loadSuperLargeDirectories(superLargeDirectories []string) {
|
||||
// set directory hash
|
||||
store.superLargeDirectoryHash = make(map[string]bool)
|
||||
for _, dir := range superLargeDirectories {
|
||||
store.superLargeDirectoryHash[dir] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
func (store *UniversalRedisLuaStore) CommitTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (store *UniversalRedisLuaStore) RollbackTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
|
||||
value, err := entry.EncodeAttributesAndChunks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
|
||||
}
|
||||
|
||||
if len(entry.Chunks) > 50 {
|
||||
value = util.MaybeGzipData(value)
|
||||
}
|
||||
|
||||
dir, name := entry.FullPath.DirAndName()
|
||||
|
||||
err = stored_procedure.InsertEntryScript.Run(ctx, store.Client,
|
||||
[]string{string(entry.FullPath), genDirectoryListKey(dir)},
|
||||
value, entry.TtlSec,
|
||||
store.isSuperLargeDirectory(dir), 0, name).Err()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("persisting %s : %v", entry.FullPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
|
||||
return store.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) FindEntry(ctx context.Context, fullpath util.FullPath) (entry *filer.Entry, err error) {
|
||||
|
||||
data, err := store.Client.Get(ctx, string(fullpath)).Result()
|
||||
if err == redis.Nil {
|
||||
return nil, filer_pb.ErrNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
entry = &filer.Entry{
|
||||
FullPath: fullpath,
|
||||
}
|
||||
err = entry.DecodeAttributesAndChunks(util.MaybeDecompressData([]byte(data)))
|
||||
if err != nil {
|
||||
return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) DeleteEntry(ctx context.Context, fullpath util.FullPath) (err error) {
|
||||
|
||||
dir, name := fullpath.DirAndName()
|
||||
|
||||
err = stored_procedure.DeleteEntryScript.Run(ctx, store.Client,
|
||||
[]string{string(fullpath), genDirectoryListKey(string(fullpath)), genDirectoryListKey(dir)},
|
||||
store.isSuperLargeDirectory(dir), name).Err()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteEntry %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) DeleteFolderChildren(ctx context.Context, fullpath util.FullPath) (err error) {
|
||||
|
||||
if store.isSuperLargeDirectory(string(fullpath)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = stored_procedure.DeleteFolderChildrenScript.Run(ctx, store.Client,
|
||||
[]string{string(fullpath)}).Err()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteFolderChildren %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
|
||||
return lastFileName, filer.ErrUnsupportedListDirectoryPrefixed
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
|
||||
|
||||
dirListKey := genDirectoryListKey(string(dirPath))
|
||||
|
||||
min := "-"
|
||||
if startFileName != "" {
|
||||
if includeStartFile {
|
||||
min = "[" + startFileName
|
||||
} else {
|
||||
min = "(" + startFileName
|
||||
}
|
||||
}
|
||||
|
||||
members, err := store.Client.ZRangeByLex(ctx, dirListKey, &redis.ZRangeBy{
|
||||
Min: min,
|
||||
Max: "+",
|
||||
Offset: 0,
|
||||
Count: limit,
|
||||
}).Result()
|
||||
if err != nil {
|
||||
return lastFileName, fmt.Errorf("list %s : %v", dirPath, err)
|
||||
}
|
||||
|
||||
// fetch entry meta
|
||||
for _, fileName := range members {
|
||||
path := util.NewFullPath(string(dirPath), fileName)
|
||||
entry, err := store.FindEntry(ctx, path)
|
||||
lastFileName = fileName
|
||||
if err != nil {
|
||||
glog.V(0).Infof("list %s : %v", path, err)
|
||||
if err == filer_pb.ErrNotFound {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if entry.TtlSec > 0 {
|
||||
if entry.Attr.Crtime.Add(time.Duration(entry.TtlSec) * time.Second).Before(time.Now()) {
|
||||
store.DeleteEntry(ctx, path)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !eachEntryFunc(entry) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastFileName, err
|
||||
}
|
||||
|
||||
func genDirectoryListKey(dir string) (dirList string) {
|
||||
return dir + DIR_LIST_MARKER
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) Shutdown() {
|
||||
store.Client.Close()
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package redis_lua
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func (store *UniversalRedisLuaStore) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
|
||||
_, err = store.Client.Set(ctx, string(key), value, 0).Result()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kv put: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
|
||||
data, err := store.Client.Get(ctx, string(key)).Result()
|
||||
|
||||
if err == redis.Nil {
|
||||
return nil, filer.ErrKvNotFound
|
||||
}
|
||||
|
||||
return []byte(data), err
|
||||
}
|
||||
|
||||
func (store *UniversalRedisLuaStore) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
|
||||
_, err = store.Client.Del(ctx, string(key)).Result()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kv delete: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &RocksDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
@@ -69,7 +70,8 @@ func TestCreateAndFind(t *testing.T) {
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir := t.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &RocksDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
@@ -91,7 +93,8 @@ func TestEmptyRoot(t *testing.T) {
|
||||
|
||||
func BenchmarkInsertEntry(b *testing.B) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir := b.TempDir()
|
||||
dir, _ := os.MkdirTemp("", "seaweedfs_filer_bench")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &RocksDBStore{}
|
||||
store.initialize(dir)
|
||||
testFiler.SetStore(store)
|
||||
|
||||
+8
-3
@@ -8,6 +8,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -57,7 +58,8 @@ type Option struct {
|
||||
Cipher bool // whether encrypt data on volume server
|
||||
UidGidMapper *meta_cache.UidGidMapper
|
||||
|
||||
uniqueCacheDir string
|
||||
uniqueCacheDir string
|
||||
uniqueCacheTempPageDir string
|
||||
}
|
||||
|
||||
var _ = fs.FS(&WFS{})
|
||||
@@ -125,7 +127,6 @@ func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
})
|
||||
grace.OnInterrupt(func() {
|
||||
wfs.metaCache.Shutdown()
|
||||
os.RemoveAll(option.getUniqueCacheDir())
|
||||
})
|
||||
|
||||
wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs, id: 1}
|
||||
@@ -302,9 +303,13 @@ func (wfs *WFS) getCurrentFiler() pb.ServerAddress {
|
||||
func (option *Option) setupUniqueCacheDirectory() {
|
||||
cacheUniqueId := util.Md5String([]byte(option.MountDirectory + string(option.FilerAddresses[0]) + option.FilerMountRootPath + util.Version()))[0:8]
|
||||
option.uniqueCacheDir = path.Join(option.CacheDir, cacheUniqueId)
|
||||
os.MkdirAll(option.uniqueCacheDir, os.FileMode(0777)&^option.Umask)
|
||||
option.uniqueCacheTempPageDir = filepath.Join(option.uniqueCacheDir, "sw")
|
||||
os.MkdirAll(option.uniqueCacheTempPageDir, os.FileMode(0777)&^option.Umask)
|
||||
}
|
||||
|
||||
func (option *Option) getTempFilePageDir() string {
|
||||
return option.uniqueCacheTempPageDir
|
||||
}
|
||||
func (option *Option) getUniqueCacheDir() string {
|
||||
return option.uniqueCacheDir
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Directory struct {
|
||||
fs.Inode
|
||||
|
||||
name string
|
||||
wfs *WFS
|
||||
entry *filer_pb.Entry
|
||||
parent *Directory
|
||||
id uint64
|
||||
}
|
||||
|
||||
func (dir *Directory) FullPath() string {
|
||||
var parts []string
|
||||
for p := dir; p != nil; p = p.parent {
|
||||
if strings.HasPrefix(p.name, "/") {
|
||||
if len(p.name) > 1 {
|
||||
parts = append(parts, p.name[1:])
|
||||
}
|
||||
} else {
|
||||
parts = append(parts, p.name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "/"
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
buf.WriteString("/")
|
||||
buf.WriteString(parts[i])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/mount/meta_cache"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var _ = fs.NodeReaddirer(&Directory{})
|
||||
var _ = fs.NodeGetattrer(&Directory{})
|
||||
|
||||
func (dir *Directory) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
out.Mode = 0755
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dir *Directory) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
|
||||
|
||||
dirPath := util.FullPath(dir.FullPath())
|
||||
glog.V(4).Infof("Readdir %s", dirPath)
|
||||
|
||||
sourceChan := make(chan fuse.DirEntry, 64)
|
||||
|
||||
stream := newDirectoryListStream(sourceChan)
|
||||
|
||||
processEachEntryFn := func(entry *filer.Entry, isLast bool) {
|
||||
sourceChan <- fuse.DirEntry{
|
||||
Mode: uint32(entry.Mode),
|
||||
Name: entry.Name(),
|
||||
Ino: dirPath.Child(entry.Name()).AsInode(os.ModeDir),
|
||||
}
|
||||
}
|
||||
|
||||
if err := meta_cache.EnsureVisited(dir.wfs.metaCache, dir.wfs, dirPath); err != nil {
|
||||
glog.Errorf("dir ReadDirAll %s: %v", dirPath, err)
|
||||
return nil, fs.ToErrno(os.ErrInvalid)
|
||||
}
|
||||
go func() {
|
||||
dir.wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, "", false, int64(math.MaxInt32), func(entry *filer.Entry) bool {
|
||||
processEachEntryFn(entry, false)
|
||||
return true
|
||||
})
|
||||
close(sourceChan)
|
||||
}()
|
||||
|
||||
return stream, fs.OK
|
||||
}
|
||||
|
||||
var _ = fs.DirStream(&DirectoryListStream{})
|
||||
|
||||
type DirectoryListStream struct {
|
||||
next fuse.DirEntry
|
||||
sourceChan chan fuse.DirEntry
|
||||
isStarted bool
|
||||
hasNext bool
|
||||
}
|
||||
|
||||
func newDirectoryListStream(ch chan fuse.DirEntry) *DirectoryListStream {
|
||||
return &DirectoryListStream{
|
||||
sourceChan: ch,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *DirectoryListStream) HasNext() bool {
|
||||
if !i.isStarted {
|
||||
i.next, i.hasNext = <-i.sourceChan
|
||||
i.isStarted = true
|
||||
}
|
||||
return i.hasNext
|
||||
}
|
||||
func (i *DirectoryListStream) Next() (fuse.DirEntry, syscall.Errno) {
|
||||
t := i.next
|
||||
i.next, i.hasNext = <-i.sourceChan
|
||||
return t, fs.OK
|
||||
}
|
||||
func (i *DirectoryListStream) Close() {
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func newMemoryChunkPages(fh *FileHandle, chunkSize int64) *ChunkedDirtyPages {
|
||||
func (pages *ChunkedDirtyPages) AddPage(offset int64, data []byte) {
|
||||
pages.hasWrites = true
|
||||
|
||||
glog.V(4).Infof("%v memory AddPage [%d, %d)", pages.fh.fh, offset, offset+int64(len(data)))
|
||||
glog.V(4).Infof("%v memory AddPage [%d, %d)", pages.fh, offset, offset+int64(len(data)))
|
||||
pages.uploadPipeline.SaveDataAt(data, offset)
|
||||
|
||||
return
|
||||
|
||||
@@ -49,8 +49,7 @@ func newFileHandle(wfs *WFS, handleId FileHandleId, inode uint64, entry *filer_p
|
||||
}
|
||||
|
||||
func (fh *FileHandle) FullPath() util.FullPath {
|
||||
fp, _ := fh.wfs.inodeToPath.GetPath(fh.inode)
|
||||
return fp
|
||||
return fh.wfs.inodeToPath.GetPath(fh.inode)
|
||||
}
|
||||
|
||||
func (fh *FileHandle) addChunks(chunks []*filer_pb.FileChunk) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package mount
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -31,7 +30,7 @@ func NewInodeToPath() *InodeToPath {
|
||||
return t
|
||||
}
|
||||
|
||||
func (i *InodeToPath) Lookup(path util.FullPath, isDirectory bool, isLookup bool) uint64 {
|
||||
func (i *InodeToPath) Lookup(path util.FullPath, isDirectory bool) uint64 {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[path]
|
||||
@@ -39,15 +38,9 @@ func (i *InodeToPath) Lookup(path util.FullPath, isDirectory bool, isLookup bool
|
||||
inode = i.nextInodeId
|
||||
i.nextInodeId++
|
||||
i.path2inode[path] = inode
|
||||
if !isLookup {
|
||||
i.inode2path[inode] = &InodeEntry{path, 0, isDirectory, false}
|
||||
} else {
|
||||
i.inode2path[inode] = &InodeEntry{path, 1, isDirectory, false}
|
||||
}
|
||||
i.inode2path[inode] = &InodeEntry{path, 1, isDirectory, false}
|
||||
} else {
|
||||
if isLookup {
|
||||
i.inode2path[inode].nlookup++
|
||||
}
|
||||
i.inode2path[inode].nlookup++
|
||||
}
|
||||
return inode
|
||||
}
|
||||
@@ -66,14 +59,14 @@ func (i *InodeToPath) GetInode(path util.FullPath) uint64 {
|
||||
return inode
|
||||
}
|
||||
|
||||
func (i *InodeToPath) GetPath(inode uint64) (util.FullPath, fuse.Status) {
|
||||
func (i *InodeToPath) GetPath(inode uint64) util.FullPath {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
path, found := i.inode2path[inode]
|
||||
if !found {
|
||||
return "", fuse.ENOENT
|
||||
glog.Fatalf("not found inode %d", inode)
|
||||
}
|
||||
return path.FullPath, fuse.OK
|
||||
return path.FullPath
|
||||
}
|
||||
|
||||
func (i *InodeToPath) HasPath(path util.FullPath) bool {
|
||||
|
||||
@@ -10,32 +10,24 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func EnsureVisited(mc *MetaCache, client filer_pb.FilerClient, dirPath util.FullPath, entryChan chan *filer.Entry) error {
|
||||
|
||||
currentPath := dirPath
|
||||
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(currentPath) {
|
||||
if mc.isCachedFn(dirPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if entryChan != nil && dirPath == currentPath {
|
||||
if err := doEnsureVisited(mc, client, currentPath, entryChan); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := doEnsureVisited(mc, client, currentPath, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := doEnsureVisited(mc, client, dirPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// continue to parent directory
|
||||
if currentPath != "/" {
|
||||
parent, _ := currentPath.DirAndName()
|
||||
currentPath = util.FullPath(parent)
|
||||
if dirPath != "/" {
|
||||
parent, _ := dirPath.DirAndName()
|
||||
dirPath = util.FullPath(parent)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@@ -45,7 +37,7 @@ func EnsureVisited(mc *MetaCache, client filer_pb.FilerClient, dirPath util.Full
|
||||
|
||||
}
|
||||
|
||||
func doEnsureVisited(mc *MetaCache, client filer_pb.FilerClient, path util.FullPath, entryChan chan *filer.Entry) error {
|
||||
func doEnsureVisited(mc *MetaCache, client filer_pb.FilerClient, path util.FullPath) error {
|
||||
|
||||
glog.V(4).Infof("ReadDirAllEntries %s ...", path)
|
||||
|
||||
@@ -59,9 +51,6 @@ func doEnsureVisited(mc *MetaCache, client filer_pb.FilerClient, path util.FullP
|
||||
glog.V(0).Infof("read %s: %v", entry.FullPath, err)
|
||||
return err
|
||||
}
|
||||
if entryChan != nil {
|
||||
entryChan <- entry
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !linux && !windows
|
||||
// +build !linux,!windows
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package unmount
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package unmount
|
||||
|
||||
func unmount(dir string) error {
|
||||
return nil
|
||||
}
|
||||
+22
-10
@@ -16,6 +16,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
@@ -49,7 +50,8 @@ type Option struct {
|
||||
Cipher bool // whether encrypt data on volume server
|
||||
UidGidMapper *meta_cache.UidGidMapper
|
||||
|
||||
uniqueCacheDir string
|
||||
uniqueCacheDir string
|
||||
uniqueCacheTempPageDir string
|
||||
}
|
||||
|
||||
type WFS struct {
|
||||
@@ -59,12 +61,12 @@ type WFS struct {
|
||||
option *Option
|
||||
metaCache *meta_cache.MetaCache
|
||||
stats statsCache
|
||||
root Directory
|
||||
chunkCache *chunk_cache.TieredChunkCache
|
||||
signature int32
|
||||
concurrentWriters *util.LimitedConcurrentExecutor
|
||||
inodeToPath *InodeToPath
|
||||
fhmap *FileHandleToInode
|
||||
dhmap *DirectoryHandleToInode
|
||||
}
|
||||
|
||||
func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
@@ -74,7 +76,13 @@ func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
signature: util.RandomInt32(),
|
||||
inodeToPath: NewInodeToPath(),
|
||||
fhmap: NewFileHandleToInode(),
|
||||
dhmap: NewDirectoryHandleToInode(),
|
||||
}
|
||||
|
||||
wfs.root = Directory{
|
||||
name: "/",
|
||||
wfs: wfs,
|
||||
entry: nil,
|
||||
parent: nil,
|
||||
}
|
||||
|
||||
wfs.option.filerIndex = rand.Intn(len(option.FilerAddresses))
|
||||
@@ -91,7 +99,6 @@ func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
})
|
||||
grace.OnInterrupt(func() {
|
||||
wfs.metaCache.Shutdown()
|
||||
os.RemoveAll(option.getUniqueCacheDir())
|
||||
})
|
||||
|
||||
if wfs.option.ConcurrentWriters > 0 {
|
||||
@@ -105,15 +112,16 @@ func (wfs *WFS) StartBackgroundTasks() {
|
||||
go meta_cache.SubscribeMetaEvents(wfs.metaCache, wfs.signature, wfs, wfs.option.FilerMountRootPath, startTime.UnixNano())
|
||||
}
|
||||
|
||||
func (wfs *WFS) Root() *Directory {
|
||||
return &wfs.root
|
||||
}
|
||||
|
||||
func (wfs *WFS) String() string {
|
||||
return "seaweedfs"
|
||||
}
|
||||
|
||||
func (wfs *WFS) maybeReadEntry(inode uint64) (path util.FullPath, fh *FileHandle, entry *filer_pb.Entry, status fuse.Status) {
|
||||
path, status = wfs.inodeToPath.GetPath(inode)
|
||||
if status != fuse.OK {
|
||||
return
|
||||
}
|
||||
path = wfs.inodeToPath.GetPath(inode)
|
||||
var found bool
|
||||
if fh, found = wfs.fhmap.FindFileHandle(inode); found {
|
||||
return path, fh, fh.entry, fuse.OK
|
||||
@@ -143,7 +151,7 @@ func (wfs *WFS) maybeLoadEntry(fullpath util.FullPath) (*filer_pb.Entry, fuse.St
|
||||
}
|
||||
|
||||
// read from async meta cache
|
||||
meta_cache.EnsureVisited(wfs.metaCache, wfs, util.FullPath(dir), nil)
|
||||
meta_cache.EnsureVisited(wfs.metaCache, wfs, util.FullPath(dir))
|
||||
cachedEntry, cacheErr := wfs.metaCache.FindEntry(context.Background(), fullpath)
|
||||
if cacheErr == filer_pb.ErrNotFound {
|
||||
return nil, fuse.ENOENT
|
||||
@@ -167,9 +175,13 @@ func (wfs *WFS) getCurrentFiler() pb.ServerAddress {
|
||||
func (option *Option) setupUniqueCacheDirectory() {
|
||||
cacheUniqueId := util.Md5String([]byte(option.MountDirectory + string(option.FilerAddresses[0]) + option.FilerMountRootPath + util.Version()))[0:8]
|
||||
option.uniqueCacheDir = path.Join(option.CacheDir, cacheUniqueId)
|
||||
os.MkdirAll(option.uniqueCacheDir, os.FileMode(0777)&^option.Umask)
|
||||
option.uniqueCacheTempPageDir = filepath.Join(option.uniqueCacheDir, "sw")
|
||||
os.MkdirAll(option.uniqueCacheTempPageDir, os.FileMode(0777)&^option.Umask)
|
||||
}
|
||||
|
||||
func (option *Option) getTempFilePageDir() string {
|
||||
return option.uniqueCacheTempPageDir
|
||||
}
|
||||
func (option *Option) getUniqueCacheDir() string {
|
||||
return option.uniqueCacheDir
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (wfs *WFS) SetAttr(cancel <-chan struct{}, input *fuse.SetAttrIn, out *fuse
|
||||
}
|
||||
|
||||
if mode, ok := input.GetMode(); ok {
|
||||
entry.Attributes.FileMode = mode & 07777
|
||||
entry.Attributes.FileMode = uint32(mode)
|
||||
}
|
||||
|
||||
if uid, ok := input.GetUID(); ok {
|
||||
@@ -81,10 +81,6 @@ func (wfs *WFS) SetAttr(cancel <-chan struct{}, input *fuse.SetAttrIn, out *fuse
|
||||
entry.Attributes.Mtime = mtime.Unix()
|
||||
}
|
||||
|
||||
if atime, ok := input.GetATime(); ok {
|
||||
entry.Attributes.Mtime = atime.Unix()
|
||||
}
|
||||
|
||||
entry.Attributes.Mtime = time.Now().Unix()
|
||||
out.AttrValid = 1
|
||||
wfs.setAttrByPbEntry(&out.Attr, input.NodeId, entry)
|
||||
@@ -138,6 +134,7 @@ func (wfs *WFS) setAttrByFilerEntry(out *fuse.Attr, inode uint64, entry *filer.E
|
||||
out.Atime = uint64(entry.Attr.Mtime.Unix())
|
||||
out.Mtime = uint64(entry.Attr.Mtime.Unix())
|
||||
out.Ctime = uint64(entry.Attr.Mtime.Unix())
|
||||
out.Crtime_ = uint64(entry.Attr.Crtime.Unix())
|
||||
out.Mode = toSystemMode(entry.Attr.Mode)
|
||||
if entry.HardLinkCounter > 0 {
|
||||
out.Nlink = uint32(entry.HardLinkCounter)
|
||||
|
||||
@@ -20,14 +20,11 @@ func (wfs *WFS) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name strin
|
||||
return s
|
||||
}
|
||||
|
||||
dirPath, code := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
dirPath := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
|
||||
fullFilePath := dirPath.Child(name)
|
||||
|
||||
visitErr := meta_cache.EnsureVisited(wfs.metaCache, wfs, dirPath, nil)
|
||||
visitErr := meta_cache.EnsureVisited(wfs.metaCache, wfs, dirPath)
|
||||
if visitErr != nil {
|
||||
glog.Errorf("dir Lookup %s: %v", dirPath, visitErr)
|
||||
return fuse.EIO
|
||||
@@ -53,7 +50,7 @@ func (wfs *WFS) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name strin
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(fullFilePath, localEntry.IsDirectory(), true)
|
||||
inode := wfs.inodeToPath.Lookup(fullFilePath, localEntry.IsDirectory())
|
||||
|
||||
wfs.outputFilerEntry(out, inode, localEntry)
|
||||
|
||||
|
||||
@@ -37,10 +37,7 @@ func (wfs *WFS) Mkdir(cancel <-chan struct{}, in *fuse.MkdirIn, name string, out
|
||||
},
|
||||
}
|
||||
|
||||
dirFullPath, code := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
dirFullPath := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
|
||||
entryFullPath := dirFullPath.Child(name)
|
||||
|
||||
@@ -68,13 +65,13 @@ func (wfs *WFS) Mkdir(cancel <-chan struct{}, in *fuse.MkdirIn, name string, out
|
||||
return nil
|
||||
})
|
||||
|
||||
glog.V(3).Infof("mkdir %s: %v", entryFullPath, err)
|
||||
glog.V(0).Infof("mkdir %s: %v", entryFullPath, err)
|
||||
|
||||
if err != nil {
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, true, true)
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, true)
|
||||
|
||||
wfs.outputPbEntry(out, inode, newEntry)
|
||||
|
||||
@@ -92,10 +89,7 @@ func (wfs *WFS) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string
|
||||
return fuse.Status(syscall.ENOTEMPTY)
|
||||
}
|
||||
|
||||
dirFullPath, code := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
dirFullPath := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
entryFullPath := dirFullPath.Child(name)
|
||||
|
||||
glog.V(3).Infof("remove directory: %v", entryFullPath)
|
||||
|
||||
+32
-117
@@ -5,67 +5,12 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/mount/meta_cache"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"math"
|
||||
"sync"
|
||||
"os"
|
||||
)
|
||||
|
||||
type DirectoryHandleId uint64
|
||||
|
||||
type DirectoryHandle struct {
|
||||
isFinished bool
|
||||
lastEntryName string
|
||||
}
|
||||
|
||||
type DirectoryHandleToInode struct {
|
||||
// shares the file handle id sequencer with FileHandleToInode{nextFh}
|
||||
sync.Mutex
|
||||
dir2inode map[DirectoryHandleId]*DirectoryHandle
|
||||
}
|
||||
|
||||
func NewDirectoryHandleToInode() *DirectoryHandleToInode {
|
||||
return &DirectoryHandleToInode{
|
||||
dir2inode: make(map[DirectoryHandleId]*DirectoryHandle),
|
||||
}
|
||||
}
|
||||
|
||||
func (wfs *WFS) AcquireDirectoryHandle() (DirectoryHandleId, *DirectoryHandle) {
|
||||
wfs.fhmap.Lock()
|
||||
fh := wfs.fhmap.nextFh
|
||||
wfs.fhmap.nextFh++
|
||||
wfs.fhmap.Unlock()
|
||||
|
||||
wfs.dhmap.Lock()
|
||||
defer wfs.dhmap.Unlock()
|
||||
dh := &DirectoryHandle{
|
||||
isFinished: false,
|
||||
lastEntryName: "",
|
||||
}
|
||||
wfs.dhmap.dir2inode[DirectoryHandleId(fh)] = dh
|
||||
return DirectoryHandleId(fh), dh
|
||||
}
|
||||
|
||||
func (wfs *WFS) GetDirectoryHandle(dhid DirectoryHandleId) *DirectoryHandle {
|
||||
wfs.dhmap.Lock()
|
||||
defer wfs.dhmap.Unlock()
|
||||
if dh, found := wfs.dhmap.dir2inode[dhid]; found {
|
||||
return dh
|
||||
}
|
||||
dh := &DirectoryHandle{
|
||||
isFinished: false,
|
||||
lastEntryName: "",
|
||||
}
|
||||
|
||||
wfs.dhmap.dir2inode[dhid] = dh
|
||||
return dh
|
||||
}
|
||||
|
||||
func (wfs *WFS) ReleaseDirectoryHandle(dhid DirectoryHandleId) {
|
||||
wfs.dhmap.Lock()
|
||||
defer wfs.dhmap.Unlock()
|
||||
delete(wfs.dhmap.dir2inode, dhid)
|
||||
}
|
||||
|
||||
// Directory handling
|
||||
|
||||
/** Open directory
|
||||
@@ -80,8 +25,6 @@ func (wfs *WFS) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.Op
|
||||
if !wfs.inodeToPath.HasInode(input.NodeId) {
|
||||
return fuse.ENOENT
|
||||
}
|
||||
dhid, _ := wfs.AcquireDirectoryHandle()
|
||||
out.Fh = uint64(dhid)
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
@@ -91,7 +34,6 @@ func (wfs *WFS) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.Op
|
||||
* path parameter will be NULL.
|
||||
*/
|
||||
func (wfs *WFS) ReleaseDir(input *fuse.ReleaseIn) {
|
||||
wfs.ReleaseDirectoryHandle(DirectoryHandleId(input.Fh))
|
||||
}
|
||||
|
||||
/** Synchronize directory contents
|
||||
@@ -130,87 +72,60 @@ func (wfs *WFS) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
|
||||
}
|
||||
|
||||
func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out *fuse.DirEntryList, isPlusMode bool) fuse.Status {
|
||||
dirPath := wfs.inodeToPath.GetPath(input.NodeId)
|
||||
|
||||
dh := wfs.GetDirectoryHandle(DirectoryHandleId(input.Fh))
|
||||
if dh.isFinished {
|
||||
if input.Offset == 0 {
|
||||
dh.isFinished = false
|
||||
dh.lastEntryName = ""
|
||||
} else {
|
||||
return fuse.OK
|
||||
}
|
||||
}
|
||||
|
||||
isEarlyTerminated := false
|
||||
dirPath, code := wfs.inodeToPath.GetPath(input.NodeId)
|
||||
if code != fuse.OK {
|
||||
return code
|
||||
}
|
||||
|
||||
var counter uint64
|
||||
var dirEntry fuse.DirEntry
|
||||
if input.Offset == 0 {
|
||||
if !isPlusMode {
|
||||
out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "."})
|
||||
out.AddDirEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".."})
|
||||
} else {
|
||||
out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "."})
|
||||
out.AddDirLookupEntry(fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".."})
|
||||
}
|
||||
counter++
|
||||
dirEntry.Ino = input.NodeId
|
||||
dirEntry.Name = "."
|
||||
dirEntry.Mode = toSystemMode(os.ModeDir)
|
||||
out.AddDirEntry(dirEntry)
|
||||
|
||||
counter++
|
||||
parentDir, _ := dirPath.DirAndName()
|
||||
parentInode := wfs.inodeToPath.GetInode(util.FullPath(parentDir))
|
||||
dirEntry.Ino = parentInode
|
||||
dirEntry.Name = ".."
|
||||
dirEntry.Mode = toSystemMode(os.ModeDir)
|
||||
out.AddDirEntry(dirEntry)
|
||||
|
||||
}
|
||||
|
||||
processEachEntryFn := func(entry *filer.Entry, isLast bool) bool {
|
||||
counter++
|
||||
if counter <= input.Offset {
|
||||
return true
|
||||
}
|
||||
dirEntry.Name = entry.Name()
|
||||
inode := wfs.inodeToPath.GetInode(dirPath.Child(dirEntry.Name))
|
||||
dirEntry.Ino = inode
|
||||
dirEntry.Mode = toSystemMode(entry.Mode)
|
||||
if !isPlusMode {
|
||||
inode := wfs.inodeToPath.Lookup(dirPath.Child(dirEntry.Name), entry.IsDirectory(), false)
|
||||
dirEntry.Ino = inode
|
||||
if !out.AddDirEntry(dirEntry) {
|
||||
isEarlyTerminated = true
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
inode := wfs.inodeToPath.Lookup(dirPath.Child(dirEntry.Name), entry.IsDirectory(), true)
|
||||
dirEntry.Ino = inode
|
||||
entryOut := out.AddDirLookupEntry(dirEntry)
|
||||
if entryOut == nil {
|
||||
isEarlyTerminated = true
|
||||
return false
|
||||
}
|
||||
wfs.outputFilerEntry(entryOut, inode, entry)
|
||||
}
|
||||
dh.lastEntryName = entry.Name()
|
||||
return true
|
||||
}
|
||||
|
||||
entryChan := make(chan *filer.Entry, 128)
|
||||
var err error
|
||||
go func() {
|
||||
if err = meta_cache.EnsureVisited(wfs.metaCache, wfs, dirPath, entryChan); err != nil {
|
||||
glog.Errorf("dir ReadDirAll %s: %v", dirPath, err)
|
||||
}
|
||||
close(entryChan)
|
||||
}()
|
||||
hasData := false
|
||||
for entry := range entryChan {
|
||||
hasData = true
|
||||
processEachEntryFn(entry, false)
|
||||
}
|
||||
if err != nil {
|
||||
if err := meta_cache.EnsureVisited(wfs.metaCache, wfs, dirPath); err != nil {
|
||||
glog.Errorf("dir ReadDirAll %s: %v", dirPath, err)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
if !hasData {
|
||||
listErr := wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, dh.lastEntryName, false, int64(math.MaxInt32), func(entry *filer.Entry) bool {
|
||||
return processEachEntryFn(entry, false)
|
||||
})
|
||||
if listErr != nil {
|
||||
glog.Errorf("list meta cache: %v", listErr)
|
||||
return fuse.EIO
|
||||
}
|
||||
}
|
||||
|
||||
if !isEarlyTerminated {
|
||||
dh.isFinished = true
|
||||
listErr := wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, "", false, int64(math.MaxInt32), func(entry *filer.Entry) bool {
|
||||
return processEachEntryFn(entry, false)
|
||||
})
|
||||
if listErr != nil {
|
||||
glog.Errorf("list meta cache: %v", listErr)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
return fuse.OK
|
||||
|
||||
@@ -64,7 +64,6 @@ func (wfs *WFS) Open(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut)
|
||||
fileHandle, code := wfs.AcquireHandle(in.NodeId, in.Uid, in.Gid)
|
||||
if code == fuse.OK {
|
||||
out.Fh = uint64(fileHandle.fh)
|
||||
// TODO https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h#L64
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (wfs *WFS) Mknod(cancel <-chan struct{}, in *fuse.MknodIn, name string, out
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(toFileMode(in.Mode)),
|
||||
FileMode: uint32(toFileMode(in.Mode) &^ wfs.option.Umask),
|
||||
Uid: in.Uid,
|
||||
Gid: in.Gid,
|
||||
Collection: wfs.option.Collection,
|
||||
@@ -54,10 +54,7 @@ func (wfs *WFS) Mknod(cancel <-chan struct{}, in *fuse.MknodIn, name string, out
|
||||
},
|
||||
}
|
||||
|
||||
dirFullPath, code := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
dirFullPath := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
|
||||
entryFullPath := dirFullPath.Child(name)
|
||||
|
||||
@@ -85,13 +82,13 @@ func (wfs *WFS) Mknod(cancel <-chan struct{}, in *fuse.MknodIn, name string, out
|
||||
return nil
|
||||
})
|
||||
|
||||
glog.V(3).Infof("mknod %s: %v", entryFullPath, err)
|
||||
glog.V(0).Infof("mknod %s: %v", entryFullPath, err)
|
||||
|
||||
if err != nil {
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, false, true)
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, false)
|
||||
|
||||
wfs.outputPbEntry(out, inode, newEntry)
|
||||
|
||||
@@ -102,21 +99,12 @@ func (wfs *WFS) Mknod(cancel <-chan struct{}, in *fuse.MknodIn, name string, out
|
||||
/** Remove a file */
|
||||
func (wfs *WFS) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) (code fuse.Status) {
|
||||
|
||||
dirFullPath, code := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
if code != fuse.OK {
|
||||
if code == fuse.ENOENT {
|
||||
return fuse.OK
|
||||
}
|
||||
return code
|
||||
}
|
||||
dirFullPath := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
entryFullPath := dirFullPath.Child(name)
|
||||
|
||||
entry, code := wfs.maybeLoadEntry(entryFullPath)
|
||||
if code != fuse.OK {
|
||||
if code == fuse.ENOENT {
|
||||
return fuse.OK
|
||||
}
|
||||
return code
|
||||
entry, status := wfs.maybeLoadEntry(entryFullPath)
|
||||
if status != fuse.OK {
|
||||
return status
|
||||
}
|
||||
|
||||
// first, ensure the filer store can correctly delete
|
||||
@@ -125,7 +113,7 @@ func (wfs *WFS) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name strin
|
||||
err := filer_pb.Remove(wfs, string(dirFullPath), name, isDeleteData, false, false, false, []int32{wfs.signature})
|
||||
if err != nil {
|
||||
glog.V(0).Infof("remove %s: %v", entryFullPath, err)
|
||||
return fuse.OK
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
// then, delete meta cache
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -128,6 +129,7 @@ func (wfs *WFS) doFlush(fh *FileHandle, uid, gid uint32) fuse.Status {
|
||||
entry.Attributes.Crtime = time.Now().Unix()
|
||||
}
|
||||
entry.Attributes.Mtime = time.Now().Unix()
|
||||
entry.Attributes.FileMode = uint32(os.FileMode(entry.Attributes.FileMode) &^ wfs.option.Umask)
|
||||
entry.Attributes.Collection, entry.Attributes.Replication = fh.dirtyPages.GetStorageOptions()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,8 @@ func (wfs *WFS) Link(cancel <-chan struct{}, in *fuse.LinkIn, name string, out *
|
||||
return s
|
||||
}
|
||||
|
||||
newParentPath, code := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
oldEntryPath, code := wfs.inodeToPath.GetPath(in.Oldnodeid)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
newParentPath := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
oldEntryPath := wfs.inodeToPath.GetPath(in.Oldnodeid)
|
||||
oldParentPath, _ := oldEntryPath.DirAndName()
|
||||
|
||||
oldEntry, status := wfs.maybeLoadEntry(oldEntryPath)
|
||||
@@ -91,7 +85,7 @@ func (wfs *WFS) Link(cancel <-chan struct{}, in *fuse.LinkIn, name string, out *
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(newEntryPath, false, true)
|
||||
inode := wfs.inodeToPath.Lookup(newEntryPath, false)
|
||||
|
||||
wfs.outputPbEntry(out, inode, request.Entry)
|
||||
|
||||
|
||||
@@ -145,15 +145,9 @@ func (wfs *WFS) Rename(cancel <-chan struct{}, in *fuse.RenameIn, oldName string
|
||||
return fuse.EINVAL
|
||||
}
|
||||
|
||||
oldDir, code := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
oldDir := wfs.inodeToPath.GetPath(in.NodeId)
|
||||
oldPath := oldDir.Child(oldName)
|
||||
newDir, code := wfs.inodeToPath.GetPath(in.Newdir)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
newDir := wfs.inodeToPath.GetPath(in.Newdir)
|
||||
newPath := newDir.Child(newName)
|
||||
|
||||
glog.V(4).Infof("dir Rename %s => %s", oldPath, newPath)
|
||||
|
||||
@@ -19,7 +19,7 @@ type statsCache struct {
|
||||
|
||||
func (wfs *WFS) StatFs(cancel <-chan struct{}, in *fuse.InHeader, out *fuse.StatfsOut) (code fuse.Status) {
|
||||
|
||||
// glog.V(4).Infof("reading fs stats")
|
||||
glog.V(4).Infof("reading fs stats")
|
||||
|
||||
if wfs.stats.lastChecked < time.Now().Unix()-20 {
|
||||
|
||||
|
||||
@@ -18,10 +18,7 @@ func (wfs *WFS) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target st
|
||||
return s
|
||||
}
|
||||
|
||||
dirPath, code := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
dirPath := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
entryFullPath := dirPath.Child(name)
|
||||
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
@@ -32,7 +29,7 @@ func (wfs *WFS) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target st
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(os.FileMode(0777) | os.ModeSymlink),
|
||||
FileMode: uint32((os.FileMode(0777) | os.ModeSymlink) &^ wfs.option.Umask),
|
||||
Uid: header.Uid,
|
||||
Gid: header.Gid,
|
||||
SymlinkTarget: target,
|
||||
@@ -59,7 +56,7 @@ func (wfs *WFS) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target st
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, false, true)
|
||||
inode := wfs.inodeToPath.Lookup(entryFullPath, false)
|
||||
|
||||
wfs.outputPbEntry(out, inode, request.Entry)
|
||||
|
||||
@@ -67,10 +64,7 @@ func (wfs *WFS) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target st
|
||||
}
|
||||
|
||||
func (wfs *WFS) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, code fuse.Status) {
|
||||
entryFullPath, code := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
if code != fuse.OK {
|
||||
return
|
||||
}
|
||||
entryFullPath := wfs.inodeToPath.GetPath(header.NodeId)
|
||||
|
||||
entry, status := wfs.maybeLoadEntry(entryFullPath)
|
||||
if status != fuse.OK {
|
||||
|
||||
@@ -81,7 +81,7 @@ func DeleteFilesWithLookupVolumeId(grpcDialOption grpc.DialOption, fileIds []str
|
||||
ret = append(ret, &volume_server_pb.DeleteResult{
|
||||
FileId: vid,
|
||||
Status: http.StatusBadRequest,
|
||||
Error: result.Error},
|
||||
Error: err.Error()},
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -271,9 +271,6 @@ func (s3a *S3ApiServer) listObjectParts(input *s3.ListPartsInput) (output *ListP
|
||||
return nil, s3err.ErrNoSuchUpload
|
||||
}
|
||||
|
||||
// Note: The upload directory is sort of a marker of the existence of an multipart upload request.
|
||||
// So can not just delete empty upload folders.
|
||||
|
||||
output.IsTruncated = aws.Bool(!isLast)
|
||||
|
||||
for _, entry := range entries {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
|
||||
// Validates the preconditions. Returns true if GET/HEAD operation should not proceed.
|
||||
// Preconditions supported are:
|
||||
// If-Modified-Since
|
||||
@@ -118,11 +119,6 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Query().Has("metadata") {
|
||||
writeJsonQuiet(w, r, http.StatusOK, entry)
|
||||
return
|
||||
}
|
||||
|
||||
etag := filer.ETagEntry(entry)
|
||||
if checkPreconditions(w, r, entry) {
|
||||
return
|
||||
|
||||
@@ -3,7 +3,6 @@ package weed_server
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -79,78 +78,11 @@ func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request, conte
|
||||
return
|
||||
}
|
||||
|
||||
if query.Has("from") {
|
||||
fs.move(ctx, w, r, so)
|
||||
} else {
|
||||
fs.autoChunk(ctx, w, r, contentLength, so)
|
||||
}
|
||||
|
||||
fs.autoChunk(ctx, w, r, contentLength, so)
|
||||
util.CloseRequest(r)
|
||||
|
||||
}
|
||||
|
||||
func (fs *FilerServer) move(ctx context.Context, w http.ResponseWriter, r *http.Request, so *operation.StorageOption) {
|
||||
src := r.URL.Query().Get("from")
|
||||
dst := r.URL.Path
|
||||
|
||||
glog.V(2).Infof("FilerServer.move %v to %v", src, dst)
|
||||
|
||||
var err error
|
||||
if src, err = clearName(src); err != nil {
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if dst, err = clearName(dst); err != nil {
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
src = strings.TrimRight(src, "/")
|
||||
if src == "" {
|
||||
err = fmt.Errorf("invalid source '/'")
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
srcPath := util.FullPath(src)
|
||||
dstPath := util.FullPath(dst)
|
||||
srcEntry, err := fs.filer.FindEntry(ctx, srcPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get src entry '%s', err: %s", src, err)
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
oldDir, oldName := srcPath.DirAndName()
|
||||
newDir, newName := dstPath.DirAndName()
|
||||
newName = util.Nvl(newName, oldName)
|
||||
|
||||
dstEntry, err := fs.filer.FindEntry(ctx, util.FullPath(strings.TrimRight(dst, "/")))
|
||||
if err != nil && err != filer_pb.ErrNotFound {
|
||||
err = fmt.Errorf("failed to get dst entry '%s', err: %s", dst, err)
|
||||
writeJsonError(w, r, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if err == nil && !dstEntry.IsDirectory() && srcEntry.IsDirectory() {
|
||||
err = fmt.Errorf("move: cannot overwrite non-directory '%s' with directory '%s'", dst, src)
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = fs.AtomicRenameEntry(ctx, &filer_pb.AtomicRenameEntryRequest{
|
||||
OldDirectory: oldDir,
|
||||
OldName: oldName,
|
||||
NewDirectory: newDir,
|
||||
NewName: newName,
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to move entry from '%s' to '%s', err: %s", src, dst, err)
|
||||
writeJsonError(w, r, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// curl -X DELETE http://localhost:8888/path/to
|
||||
// curl -X DELETE http://localhost:8888/path/to?recursive=true
|
||||
// curl -X DELETE http://localhost:8888/path/to?recursive=true&ignoreRecursiveError=true
|
||||
|
||||
+13
-25
@@ -19,17 +19,6 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/topology"
|
||||
)
|
||||
|
||||
type RaftServerOption struct {
|
||||
GrpcDialOption grpc.DialOption
|
||||
Peers []pb.ServerAddress
|
||||
ServerAddr pb.ServerAddress
|
||||
DataDir string
|
||||
Topo *topology.Topology
|
||||
RaftResumeState bool
|
||||
HeartbeatInterval time.Duration
|
||||
ElectionTimeout time.Duration
|
||||
}
|
||||
|
||||
type RaftServer struct {
|
||||
peers []pb.ServerAddress // initial peers to join with
|
||||
raftServer raft.Server
|
||||
@@ -63,12 +52,12 @@ func (s StateMachine) Recovery(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRaftServer(option *RaftServerOption) (*RaftServer, error) {
|
||||
func NewRaftServer(grpcDialOption grpc.DialOption, peers []pb.ServerAddress, serverAddr pb.ServerAddress, dataDir string, topo *topology.Topology, raftResumeState bool) (*RaftServer, error) {
|
||||
s := &RaftServer{
|
||||
peers: option.Peers,
|
||||
serverAddr: option.ServerAddr,
|
||||
dataDir: option.DataDir,
|
||||
topo: option.Topo,
|
||||
peers: peers,
|
||||
serverAddr: serverAddr,
|
||||
dataDir: dataDir,
|
||||
topo: topo,
|
||||
}
|
||||
|
||||
if glog.V(4) {
|
||||
@@ -78,10 +67,10 @@ func NewRaftServer(option *RaftServerOption) (*RaftServer, error) {
|
||||
raft.RegisterCommand(&topology.MaxVolumeIdCommand{})
|
||||
|
||||
var err error
|
||||
transporter := raft.NewGrpcTransporter(option.GrpcDialOption)
|
||||
glog.V(0).Infof("Starting RaftServer with %v", option.ServerAddr)
|
||||
transporter := raft.NewGrpcTransporter(grpcDialOption)
|
||||
glog.V(0).Infof("Starting RaftServer with %v", serverAddr)
|
||||
|
||||
if !option.RaftResumeState {
|
||||
if !raftResumeState {
|
||||
// always clear previous metadata
|
||||
os.RemoveAll(path.Join(s.dataDir, "conf"))
|
||||
os.RemoveAll(path.Join(s.dataDir, "log"))
|
||||
@@ -91,15 +80,14 @@ func NewRaftServer(option *RaftServerOption) (*RaftServer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stateMachine := StateMachine{topo: option.Topo}
|
||||
s.raftServer, err = raft.NewServer(string(s.serverAddr), s.dataDir, transporter, stateMachine, option.Topo, "")
|
||||
stateMachine := StateMachine{topo: topo}
|
||||
s.raftServer, err = raft.NewServer(string(s.serverAddr), s.dataDir, transporter, stateMachine, topo, "")
|
||||
if err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return nil, err
|
||||
}
|
||||
heartbeatInterval := time.Duration(float64(option.HeartbeatInterval) * (rand.Float64()*0.25 + 1))
|
||||
s.raftServer.SetHeartbeatInterval(heartbeatInterval)
|
||||
s.raftServer.SetElectionTimeout(option.ElectionTimeout)
|
||||
s.raftServer.SetHeartbeatInterval(time.Duration(300+rand.Intn(150)) * time.Millisecond)
|
||||
s.raftServer.SetElectionTimeout(10 * time.Second)
|
||||
if err := s.raftServer.LoadSnapshot(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +123,7 @@ func NewRaftServer(option *RaftServerOption) (*RaftServer, error) {
|
||||
|
||||
s.GrpcServer = raft.NewGrpcServer(s.raftServer)
|
||||
|
||||
if s.raftServer.IsLogEmpty() && isTheFirstOne(option.ServerAddr, s.peers) {
|
||||
if s.raftServer.IsLogEmpty() && isTheFirstOne(serverAddr, s.peers) {
|
||||
// Initialize the server by joining itself.
|
||||
// s.DoJoinCommand()
|
||||
}
|
||||
|
||||
@@ -98,7 +98,6 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
|
||||
|
||||
handleStaticResources(adminMux)
|
||||
adminMux.HandleFunc("/status", vs.statusHandler)
|
||||
adminMux.HandleFunc("/healthz", vs.healthzHandler)
|
||||
if signingKey == "" || enableUiAccess {
|
||||
// only expose the volume server details for safe environments
|
||||
adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/topology"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
@@ -10,24 +9,6 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func (vs *VolumeServer) healthzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", "SeaweedFS Volume "+util.VERSION)
|
||||
volumeInfos := vs.store.VolumeInfos()
|
||||
for _, vinfo := range volumeInfos {
|
||||
if len(vinfo.Collection) == 0 {
|
||||
continue
|
||||
}
|
||||
if vinfo.ReplicaPlacement.GetCopyCount() > 1 {
|
||||
_, err := topology.GetWritableRemoteReplications(vs.store, vs.grpcDialOption, vinfo.Id, vs.GetMaster)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (vs *VolumeServer) statusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", "SeaweedFS Volume "+util.VERSION)
|
||||
m := make(map[string]interface{})
|
||||
|
||||
@@ -62,7 +62,7 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
|
||||
isDelete := fsConfigureCommand.Bool("delete", false, "delete the configuration by locationPrefix")
|
||||
apply := fsConfigureCommand.Bool("apply", false, "update and apply filer configuration")
|
||||
if err = fsConfigureCommand.Parse(args); err != nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
fc, err := filer.ReadFilerConf(commandEnv.option.FilerAddress, commandEnv.option.GrpcDialOption, commandEnv.MasterClient)
|
||||
|
||||
@@ -6,10 +6,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -64,8 +61,7 @@ func (c *commandVolumeFsck) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
verbose := fsckCommand.Bool("v", false, "verbose mode")
|
||||
findMissingChunksInFiler := fsckCommand.Bool("findMissingChunksInFiler", false, "see \"help volume.fsck\"")
|
||||
findMissingChunksInFilerPath := fsckCommand.String("findMissingChunksInFilerPath", "/", "used together with findMissingChunksInFiler")
|
||||
applyPurging := fsckCommand.Bool("reallyDeleteFromVolume", false, "<expert only!> after detection, delete missing data from volumes / delete missing file entries from filer")
|
||||
purgeAbsent := fsckCommand.Bool("reallyDeleteFilerEntries", false, "<expert only!> delete missing file entries from filer if the corresponding volume is missing for any reason, please ensure all still existing/expected volumes are connected! used together with findMissingChunksInFiler")
|
||||
applyPurging := fsckCommand.Bool("reallyDeleteFromVolume", false, "<expert only> delete data not referenced by the filer")
|
||||
if err = fsckCommand.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -102,20 +98,20 @@ func (c *commandVolumeFsck) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
|
||||
if *findMissingChunksInFiler {
|
||||
// collect all filer file ids and paths
|
||||
if err = c.collectFilerFileIdAndPaths(volumeIdToVInfo, tempFolder, writer, *findMissingChunksInFilerPath, *verbose, *purgeAbsent); err != nil {
|
||||
if err = c.collectFilerFileIdAndPaths(volumeIdToVInfo, tempFolder, writer, *findMissingChunksInFilerPath, *verbose, applyPurging); err != nil {
|
||||
return fmt.Errorf("collectFilerFileIdAndPaths: %v", err)
|
||||
}
|
||||
// for each volume, check filer file ids
|
||||
if err = c.findFilerChunksMissingInVolumeServers(volumeIdToVInfo, tempFolder, writer, *verbose, *applyPurging); err != nil {
|
||||
if err = c.findFilerChunksMissingInVolumeServers(volumeIdToVInfo, tempFolder, writer, *verbose, applyPurging); err != nil {
|
||||
return fmt.Errorf("findFilerChunksMissingInVolumeServers: %v", err)
|
||||
}
|
||||
} else {
|
||||
// collect all filer file ids
|
||||
if err = c.collectFilerFileIds(volumeIdToVInfo, tempFolder, writer, *verbose); err != nil {
|
||||
if err = c.collectFilerFileIds(tempFolder, volumeIdToVInfo, *verbose, writer); err != nil {
|
||||
return fmt.Errorf("failed to collect file ids from filer: %v", err)
|
||||
}
|
||||
// volume file ids subtract filer file ids
|
||||
if err = c.findExtraChunksInVolumeServers(volumeIdToVInfo, tempFolder, writer, *verbose, *applyPurging); err != nil {
|
||||
// volume file ids substract filer file ids
|
||||
if err = c.findExtraChunksInVolumeServers(volumeIdToVInfo, tempFolder, writer, *verbose, applyPurging); err != nil {
|
||||
return fmt.Errorf("findExtraChunksInVolumeServers: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -123,7 +119,7 @@ func (c *commandVolumeFsck) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) collectFilerFileIdAndPaths(volumeIdToServer map[uint32]VInfo, tempFolder string, writer io.Writer, filerPath string, verbose bool, purgeAbsent bool) error {
|
||||
func (c *commandVolumeFsck) collectFilerFileIdAndPaths(volumeIdToServer map[uint32]VInfo, tempFolder string, writer io.Writer, filerPath string, verbose bool, applyPurging *bool) error {
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(writer, "checking each file from filer ...\n")
|
||||
@@ -180,20 +176,16 @@ func (c *commandVolumeFsck) collectFilerFileIdAndPaths(volumeIdToServer map[uint
|
||||
// fmt.Fprintf(writer, "%d,%x%08x %d %s\n", i.vid, i.fileKey, i.cookie, len(i.path), i.path)
|
||||
} else {
|
||||
fmt.Fprintf(writer, "%d,%x%08x %s volume not found\n", i.vid, i.fileKey, i.cookie, i.path)
|
||||
if purgeAbsent {
|
||||
fmt.Printf("deleting path %s after volume not found", i.path)
|
||||
c.httpDelete(i.path, verbose)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) findFilerChunksMissingInVolumeServers(volumeIdToVInfo map[uint32]VInfo, tempFolder string, writer io.Writer, verbose bool, applyPurging bool) error {
|
||||
func (c *commandVolumeFsck) findFilerChunksMissingInVolumeServers(volumeIdToVInfo map[uint32]VInfo, tempFolder string, writer io.Writer, verbose bool, applyPurging *bool) error {
|
||||
|
||||
for volumeId, vinfo := range volumeIdToVInfo {
|
||||
checkErr := c.oneVolumeFileIdsCheckOneVolume(tempFolder, volumeId, writer, verbose, applyPurging)
|
||||
checkErr := c.oneVolumeFileIdsCheckOneVolume(tempFolder, volumeId, writer, verbose)
|
||||
if checkErr != nil {
|
||||
return fmt.Errorf("failed to collect file ids from volume %d on %s: %v", volumeId, vinfo.server, checkErr)
|
||||
}
|
||||
@@ -201,10 +193,8 @@ func (c *commandVolumeFsck) findFilerChunksMissingInVolumeServers(volumeIdToVInf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) findExtraChunksInVolumeServers(volumeIdToVInfo map[uint32]VInfo, tempFolder string, writer io.Writer, verbose bool, applyPurging bool) error {
|
||||
|
||||
func (c *commandVolumeFsck) findExtraChunksInVolumeServers(volumeIdToVInfo map[uint32]VInfo, tempFolder string, writer io.Writer, verbose bool, applyPurging *bool) error {
|
||||
var totalInUseCount, totalOrphanChunkCount, totalOrphanDataSize uint64
|
||||
|
||||
for volumeId, vinfo := range volumeIdToVInfo {
|
||||
inUseCount, orphanFileIds, orphanDataSize, checkErr := c.oneVolumeFileIdsSubtractFilerFileIds(tempFolder, volumeId, writer, verbose)
|
||||
if checkErr != nil {
|
||||
@@ -220,53 +210,39 @@ func (c *commandVolumeFsck) findExtraChunksInVolumeServers(volumeIdToVInfo map[u
|
||||
}
|
||||
}
|
||||
|
||||
if applyPurging && len(orphanFileIds) > 0 {
|
||||
if verbose {
|
||||
fmt.Fprintf(writer, "purging process for volume %d", volumeId)
|
||||
}
|
||||
|
||||
if *applyPurging && len(orphanFileIds) > 0 {
|
||||
if vinfo.isEcVolume {
|
||||
fmt.Fprintf(writer, "skip purging for Erasure Coded volume %d.\n", volumeId)
|
||||
fmt.Fprintf(writer, "Skip purging for Erasure Coded volume %d.\n", volumeId)
|
||||
continue
|
||||
}
|
||||
|
||||
needleVID := needle.VolumeId(volumeId)
|
||||
|
||||
if vinfo.isReadOnly {
|
||||
err := markVolumeWritable(c.env.option.GrpcDialOption, needleVID, vinfo.server, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mark volume %d read/write: %v", volumeId, err)
|
||||
fmt.Fprintf(writer, "Skip purging for read only volume %d.\n", volumeId)
|
||||
continue
|
||||
}
|
||||
if inUseCount == 0 {
|
||||
if err := deleteVolume(c.env.option.GrpcDialOption, needle.VolumeId(volumeId), vinfo.server); err != nil {
|
||||
return fmt.Errorf("delete volume %d: %v", volumeId, err)
|
||||
}
|
||||
} else {
|
||||
if err := c.purgeFileIdsForOneVolume(volumeId, orphanFileIds, writer); err != nil {
|
||||
return fmt.Errorf("purge for volume %d: %v", volumeId, err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "temporarily marked %d on server %v writable for forced purge\n", volumeId, vinfo.server)
|
||||
defer markVolumeWritable(c.env.option.GrpcDialOption, needleVID, vinfo.server, false)
|
||||
}
|
||||
|
||||
fmt.Fprintf(writer, "marked %d on server %v writable for forced purge\n", volumeId, vinfo.server)
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(writer, "purging files from volume %d\n", volumeId)
|
||||
}
|
||||
|
||||
if err := c.purgeFileIdsForOneVolume(volumeId, orphanFileIds, writer); err != nil {
|
||||
return fmt.Errorf("purging volume %d: %v", volumeId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !applyPurging {
|
||||
if totalOrphanChunkCount == 0 {
|
||||
fmt.Fprintf(writer, "no orphan data\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !*applyPurging {
|
||||
pct := float64(totalOrphanChunkCount*100) / (float64(totalOrphanChunkCount + totalInUseCount))
|
||||
fmt.Fprintf(writer, "\nTotal\t\tentries:%d\torphan:%d\t%.2f%%\t%dB\n",
|
||||
totalOrphanChunkCount+totalInUseCount, totalOrphanChunkCount, pct, totalOrphanDataSize)
|
||||
|
||||
fmt.Fprintf(writer, "This could be normal if multiple filers or no filers are used.\n")
|
||||
}
|
||||
|
||||
if totalOrphanChunkCount == 0 {
|
||||
fmt.Fprintf(writer, "no orphan data\n")
|
||||
//return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -307,7 +283,7 @@ func (c *commandVolumeFsck) collectOneVolumeFileIds(tempFolder string, volumeId
|
||||
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) collectFilerFileIds(volumeIdToServer map[uint32]VInfo, tempFolder string, writer io.Writer, verbose bool) error {
|
||||
func (c *commandVolumeFsck) collectFilerFileIds(tempFolder string, volumeIdToServer map[uint32]VInfo, verbose bool, writer io.Writer) error {
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(writer, "collecting file ids from filer ...\n")
|
||||
@@ -357,10 +333,10 @@ func (c *commandVolumeFsck) collectFilerFileIds(volumeIdToServer map[uint32]VInf
|
||||
})
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) oneVolumeFileIdsCheckOneVolume(tempFolder string, volumeId uint32, writer io.Writer, verbose bool, applyPurging bool) (err error) {
|
||||
func (c *commandVolumeFsck) oneVolumeFileIdsCheckOneVolume(tempFolder string, volumeId uint32, writer io.Writer, verbose bool) (err error) {
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(writer, "find missing file chunks in volume %d ...\n", volumeId)
|
||||
fmt.Fprintf(writer, "find missing file chuns in volume %d ...\n", volumeId)
|
||||
}
|
||||
|
||||
db := needle_map.NewMemDb()
|
||||
@@ -390,7 +366,11 @@ func (c *commandVolumeFsck) oneVolumeFileIdsCheckOneVolume(tempFolder string, vo
|
||||
for {
|
||||
readSize, err = io.ReadFull(br, buffer)
|
||||
if err != nil || readSize != 16 {
|
||||
break
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
item.fileKey = util.BytesToUint64(buffer[:8])
|
||||
@@ -406,51 +386,14 @@ func (c *commandVolumeFsck) oneVolumeFileIdsCheckOneVolume(tempFolder string, vo
|
||||
}
|
||||
item.path = util.FullPath(string(pathBytes))
|
||||
|
||||
needleId := types.NeedleId(item.fileKey)
|
||||
if _, found := db.Get(needleId); !found {
|
||||
fmt.Fprintf(writer, "%s\n", item.path)
|
||||
|
||||
if applyPurging {
|
||||
// defining the URL this way automatically escapes complex path names
|
||||
c.httpDelete(item.path, verbose)
|
||||
}
|
||||
if _, found := db.Get(types.NeedleId(item.fileKey)); !found {
|
||||
fmt.Fprintf(writer, "%d,%x%08x in %s %d not found\n", volumeId, item.fileKey, item.cookie, item.path, pathSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) httpDelete(path util.FullPath, verbose bool) {
|
||||
req, err := http.NewRequest(http.MethodDelete, "", nil)
|
||||
|
||||
req.URL = &url.URL{
|
||||
Scheme: "http",
|
||||
Host: c.env.option.FilerAddress.ToHttpAddress(),
|
||||
Path: string(path),
|
||||
}
|
||||
if verbose {
|
||||
fmt.Printf("full HTTP delete request to be sent: %v\n", req)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Errorf("HTTP delete request error: %v\n", err)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
return
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Errorf("DELETE fetch error: %v\n", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Errorf("DELETE response error: %v\n", err)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Println("delete response Status : ", resp.Status)
|
||||
fmt.Println("delete response Headers : ", resp.Header)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commandVolumeFsck) oneVolumeFileIdsSubtractFilerFileIds(tempFolder string, volumeId uint32, writer io.Writer, verbose bool) (inUseCount uint64, orphanFileIds []string, orphanDataSize uint64, err error) {
|
||||
|
||||
@@ -317,16 +317,6 @@ func (l *DiskLocation) VolumesLen() int {
|
||||
return len(l.volumes)
|
||||
}
|
||||
|
||||
func (l *DiskLocation) SetStopping() {
|
||||
l.volumesLock.Lock()
|
||||
for _, v := range l.volumes {
|
||||
v.SetStopping()
|
||||
}
|
||||
l.volumesLock.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *DiskLocation) Close() {
|
||||
l.volumesLock.Lock()
|
||||
for _, v := range l.volumes {
|
||||
|
||||
@@ -327,9 +327,6 @@ func (s *Store) CollectHeartbeat() *master_pb.Heartbeat {
|
||||
|
||||
func (s *Store) SetStopping() {
|
||||
s.isStopping = true
|
||||
for _, location := range s.Locations {
|
||||
location.SetStopping()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Close() {
|
||||
|
||||
@@ -175,21 +175,6 @@ func (v *Volume) DiskType() types.DiskType {
|
||||
return v.location.DiskType
|
||||
}
|
||||
|
||||
func (v *Volume) SetStopping() {
|
||||
v.dataFileAccessLock.Lock()
|
||||
defer v.dataFileAccessLock.Unlock()
|
||||
if v.nm != nil {
|
||||
if err := v.nm.Sync(); err != nil {
|
||||
glog.Warningf("Volume SetStopping fail to sync volume idx %d", v.Id)
|
||||
}
|
||||
}
|
||||
if v.DataBackend != nil {
|
||||
if err := v.DataBackend.Sync(); err != nil {
|
||||
glog.Warningf("Volume SetStopping fail to sync volume %d", v.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close cleanly shuts down this volume
|
||||
func (v *Volume) Close() {
|
||||
v.dataFileAccessLock.Lock()
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -61,7 +62,11 @@ func TestMakeDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCompaction(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dir, err := os.MkdirTemp("", "example")
|
||||
if err != nil {
|
||||
t.Fatalf("temp dir creation: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, 0)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -11,7 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func TestSearchVolumesWithDeletedNeedles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dir, err := os.MkdirTemp("", "example")
|
||||
if err != nil {
|
||||
t.Fatalf("temp dir creation: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, 0)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func ReplicatedWrite(masterFn operation.GetMasterFn, grpcDialOption grpc.DialOpt
|
||||
var remoteLocations []operation.Location
|
||||
if r.FormValue("type") != "replicate" {
|
||||
// this is the initial request
|
||||
remoteLocations, err = GetWritableRemoteReplications(s, grpcDialOption, volumeId, masterFn)
|
||||
remoteLocations, err = getWritableRemoteReplications(s, grpcDialOption, volumeId, masterFn)
|
||||
if err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return
|
||||
@@ -101,7 +101,6 @@ func ReplicatedWrite(masterFn operation.GetMasterFn, grpcDialOption grpc.DialOpt
|
||||
stats.VolumeServerRequestCounter.WithLabelValues(stats.ErrorWriteToReplicas).Inc()
|
||||
err = fmt.Errorf("failed to write to replicas for volume %d: %v", volumeId, err)
|
||||
glog.V(0).Infoln(err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -114,7 +113,7 @@ func ReplicatedDelete(masterFn operation.GetMasterFn, grpcDialOption grpc.DialOp
|
||||
|
||||
var remoteLocations []operation.Location
|
||||
if r.FormValue("type") != "replicate" {
|
||||
remoteLocations, err = GetWritableRemoteReplications(store, grpcDialOption, volumeId, masterFn)
|
||||
remoteLocations, err = getWritableRemoteReplications(store, grpcDialOption, volumeId, masterFn)
|
||||
if err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return
|
||||
@@ -174,7 +173,7 @@ func DistributedOperation(locations []operation.Location, op func(location opera
|
||||
return ret.Error()
|
||||
}
|
||||
|
||||
func GetWritableRemoteReplications(s *storage.Store, grpcDialOption grpc.DialOption, volumeId needle.VolumeId, masterFn operation.GetMasterFn) (remoteLocations []operation.Location, err error) {
|
||||
func getWritableRemoteReplications(s *storage.Store, grpcDialOption grpc.DialOption, volumeId needle.VolumeId, masterFn operation.GetMasterFn) (remoteLocations []operation.Location, err error) {
|
||||
|
||||
v := s.GetVolume(volumeId)
|
||||
if v != nil && v.ReplicaPlacement.GetCopyCount() == 1 {
|
||||
|
||||
@@ -4,11 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOnDisk(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
tmpDir, _ := os.MkdirTemp("", "c")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
totalDiskSizeInKB := int64(32)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
VERSION_NUMBER = fmt.Sprintf("%.02f", 2.90)
|
||||
VERSION_NUMBER = fmt.Sprintf("%.02f", 2.88)
|
||||
VERSION = sizeLimit + " " + VERSION_NUMBER
|
||||
COMMIT = ""
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user