mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-04 13:42:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d74cdf0115 | ||
|
|
84312e6799 | ||
|
|
8db58450a0 | ||
|
|
513fd5e0f6 | ||
|
|
10cddc44de | ||
|
|
4fc074db81 | ||
|
|
fa0d973113 | ||
|
|
042de9359c | ||
|
|
a26a37dfa3 | ||
|
|
ef94ff6837 | ||
|
|
8ae3ea4b1d | ||
|
|
9dca75aea8 | ||
|
|
5198c7a7d5 | ||
|
|
c2269123d3 | ||
|
|
39f636c682 | ||
|
|
a8864e2abd | ||
|
|
12a7e87007 | ||
|
|
ccbe02218a | ||
|
|
4bdb17d086 | ||
|
|
a48785c7df | ||
|
|
5861ba4608 | ||
|
|
7a9d27fce8 | ||
|
|
400c8ef8d8 | ||
|
|
cf552417a7 | ||
|
|
31f1cdeac2 | ||
|
|
d861cbd81b | ||
|
|
4ee58922ef | ||
|
|
86185262bb | ||
|
|
79f2e780c1 | ||
|
|
ddc8643ee0 | ||
|
|
f0ad172e80 | ||
|
|
89eb9f6e70 | ||
|
|
46ef1811a1 | ||
|
|
11c120c040 | ||
|
|
6e5df901e4 | ||
|
|
c60f2d333e | ||
|
|
ca998328c2 | ||
|
|
7deb4b20cd | ||
|
|
f4d77e527d | ||
|
|
cd7bf1a72a | ||
|
|
a8114da02d | ||
|
|
ae74d8f02a | ||
|
|
bcd400995a | ||
|
|
ecb60d5f03 | ||
|
|
11c405fc85 | ||
|
|
72e9d9673e | ||
|
|
c31c5e829c | ||
|
|
83cf94ad2d | ||
|
|
1faafce832 | ||
|
|
e983f91b03 | ||
|
|
4b3cc28cab | ||
|
|
d1c813c470 | ||
|
|
372872ebbf | ||
|
|
e332da4837 | ||
|
|
2acf6be24e | ||
|
|
6cbd786db9 | ||
|
|
d41e6826d3 | ||
|
|
d9a2a7f1c4 | ||
|
|
54410ca955 | ||
|
|
b0127645a5 | ||
|
|
198688c717 | ||
|
|
c83ab91e2e | ||
|
|
3074e9b428 | ||
|
|
b971317a16 | ||
|
|
283d703d50 | ||
|
|
609e228578 | ||
|
|
ba92f2e714 | ||
|
|
16c0304416 | ||
|
|
217e0f9066 | ||
|
|
3e669e6d7b | ||
|
|
07f712c83f | ||
|
|
36c79de3f4 | ||
|
|
e41766feb6 | ||
|
|
1adc8f86ea | ||
|
|
6bc09b18c4 | ||
|
|
c04b7e106f | ||
|
|
ffee470cfc | ||
|
|
fc0cbf565f | ||
|
|
e75633c64f | ||
|
|
9d50867d08 | ||
|
|
ff4c1d5965 | ||
|
|
4480d72cab | ||
|
|
8efd331ce0 | ||
|
|
606c6ae8e8 | ||
|
|
270645f8d7 | ||
|
|
90677e1097 | ||
|
|
3f3268cd1b | ||
|
|
ca0f07a188 | ||
|
|
5985a7d38d | ||
|
|
f5de42fae3 | ||
|
|
1e033d45b8 | ||
|
|
52200a903b | ||
|
|
519b0e1e49 | ||
|
|
8e02e138ea | ||
|
|
5021bea698 | ||
|
|
f952f979d1 | ||
|
|
4d4acc715e | ||
|
|
ba175f81b5 | ||
|
|
995ae91007 | ||
|
|
70da715d8d | ||
|
|
93a7df500f | ||
|
|
6a573d62e0 | ||
|
|
d19bbee98d |
@@ -33,8 +33,9 @@ jobs:
|
||||
run: echo BUILD_TIME=$(date -u +%Y-%m-%d-%H-%M) >> ${GITHUB_ENV}
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.14
|
||||
uses: wangyoucao577/go-release-action@v1.17
|
||||
with:
|
||||
goversion: 1.16
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
@@ -49,8 +50,9 @@ jobs:
|
||||
asset_name: "weed-large-disk-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.14
|
||||
uses: wangyoucao577/go-release-action@v1.17
|
||||
with:
|
||||
goversion: 1.16
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
@@ -60,5 +62,5 @@ jobs:
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed-
|
||||
binary_name: weed
|
||||
asset_name: "weed-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.15.x
|
||||
- 1.16.x
|
||||
|
||||
before_install:
|
||||
|
||||
@@ -68,12 +68,16 @@ Table of Contents
|
||||
* [License](#license)
|
||||
|
||||
|
||||
## Quick Start ##
|
||||
## 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.
|
||||
|
||||
Also, to increase capacity, just add more volume servers by running `weed volume -dir="/some/data/dir2" -mserver="<master_host>:9333" -port=8081` locally, or on a different machine, or on thousands of machines. That is it!
|
||||
|
||||
## Quick Start for S3 API on Docker ##
|
||||
|
||||
`docker run -p 8333:8333 chrislusf/seaweedfs server -s3`
|
||||
|
||||
## Introduction ##
|
||||
|
||||
SeaweedFS is a simple and highly scalable distributed file system. There are two objectives:
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
FROM alpine
|
||||
|
||||
ARG RELEASE=latest # 'latest' or 'dev'
|
||||
# 'latest' or 'dev'
|
||||
ARG RELEASE=latest
|
||||
|
||||
RUN \
|
||||
ARCH=$(if [ $(uname -m) == "x86_64" ] && [ $(getconf LONG_BIT) == "64" ]; then echo "amd64"; \
|
||||
|
||||
@@ -26,9 +26,10 @@ services:
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8111:8111
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: '-v=1 filer -master="master:9333"'
|
||||
command: '-v=1 filer -master="master:9333" -iam'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
|
||||
@@ -42,6 +42,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.11.0 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/jinzhu/copier v0.2.8
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/karlseguin/ccache v2.0.3+incompatible // indirect
|
||||
github.com/karlseguin/ccache/v2 v2.0.7
|
||||
@@ -61,7 +62,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.3.0
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
|
||||
github.com/seaweedfs/fuse v1.1.3
|
||||
github.com/seaweedfs/fuse v1.1.4
|
||||
github.com/seaweedfs/goexif v1.0.2
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
|
||||
@@ -439,6 +439,8 @@ github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjL
|
||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jinzhu/copier v0.2.8 h1:N8MbL5niMwE3P4dOwurJixz5rMkKfujmMRFmAanSzWE=
|
||||
github.com/jinzhu/copier v0.2.8/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@@ -691,6 +693,8 @@ github.com/seaweedfs/fuse v1.1.1 h1:WD51YFJcBViOx8I89jeqPD+vAKl4EowzBy9GUw0plb0=
|
||||
github.com/seaweedfs/fuse v1.1.1/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8=
|
||||
github.com/seaweedfs/fuse v1.1.3 h1:0DddotXwSRGbYG2kynoJyr8GHCy30Z2SpdhP3vdyijY=
|
||||
github.com/seaweedfs/fuse v1.1.3/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8=
|
||||
github.com/seaweedfs/fuse v1.1.4 h1:YYqkK86agMhXRSwR+wFbRI8ikMgk3kL6PNTna1MAHyQ=
|
||||
github.com/seaweedfs/fuse v1.1.4/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8=
|
||||
github.com/seaweedfs/goexif v1.0.2 h1:p+rTXYdQ2mgxd+1JaTrQ9N8DvYuw9UH9xgYmJ+Bb29E=
|
||||
github.com/seaweedfs/goexif v1.0.2/go.mod h1:MrKs5LK0HXdffrdCZrW3OIMegL2xXpC6ThLyXMyjdrk=
|
||||
github.com/secsy/goftp v0.0.0-20190720192957-f31499d7c79a h1:C6IhVTxNkhlb0tlCB6JfHOUv1f0xHPK7V8X4HlJZEJw=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
appVersion: "2.39"
|
||||
version: 2.39
|
||||
appVersion: "2.42"
|
||||
version: 2.42
|
||||
|
||||
@@ -23,6 +23,6 @@ spec:
|
||||
targetPort: {{ .Values.volume.metricsPort }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
selector:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: volume
|
||||
@@ -4,7 +4,7 @@ global:
|
||||
registry: ""
|
||||
repository: ""
|
||||
imageName: chrislusf/seaweedfs
|
||||
# imageTag: "2.39" - started using {.Chart.appVersion}
|
||||
# imageTag: "2.42" - started using {.Chart.appVersion}
|
||||
imagePullPolicy: IfNotPresent
|
||||
imagePullSecrets: imagepullsecret
|
||||
restartPolicy: Always
|
||||
|
||||
@@ -52,7 +52,7 @@ func main() {
|
||||
}
|
||||
|
||||
func genFile(grpcDialOption grpc.DialOption, i int) (*operation.AssignResult, string) {
|
||||
assignResult, err := operation.Assign(*master, grpcDialOption, &operation.VolumeAssignRequest{
|
||||
assignResult, err := operation.Assign(func() string { return *master }, grpcDialOption, &operation.VolumeAssignRequest{
|
||||
Count: 1,
|
||||
Replication: *replication,
|
||||
})
|
||||
|
||||
@@ -215,7 +215,7 @@ func runExport(cmd *Command, args []string) bool {
|
||||
|
||||
err = storage.ScanVolumeFile(util.ResolvePath(*export.dir), *export.collection, vid, storage.NeedleMapInMemory, volumeFileScanner)
|
||||
if err != nil && err != io.EOF {
|
||||
glog.Fatalf("Export Volume File [ERROR] %s\n", err)
|
||||
glog.Errorf("Export Volume File [ERROR] %s\n", err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+19
-17
@@ -207,16 +207,6 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi
|
||||
}
|
||||
|
||||
mode := fi.Mode()
|
||||
if mode.IsDir() {
|
||||
files, _ := ioutil.ReadDir(fileOrDir)
|
||||
for _, subFileOrDir := range files {
|
||||
if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), destPath+fi.Name()+"/", fileCopyTaskChan); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
uid, gid := util.GetFileUidGid(fi)
|
||||
|
||||
fileCopyTaskChan <- FileCopyTask{
|
||||
@@ -228,6 +218,16 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi
|
||||
gid: gid,
|
||||
}
|
||||
|
||||
if mode.IsDir() {
|
||||
files, _ := ioutil.ReadDir(fileOrDir)
|
||||
println("checking directory", fileOrDir)
|
||||
for _, subFileOrDir := range files {
|
||||
if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), destPath+fi.Name()+"/", fileCopyTaskChan); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -293,20 +293,22 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err
|
||||
|
||||
// upload the file content
|
||||
fileName := filepath.Base(f.Name())
|
||||
mimeType := detectMimeType(f)
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var mimeType string
|
||||
|
||||
var chunks []*filer_pb.FileChunk
|
||||
var assignResult *filer_pb.AssignVolumeResponse
|
||||
var assignError error
|
||||
|
||||
if task.fileSize > 0 {
|
||||
if task.fileMode & os.ModeDir == 0 && task.fileSize > 0 {
|
||||
|
||||
mimeType = detectMimeType(f)
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// assign a volume
|
||||
err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
err = pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.AssignVolumeRequest{
|
||||
Count: 1,
|
||||
|
||||
@@ -167,6 +167,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
if *option.nonempty {
|
||||
options = append(options, fuse.AllowNonEmptyMount())
|
||||
}
|
||||
if *option.readOnly {
|
||||
options = append(options, fuse.ReadOnly())
|
||||
}
|
||||
|
||||
// find mount point
|
||||
mountRoot := filerMountRootPath
|
||||
@@ -191,7 +194,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
CacheDir: *option.cacheDir,
|
||||
CacheSizeMB: *option.cacheSizeMB,
|
||||
DataCenter: *option.dataCenter,
|
||||
EntryCacheTtl: 3 * time.Second,
|
||||
MountUid: uid,
|
||||
MountGid: gid,
|
||||
MountMode: mountMode,
|
||||
@@ -201,7 +203,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
VolumeServerAccess: *mountOptions.volumeServerAccess,
|
||||
Cipher: cipher,
|
||||
UidGidMapper: uidGidMapper,
|
||||
ReadOnly: *option.readOnly,
|
||||
})
|
||||
|
||||
// mount
|
||||
|
||||
@@ -281,7 +281,7 @@ index.max_result_window = 10000
|
||||
# Make sure they are not the same if using the same store type!
|
||||
# 4. Set enabled to true
|
||||
#
|
||||
# The following is just using cassandra as an example
|
||||
# The following is just using redis as an example
|
||||
##########################
|
||||
[redis2.tmp]
|
||||
enabled = false
|
||||
|
||||
@@ -58,7 +58,8 @@ var (
|
||||
serverDisableHttp = cmdServer.Flag.Bool("disableHttp", false, "disable http requests, only gRPC operations are allowed.")
|
||||
volumeDataFolders = cmdServer.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
|
||||
volumeMaxDataVolumeCounts = cmdServer.Flag.String("volume.max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.")
|
||||
volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.")
|
||||
volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).")
|
||||
volumeMinFreeSpace = cmdServer.Flag.String("volume.minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.")
|
||||
serverMetricsHttpPort = cmdServer.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
|
||||
// pulseSeconds = cmdServer.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats")
|
||||
@@ -244,8 +245,8 @@ func runServer(cmd *Command, args []string) bool {
|
||||
|
||||
// start volume server
|
||||
if *isStartingVolumeServer {
|
||||
go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, *volumeMinFreeSpacePercent)
|
||||
|
||||
minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent)
|
||||
go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, minFreeSpaces)
|
||||
}
|
||||
|
||||
if *isStartingMasterServer {
|
||||
@@ -253,6 +254,4 @@ func runServer(cmd *Command, args []string) bool {
|
||||
}
|
||||
|
||||
select {}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
+10
-18
@@ -57,7 +57,6 @@ type VolumeServerOptions struct {
|
||||
compactionMBPerSecond *int
|
||||
fileSizeLimitMB *int
|
||||
concurrentUploadLimitMB *int
|
||||
minFreeSpacePercents []float32
|
||||
pprof *bool
|
||||
preStopSeconds *int
|
||||
metricsHttpPort *int
|
||||
@@ -105,7 +104,8 @@ var (
|
||||
volumeFolders = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
|
||||
maxVolumeCounts = cmdVolume.Flag.String("max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.")
|
||||
volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
|
||||
minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.")
|
||||
minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).")
|
||||
minFreeSpace = cmdVolume.Flag.String("minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.")
|
||||
)
|
||||
|
||||
func runVolume(cmd *Command, args []string) bool {
|
||||
@@ -120,12 +120,13 @@ func runVolume(cmd *Command, args []string) bool {
|
||||
|
||||
go stats_collect.StartMetricsServer(*v.metricsHttpPort)
|
||||
|
||||
v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, *minFreeSpacePercent)
|
||||
minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent)
|
||||
v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, minFreeSpaces)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption, minFreeSpacePercent string) {
|
||||
func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption string, minFreeSpaces []util.MinFreeSpace) {
|
||||
|
||||
// Set multiple folders and each folder's max volume count limit'
|
||||
v.folders = strings.Split(volumeFolders, ",")
|
||||
@@ -153,22 +154,13 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
|
||||
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
|
||||
}
|
||||
|
||||
// set minFreeSpacePercent
|
||||
minFreeSpacePercentStrings := strings.Split(minFreeSpacePercent, ",")
|
||||
for _, freeString := range minFreeSpacePercentStrings {
|
||||
if value, e := strconv.ParseFloat(freeString, 32); e == nil {
|
||||
v.minFreeSpacePercents = append(v.minFreeSpacePercents, float32(value))
|
||||
} else {
|
||||
glog.Fatalf("The value specified in -minFreeSpacePercent not a valid value %s", freeString)
|
||||
}
|
||||
}
|
||||
if len(v.minFreeSpacePercents) == 1 && len(v.folders) > 1 {
|
||||
if len(minFreeSpaces) == 1 && len(v.folders) > 1 {
|
||||
for i := 0; i < len(v.folders)-1; i++ {
|
||||
v.minFreeSpacePercents = append(v.minFreeSpacePercents, v.minFreeSpacePercents[0])
|
||||
minFreeSpaces = append(minFreeSpaces, minFreeSpaces[0])
|
||||
}
|
||||
}
|
||||
if len(v.folders) != len(v.minFreeSpacePercents) {
|
||||
glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(v.minFreeSpacePercents))
|
||||
if len(v.folders) != len(minFreeSpaces) {
|
||||
glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(minFreeSpaces))
|
||||
}
|
||||
|
||||
// set disk types
|
||||
@@ -231,7 +223,7 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
|
||||
|
||||
volumeServer := weed_server.NewVolumeServer(volumeMux, publicVolumeMux,
|
||||
*v.ip, *v.port, *v.publicUrl,
|
||||
v.folders, v.folderMaxLimits, v.minFreeSpacePercents, diskTypes,
|
||||
v.folders, v.folderMaxLimits, minFreeSpaces, diskTypes,
|
||||
*v.idxFolder,
|
||||
volumeNeedleMapKind,
|
||||
strings.Split(masters, ","), 5, *v.dataCenter, *v.rack,
|
||||
|
||||
@@ -276,7 +276,9 @@ func (store *AbstractSqlStore) DeleteFolderChildren(ctx context.Context, fullpat
|
||||
}
|
||||
}
|
||||
|
||||
res, err := db.ExecContext(ctx, store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath)), fullpath)
|
||||
glog.V(4).Infof("delete %s SQL %s %d", string(shortPath), store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath)))
|
||||
|
||||
res, err := db.ExecContext(ctx, store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath)), string(shortPath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleteFolderChildren %s: %s", fullpath, err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||
"math"
|
||||
@@ -43,12 +42,11 @@ func ETagEntry(entry *Entry) (etag string) {
|
||||
|
||||
func ETagChunks(chunks []*filer_pb.FileChunk) (etag string) {
|
||||
if len(chunks) == 1 {
|
||||
return chunks[0].ETag
|
||||
return fmt.Sprintf("%x", util.Base64Md5ToBytes(chunks[0].ETag))
|
||||
}
|
||||
md5_digests := [][]byte{}
|
||||
for _, c := range chunks {
|
||||
md5_decoded, _ := hex.DecodeString(c.ETag)
|
||||
md5_digests = append(md5_digests, md5_decoded)
|
||||
md5_digests = append(md5_digests, util.Base64Md5ToBytes(c.ETag))
|
||||
}
|
||||
return fmt.Sprintf("%x-%d", util.Md5(bytes.Join(md5_digests, nil)), len(chunks))
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func (f *Filer) LoadBuckets() {
|
||||
|
||||
limit := int64(math.MaxInt32)
|
||||
|
||||
entries, _, err := f.ListDirectoryEntries(context.Background(), util.FullPath(f.DirBucketsPath), "", false, limit, "", "")
|
||||
entries, _, err := f.ListDirectoryEntries(context.Background(), util.FullPath(f.DirBucketsPath), "", false, limit, "", "", "")
|
||||
|
||||
if err != nil {
|
||||
glog.V(1).Infof("no buckets found: %v", err)
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
FilerConfName = "filer.conf"
|
||||
IamConfigDirecotry = "/etc/iam"
|
||||
IamIdentityFile = "identity.json"
|
||||
IamPoliciesFile = "policies.json"
|
||||
)
|
||||
|
||||
type FilerConf struct {
|
||||
|
||||
@@ -73,7 +73,7 @@ func (f *Filer) doBatchDeleteFolderMetaAndData(ctx context.Context, entry *Entry
|
||||
includeLastFile := false
|
||||
if !isDeletingBucket {
|
||||
for {
|
||||
entries, _, err := f.ListDirectoryEntries(ctx, entry.FullPath, lastFileName, includeLastFile, PaginationSize, "", "")
|
||||
entries, _, err := f.ListDirectoryEntries(ctx, entry.FullPath, lastFileName, includeLastFile, PaginationSize, "", "", "")
|
||||
if err != nil {
|
||||
glog.Errorf("list folder %s: %v", entry.FullPath, err)
|
||||
return nil, nil, fmt.Errorf("list folder %s: %v", entry.FullPath, err)
|
||||
|
||||
@@ -116,13 +116,13 @@ func (f *Filer) ReadPersistedLogBuffer(startTime time.Time, eachLogEntryFn func(
|
||||
sizeBuf := make([]byte, 4)
|
||||
startTsNs := startTime.UnixNano()
|
||||
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 366, "", "")
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 366, "", "", "")
|
||||
if listDayErr != nil {
|
||||
return lastTsNs, fmt.Errorf("fail to list log by day: %v", listDayErr)
|
||||
}
|
||||
for _, dayEntry := range dayEntries {
|
||||
// println("checking day", dayEntry.FullPath)
|
||||
hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, 24*60, "", "")
|
||||
hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, 24*60, "", "", "")
|
||||
if listHourMinuteErr != nil {
|
||||
return lastTsNs, fmt.Errorf("fail to list log %s by day: %v", dayEntry.Name(), listHourMinuteErr)
|
||||
}
|
||||
|
||||
+21
-13
@@ -20,9 +20,9 @@ func splitPattern(pattern string) (prefix string, restPattern string) {
|
||||
}
|
||||
|
||||
// For now, prefix and namePattern are mutually exclusive
|
||||
func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string) (entries []*Entry, hasMore bool, err error) {
|
||||
func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, namePatternExclude string) (entries []*Entry, hasMore bool, err error) {
|
||||
|
||||
_, err = f.StreamListDirectoryEntries(ctx, p, startFileName, inclusive, limit+1, prefix, namePattern, func(entry *Entry) bool {
|
||||
_, err = f.StreamListDirectoryEntries(ctx, p, startFileName, inclusive, limit+1, prefix, namePattern, namePatternExclude, func(entry *Entry) bool {
|
||||
entries = append(entries, entry)
|
||||
return true
|
||||
})
|
||||
@@ -36,7 +36,7 @@ func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, start
|
||||
}
|
||||
|
||||
// For now, prefix and namePattern are mutually exclusive
|
||||
func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) {
|
||||
func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, namePatternExclude string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) {
|
||||
if strings.HasSuffix(string(p), "/") && len(p) > 1 {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
@@ -47,30 +47,38 @@ func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath,
|
||||
}
|
||||
var missedCount int64
|
||||
|
||||
missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, startFileName, inclusive, limit, prefix, restNamePattern, eachEntryFunc)
|
||||
missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, startFileName, inclusive, limit, prefix, restNamePattern, namePatternExclude, eachEntryFunc)
|
||||
|
||||
for missedCount > 0 && err == nil {
|
||||
missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, lastFileName, false, missedCount, prefix, restNamePattern, eachEntryFunc)
|
||||
missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, lastFileName, false, missedCount, prefix, restNamePattern, namePatternExclude, eachEntryFunc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix, restNamePattern string, eachEntryFunc ListEachEntryFunc) (missedCount int64, lastFileName string, err error) {
|
||||
func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix, restNamePattern string, namePatternExclude string, eachEntryFunc ListEachEntryFunc) (missedCount int64, lastFileName string, err error) {
|
||||
|
||||
if len(restNamePattern) == 0 {
|
||||
if len(restNamePattern) == 0 && len(namePatternExclude) == 0{
|
||||
lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, eachEntryFunc)
|
||||
return 0, lastFileName, err
|
||||
}
|
||||
|
||||
lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, func(entry *Entry) bool {
|
||||
nameToTest := strings.ToLower(entry.Name())
|
||||
if matched, matchErr := filepath.Match(restNamePattern, nameToTest[len(prefix):]); matchErr == nil && matched {
|
||||
if !eachEntryFunc(entry) {
|
||||
return false
|
||||
nameToTest := entry.Name()
|
||||
if len(namePatternExclude) > 0 {
|
||||
if matched, matchErr := filepath.Match(namePatternExclude, nameToTest); matchErr == nil && matched {
|
||||
missedCount++
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
missedCount++
|
||||
}
|
||||
if len(restNamePattern) > 0 {
|
||||
if matched, matchErr := filepath.Match(restNamePattern, nameToTest[len(prefix):]); matchErr == nil && !matched {
|
||||
missedCount++
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !eachEntryFunc(entry) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -149,8 +149,8 @@ func (fsw *FilerStoreWrapper) FindEntry(ctx context.Context, fp util.FullPath) (
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "find").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
glog.V(4).Infof("FindEntry %s", fp)
|
||||
entry, err = actualStore.FindEntry(ctx, fp)
|
||||
glog.V(4).Infof("FindEntry %s: %v", fp, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -51,14 +51,14 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "")
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
@@ -77,7 +77,7 @@ func TestEmptyRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("list entries: %v", err)
|
||||
return
|
||||
|
||||
@@ -49,14 +49,14 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "")
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
@@ -75,7 +75,7 @@ func TestEmptyRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("list entries: %v", err)
|
||||
return
|
||||
|
||||
@@ -49,14 +49,14 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "")
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
@@ -75,7 +75,7 @@ func TestEmptyRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("list entries: %v", err)
|
||||
return
|
||||
|
||||
+19
-3
@@ -139,13 +139,15 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
}
|
||||
glog.V(4).Infof("read [%d,%d), %d/%d chunk %s [%d,%d)", chunkStart, chunkStop, i, len(c.chunkViews), chunk.FileId, chunk.LogicOffset-chunk.Offset, chunk.LogicOffset-chunk.Offset+int64(chunk.Size))
|
||||
var buffer []byte
|
||||
buffer, err = c.readFromWholeChunkData(chunk, nextChunk)
|
||||
bufferOffset := chunkStart - chunk.LogicOffset + chunk.Offset
|
||||
bufferLength := chunkStop - chunkStart
|
||||
buffer, err = c.readChunkSlice(chunk, nextChunk, uint64(bufferOffset), uint64(bufferLength))
|
||||
if err != nil {
|
||||
glog.Errorf("fetching chunk %+v: %v\n", chunk, err)
|
||||
return
|
||||
}
|
||||
bufferOffset := chunkStart - chunk.LogicOffset + chunk.Offset
|
||||
copied := copy(p[startOffset-offset:chunkStop-chunkStart+startOffset-offset], buffer[bufferOffset:bufferOffset+chunkStop-chunkStart])
|
||||
|
||||
copied := copy(p[startOffset-offset:chunkStop-chunkStart+startOffset-offset], buffer)
|
||||
n += copied
|
||||
startOffset, remaining = startOffset+int64(copied), remaining-int64(copied)
|
||||
}
|
||||
@@ -167,6 +169,20 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) readChunkSlice(chunkView *ChunkView, nextChunkViews *ChunkView, offset, length uint64) ([]byte, error) {
|
||||
|
||||
chunkSlice := c.chunkCache.GetChunkSlice(chunkView.FileId, offset, length)
|
||||
if len(chunkSlice) > 0 {
|
||||
return chunkSlice, nil
|
||||
}
|
||||
chunkData, err := c.readFromWholeChunkData(chunkView, nextChunkViews)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wanted := min(int64(length), int64(len(chunkData))-int64(offset))
|
||||
return chunkData[offset : int64(offset)+wanted], nil
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) readFromWholeChunkData(chunkView *ChunkView, nextChunkViews ...*ChunkView) (chunkData []byte, err error) {
|
||||
|
||||
if c.lastChunkFileId == chunkView.FileId {
|
||||
|
||||
@@ -20,6 +20,11 @@ func (m *mockChunkCache) GetChunk(fileId string, minSize uint64) (data []byte) {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func(m *mockChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChunkCache) SetChunk(fileId string, data []byte) {
|
||||
}
|
||||
|
||||
|
||||
@@ -53,14 +53,14 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "")
|
||||
entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
}
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if len(entries) != 1 {
|
||||
t.Errorf("list entries count: %v", len(entries))
|
||||
return
|
||||
@@ -79,7 +79,7 @@ func TestEmptyRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// checking one upper directory
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "")
|
||||
entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("list entries: %v", err)
|
||||
return
|
||||
|
||||
+150
-148
@@ -24,9 +24,12 @@ type Dir struct {
|
||||
wfs *WFS
|
||||
entry *filer_pb.Entry
|
||||
parent *Dir
|
||||
id uint64
|
||||
}
|
||||
|
||||
var _ = fs.Node(&Dir{})
|
||||
|
||||
//var _ = fs.NodeIdentifier(&Dir{})
|
||||
var _ = fs.NodeCreater(&Dir{})
|
||||
var _ = fs.NodeMknoder(&Dir{})
|
||||
var _ = fs.NodeMkdirer(&Dir{})
|
||||
@@ -42,6 +45,10 @@ var _ = fs.NodeRemovexattrer(&Dir{})
|
||||
var _ = fs.NodeListxattrer(&Dir{})
|
||||
var _ = fs.NodeForgetter(&Dir{})
|
||||
|
||||
func (dir *Dir) xId() uint64 {
|
||||
return dir.id
|
||||
}
|
||||
|
||||
func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||
|
||||
// https://github.com/bazil/fuse/issues/196
|
||||
@@ -53,17 +60,18 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
glog.V(3).Infof("dir Attr %s,err: %+v", dir.FullPath(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
// attr.Inode = util.FullPath(dir.FullPath()).AsInode()
|
||||
attr.Mode = os.FileMode(dir.entry.Attributes.FileMode) | os.ModeDir
|
||||
attr.Mtime = time.Unix(dir.entry.Attributes.Mtime, 0)
|
||||
attr.Crtime = time.Unix(dir.entry.Attributes.Crtime, 0)
|
||||
attr.Gid = dir.entry.Attributes.Gid
|
||||
attr.Uid = dir.entry.Attributes.Uid
|
||||
// attr.Inode = dir.Id()
|
||||
attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir
|
||||
attr.Mtime = time.Unix(entry.Attributes.Mtime, 0)
|
||||
attr.Crtime = time.Unix(entry.Attributes.Crtime, 0)
|
||||
attr.Gid = entry.Attributes.Gid
|
||||
attr.Uid = entry.Attributes.Uid
|
||||
|
||||
glog.V(4).Infof("dir Attr %s, attr: %+v", dir.FullPath(), attr)
|
||||
|
||||
@@ -74,16 +82,18 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f
|
||||
|
||||
glog.V(4).Infof("dir Getxattr %s", dir.FullPath())
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return getxattr(dir.entry, req, resp)
|
||||
return getxattr(entry, req, resp)
|
||||
}
|
||||
|
||||
func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) {
|
||||
// attr.Inode = 1 // filer2.FullPath(dir.Path).AsInode()
|
||||
attr.Valid = time.Second
|
||||
attr.Inode = 1 // dir.Id()
|
||||
attr.Uid = dir.wfs.option.MountUid
|
||||
attr.Gid = dir.wfs.option.MountGid
|
||||
attr.Mode = dir.wfs.option.MountMode
|
||||
@@ -102,49 +112,67 @@ func (dir *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dir *Dir) newFile(name string, entry *filer_pb.Entry) fs.Node {
|
||||
f := dir.wfs.fsNodeCache.EnsureFsNode(util.NewFullPath(dir.FullPath(), name), func() fs.Node {
|
||||
return &File{
|
||||
Name: name,
|
||||
dir: dir,
|
||||
wfs: dir.wfs,
|
||||
entry: entry,
|
||||
entryViewCache: nil,
|
||||
}
|
||||
})
|
||||
f.(*File).dir = dir // in case dir node was created later
|
||||
return f
|
||||
func (dir *Dir) newFile(name string) fs.Node {
|
||||
|
||||
fileFullPath := util.NewFullPath(dir.FullPath(), name)
|
||||
fileId := fileFullPath.AsInode()
|
||||
dir.wfs.handlesLock.Lock()
|
||||
existingHandle, found := dir.wfs.handles[fileId]
|
||||
dir.wfs.handlesLock.Unlock()
|
||||
|
||||
if found {
|
||||
glog.V(4).Infof("newFile found opened file handle: %+v", fileFullPath)
|
||||
return existingHandle.f
|
||||
}
|
||||
return &File{
|
||||
Name: name,
|
||||
dir: dir,
|
||||
wfs: dir.wfs,
|
||||
id: fileId,
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *Dir) newDirectory(fullpath util.FullPath, entry *filer_pb.Entry) fs.Node {
|
||||
func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node {
|
||||
|
||||
return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir, id: fullpath.AsInode()}
|
||||
|
||||
d := dir.wfs.fsNodeCache.EnsureFsNode(fullpath, func() fs.Node {
|
||||
return &Dir{name: entry.Name, wfs: dir.wfs, entry: entry, parent: dir}
|
||||
})
|
||||
d.(*Dir).parent = dir // in case dir node was created later
|
||||
return d
|
||||
}
|
||||
|
||||
func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest,
|
||||
resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return nil, nil, fuse.EPERM
|
||||
}
|
||||
exclusive := req.Flags&fuse.OpenExclusive != 0
|
||||
isDirectory := req.Mode&os.ModeDir > 0
|
||||
|
||||
request, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, req.Flags&fuse.OpenExclusive != 0)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if exclusive || isDirectory {
|
||||
_, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, exclusive)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
var node fs.Node
|
||||
if request.Entry.IsDirectory {
|
||||
node = dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name), request.Entry)
|
||||
if isDirectory {
|
||||
node = dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name))
|
||||
return node, nil, nil
|
||||
}
|
||||
|
||||
node = dir.newFile(req.Name, request.Entry)
|
||||
node = dir.newFile(req.Name)
|
||||
file := node.(*File)
|
||||
file.entry = &filer_pb.Entry{
|
||||
Name: req.Name,
|
||||
IsDirectory: req.Mode&os.ModeDir > 0,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(req.Mode &^ dir.wfs.option.Umask),
|
||||
Uid: req.Uid,
|
||||
Gid: req.Gid,
|
||||
Collection: dir.wfs.option.Collection,
|
||||
Replication: dir.wfs.option.Replication,
|
||||
TtlSec: dir.wfs.option.TtlSec,
|
||||
},
|
||||
}
|
||||
file.dirtyMetadata = true
|
||||
fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid)
|
||||
return file, fh, nil
|
||||
|
||||
@@ -152,23 +180,20 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest,
|
||||
|
||||
func (dir *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
|
||||
request, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, false)
|
||||
_, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var node fs.Node
|
||||
node = dir.newFile(req.Name, request.Entry)
|
||||
node = dir.newFile(req.Name)
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, exlusive bool) (*filer_pb.CreateEntryRequest, error) {
|
||||
func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, exclusive bool) (*filer_pb.CreateEntryRequest, error) {
|
||||
dirFullPath := dir.FullPath()
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
Directory: dir.FullPath(),
|
||||
Directory: dirFullPath,
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: name,
|
||||
IsDirectory: mode&os.ModeDir > 0,
|
||||
@@ -183,10 +208,10 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex
|
||||
TtlSec: dir.wfs.option.TtlSec,
|
||||
},
|
||||
},
|
||||
OExcl: exlusive,
|
||||
OExcl: exclusive,
|
||||
Signatures: []int32{dir.wfs.signature},
|
||||
}
|
||||
glog.V(1).Infof("create %s/%s", dir.FullPath(), name)
|
||||
glog.V(1).Infof("create %s/%s", dirFullPath, name)
|
||||
|
||||
err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
@@ -197,11 +222,14 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex
|
||||
if strings.Contains(err.Error(), "EEXIST") {
|
||||
return fuse.EEXIST
|
||||
}
|
||||
glog.V(0).Infof("create %s/%s: %v", dir.FullPath(), name, err)
|
||||
glog.V(0).Infof("create %s/%s: %v", dirFullPath, name, err)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))
|
||||
if err := dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil {
|
||||
glog.Errorf("local InsertEntry dir %s/%s: %v", dirFullPath, name, err)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -210,10 +238,6 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex
|
||||
|
||||
func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("mkdir %s: %s", dir.FullPath(), req.Name)
|
||||
|
||||
newEntry := &filer_pb.Entry{
|
||||
@@ -228,35 +252,40 @@ func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, err
|
||||
},
|
||||
}
|
||||
|
||||
dirFullPath := dir.FullPath()
|
||||
|
||||
err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
dir.wfs.mapPbIdFromLocalToFiler(newEntry)
|
||||
defer dir.wfs.mapPbIdFromFilerToLocal(newEntry)
|
||||
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
Directory: dir.FullPath(),
|
||||
Directory: dirFullPath,
|
||||
Entry: newEntry,
|
||||
Signatures: []int32{dir.wfs.signature},
|
||||
}
|
||||
|
||||
glog.V(1).Infof("mkdir: %v", request)
|
||||
if err := filer_pb.CreateEntry(client, request); err != nil {
|
||||
glog.V(0).Infof("mkdir %s/%s: %v", dir.FullPath(), req.Name, err)
|
||||
glog.V(0).Infof("mkdir %s/%s: %v", dirFullPath, req.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))
|
||||
if err := dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil {
|
||||
glog.Errorf("local mkdir dir %s/%s: %v", dirFullPath, req.Name, err)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
node := dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name), newEntry)
|
||||
node := dir.newDirectory(util.NewFullPath(dirFullPath, req.Name))
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
glog.V(0).Infof("mkdir %s/%s: %v", dir.FullPath(), req.Name, err)
|
||||
glog.V(0).Infof("mkdir %s/%s: %v", dirFullPath, req.Name, err)
|
||||
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
@@ -272,40 +301,40 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.
|
||||
glog.Errorf("dir Lookup %s: %v", dirPath, visitErr)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
cachedEntry, cacheErr := dir.wfs.metaCache.FindEntry(context.Background(), fullFilePath)
|
||||
localEntry, cacheErr := dir.wfs.metaCache.FindEntry(context.Background(), fullFilePath)
|
||||
if cacheErr == filer_pb.ErrNotFound {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
entry := cachedEntry.ToProtoEntry()
|
||||
|
||||
if entry == nil {
|
||||
if localEntry == nil {
|
||||
// glog.V(3).Infof("dir Lookup cache miss %s", fullFilePath)
|
||||
entry, err = filer_pb.GetEntry(dir.wfs, fullFilePath)
|
||||
entry, err := filer_pb.GetEntry(dir.wfs, fullFilePath)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("dir GetEntry %s: %v", fullFilePath, err)
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
localEntry = filer.FromPbEntry(string(dirPath), entry)
|
||||
} else {
|
||||
glog.V(4).Infof("dir Lookup cache hit %s", fullFilePath)
|
||||
}
|
||||
|
||||
if entry != nil {
|
||||
if entry.IsDirectory {
|
||||
node = dir.newDirectory(fullFilePath, entry)
|
||||
if localEntry != nil {
|
||||
if localEntry.IsDirectory() {
|
||||
node = dir.newDirectory(fullFilePath)
|
||||
} else {
|
||||
node = dir.newFile(req.Name, entry)
|
||||
node = dir.newFile(req.Name)
|
||||
}
|
||||
|
||||
// resp.EntryValid = time.Second
|
||||
// resp.Attr.Inode = fullFilePath.AsInode()
|
||||
resp.Attr.Inode = fullFilePath.AsInode()
|
||||
resp.Attr.Valid = time.Second
|
||||
resp.Attr.Mtime = time.Unix(entry.Attributes.Mtime, 0)
|
||||
resp.Attr.Crtime = time.Unix(entry.Attributes.Crtime, 0)
|
||||
resp.Attr.Mode = os.FileMode(entry.Attributes.FileMode)
|
||||
resp.Attr.Gid = entry.Attributes.Gid
|
||||
resp.Attr.Uid = entry.Attributes.Uid
|
||||
if entry.HardLinkCounter > 0 {
|
||||
resp.Attr.Nlink = uint32(entry.HardLinkCounter)
|
||||
resp.Attr.Mtime = localEntry.Attr.Mtime
|
||||
resp.Attr.Crtime = localEntry.Attr.Crtime
|
||||
resp.Attr.Mode = localEntry.Attr.Mode
|
||||
resp.Attr.Gid = localEntry.Attr.Gid
|
||||
resp.Attr.Uid = localEntry.Attr.Uid
|
||||
if localEntry.HardLinkCounter > 0 {
|
||||
resp.Attr.Nlink = uint32(localEntry.HardLinkCounter)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
@@ -320,15 +349,14 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) {
|
||||
dirPath := util.FullPath(dir.FullPath())
|
||||
glog.V(4).Infof("dir ReadDirAll %s", dirPath)
|
||||
|
||||
processEachEntryFn := func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if entry.IsDirectory {
|
||||
dirent := fuse.Dirent{Name: entry.Name, Type: fuse.DT_Dir}
|
||||
processEachEntryFn := func(entry *filer.Entry, isLast bool) {
|
||||
if entry.IsDirectory() {
|
||||
dirent := fuse.Dirent{Name: entry.Name(), Type: fuse.DT_Dir, Inode: dirPath.Child(entry.Name()).AsInode()}
|
||||
ret = append(ret, dirent)
|
||||
} else {
|
||||
dirent := fuse.Dirent{Name: entry.Name, Type: findFileType(uint16(entry.Attributes.FileMode))}
|
||||
dirent := fuse.Dirent{Name: entry.Name(), Type: findFileType(uint16(entry.Attr.Mode)), Inode: dirPath.Child(entry.Name()).AsInode()}
|
||||
ret = append(ret, dirent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = meta_cache.EnsureVisited(dir.wfs.metaCache, dir.wfs, dirPath); err != nil {
|
||||
@@ -336,7 +364,7 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) {
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
listErr := dir.wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, "", false, int64(math.MaxInt32), func(entry *filer.Entry) bool {
|
||||
processEachEntryFn(entry.ToProtoEntry(), false)
|
||||
processEachEntryFn(entry, false)
|
||||
return true
|
||||
})
|
||||
if listErr != nil {
|
||||
@@ -368,10 +396,6 @@ func findFileType(mode uint16) fuse.DirentType {
|
||||
|
||||
func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
if !req.Dir {
|
||||
return dir.removeOneFile(req)
|
||||
}
|
||||
@@ -382,40 +406,32 @@ func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
|
||||
|
||||
func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error {
|
||||
|
||||
filePath := util.NewFullPath(dir.FullPath(), req.Name)
|
||||
dirFullPath := dir.FullPath()
|
||||
filePath := util.NewFullPath(dirFullPath, req.Name)
|
||||
entry, err := filer_pb.GetEntry(dir.wfs, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// first, ensure the filer store can correctly delete
|
||||
glog.V(3).Infof("remove file: %v", req)
|
||||
isDeleteData := entry.HardLinkCounter <= 1
|
||||
err = filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, isDeleteData, false, false, false, []int32{dir.wfs.signature})
|
||||
isDeleteData := entry != nil && entry.HardLinkCounter <= 1
|
||||
err = filer_pb.Remove(dir.wfs, dirFullPath, req.Name, isDeleteData, false, false, false, []int32{dir.wfs.signature})
|
||||
if err != nil {
|
||||
glog.V(3).Infof("not found remove file %s/%s: %v", dir.FullPath(), req.Name, err)
|
||||
glog.V(3).Infof("not found remove file %s: %v", filePath, err)
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
// then, delete meta cache and fsNode cache
|
||||
dir.wfs.metaCache.DeleteEntry(context.Background(), filePath)
|
||||
|
||||
// clear entry inside the file
|
||||
fsNode := dir.wfs.fsNodeCache.GetFsNode(filePath)
|
||||
dir.wfs.fsNodeCache.DeleteFsNode(filePath)
|
||||
if fsNode != nil {
|
||||
if file, ok := fsNode.(*File); ok {
|
||||
file.clearEntry()
|
||||
}
|
||||
if err = dir.wfs.metaCache.DeleteEntry(context.Background(), filePath); err != nil {
|
||||
glog.V(3).Infof("local DeleteEntry %s: %v", filePath, err)
|
||||
return fuse.ESTALE
|
||||
}
|
||||
|
||||
// remove current file handle if any
|
||||
dir.wfs.handlesLock.Lock()
|
||||
defer dir.wfs.handlesLock.Unlock()
|
||||
inodeId := util.NewFullPath(dir.FullPath(), req.Name).AsInode()
|
||||
inodeId := filePath.AsInode()
|
||||
delete(dir.wfs.handles, inodeId)
|
||||
|
||||
return nil
|
||||
@@ -424,20 +440,20 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error {
|
||||
|
||||
func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error {
|
||||
|
||||
dirFullPath := dir.FullPath()
|
||||
glog.V(3).Infof("remove directory entry: %v", req)
|
||||
ignoreRecursiveErr := true // ignore recursion error since the OS should manage it
|
||||
err := filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, true, false, ignoreRecursiveErr, false, []int32{dir.wfs.signature})
|
||||
err := filer_pb.Remove(dir.wfs, dirFullPath, req.Name, true, true, ignoreRecursiveErr, false, []int32{dir.wfs.signature})
|
||||
if err != nil {
|
||||
glog.V(0).Infof("remove %s/%s: %v", dir.FullPath(), req.Name, err)
|
||||
glog.V(0).Infof("remove %s/%s: %v", dirFullPath, req.Name, err)
|
||||
if strings.Contains(err.Error(), "non-empty") {
|
||||
return fuse.EEXIST
|
||||
}
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
t := util.NewFullPath(dir.FullPath(), req.Name)
|
||||
t := util.NewFullPath(dirFullPath, req.Name)
|
||||
dir.wfs.metaCache.DeleteEntry(context.Background(), t)
|
||||
dir.wfs.fsNodeCache.DeleteFsNode(t)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -445,73 +461,64 @@ func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error {
|
||||
|
||||
func (dir *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("%v dir setattr %+v", dir.FullPath(), req)
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.Valid.Mode() {
|
||||
dir.entry.Attributes.FileMode = uint32(req.Mode)
|
||||
entry.Attributes.FileMode = uint32(req.Mode)
|
||||
}
|
||||
|
||||
if req.Valid.Uid() {
|
||||
dir.entry.Attributes.Uid = req.Uid
|
||||
entry.Attributes.Uid = req.Uid
|
||||
}
|
||||
|
||||
if req.Valid.Gid() {
|
||||
dir.entry.Attributes.Gid = req.Gid
|
||||
entry.Attributes.Gid = req.Gid
|
||||
}
|
||||
|
||||
if req.Valid.Mtime() {
|
||||
dir.entry.Attributes.Mtime = req.Mtime.Unix()
|
||||
entry.Attributes.Mtime = req.Mtime.Unix()
|
||||
}
|
||||
|
||||
return dir.saveEntry()
|
||||
return dir.saveEntry(entry)
|
||||
|
||||
}
|
||||
|
||||
func (dir *Dir) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("dir Setxattr %s: %s", dir.FullPath(), req.Name)
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setxattr(dir.entry, req); err != nil {
|
||||
if err := setxattr(entry, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dir.saveEntry()
|
||||
return dir.saveEntry(entry)
|
||||
|
||||
}
|
||||
|
||||
func (dir *Dir) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("dir Removexattr %s: %s", dir.FullPath(), req.Name)
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := removexattr(dir.entry, req); err != nil {
|
||||
if err := removexattr(entry, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dir.saveEntry()
|
||||
return dir.saveEntry(entry)
|
||||
|
||||
}
|
||||
|
||||
@@ -519,11 +526,12 @@ func (dir *Dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp
|
||||
|
||||
glog.V(4).Infof("dir Listxattr %s", dir.FullPath())
|
||||
|
||||
if err := dir.maybeLoadEntry(); err != nil {
|
||||
entry, err := dir.maybeLoadEntry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := listxattr(dir.entry, req, resp); err != nil {
|
||||
if err := listxattr(entry, req, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -533,34 +541,25 @@ func (dir *Dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp
|
||||
|
||||
func (dir *Dir) Forget() {
|
||||
glog.V(4).Infof("Forget dir %s", dir.FullPath())
|
||||
|
||||
dir.wfs.fsNodeCache.DeleteFsNode(util.FullPath(dir.FullPath()))
|
||||
}
|
||||
|
||||
func (dir *Dir) maybeLoadEntry() error {
|
||||
if dir.entry == nil {
|
||||
parentDirPath, name := util.FullPath(dir.FullPath()).DirAndName()
|
||||
entry, err := dir.wfs.maybeLoadEntry(parentDirPath, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir.entry = entry
|
||||
}
|
||||
return nil
|
||||
func (dir *Dir) maybeLoadEntry() (*filer_pb.Entry, error) {
|
||||
parentDirPath, name := util.FullPath(dir.FullPath()).DirAndName()
|
||||
return dir.wfs.maybeLoadEntry(parentDirPath, name)
|
||||
}
|
||||
|
||||
func (dir *Dir) saveEntry() error {
|
||||
func (dir *Dir) saveEntry(entry *filer_pb.Entry) error {
|
||||
|
||||
parentDir, name := util.FullPath(dir.FullPath()).DirAndName()
|
||||
|
||||
return dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
dir.wfs.mapPbIdFromLocalToFiler(dir.entry)
|
||||
defer dir.wfs.mapPbIdFromFilerToLocal(dir.entry)
|
||||
dir.wfs.mapPbIdFromLocalToFiler(entry)
|
||||
defer dir.wfs.mapPbIdFromFilerToLocal(entry)
|
||||
|
||||
request := &filer_pb.UpdateEntryRequest{
|
||||
Directory: parentDir,
|
||||
Entry: dir.entry,
|
||||
Entry: entry,
|
||||
Signatures: []int32{dir.wfs.signature},
|
||||
}
|
||||
|
||||
@@ -571,7 +570,10 @@ func (dir *Dir) saveEntry() error {
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))
|
||||
if err := dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil {
|
||||
glog.Errorf("UpdateEntry dir %s/%s: %v", parentDir, name, err)
|
||||
return fuse.ESTALE
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -24,10 +24,6 @@ const (
|
||||
|
||||
func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
|
||||
oldFile, ok := old.(*File)
|
||||
if !ok {
|
||||
glog.Errorf("old node is not a file: %+v", old)
|
||||
@@ -35,11 +31,11 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f
|
||||
|
||||
glog.V(4).Infof("Link: %v/%v -> %v/%v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName)
|
||||
|
||||
if _, err := oldFile.maybeLoadEntry(ctx); err != nil {
|
||||
oldEntry, err := oldFile.maybeLoadEntry(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldEntry := oldFile.getEntry()
|
||||
if oldEntry == nil {
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
@@ -72,7 +68,7 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f
|
||||
}
|
||||
|
||||
// apply changes to the filer, and also apply to local metaCache
|
||||
err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
err = dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
dir.wfs.mapPbIdFromLocalToFiler(request.Entry)
|
||||
defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry)
|
||||
@@ -97,11 +93,8 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f
|
||||
}
|
||||
|
||||
// create new file node
|
||||
newNode := dir.newFile(req.NewName, request.Entry)
|
||||
newNode := dir.newFile(req.NewName)
|
||||
newFile := newNode.(*File)
|
||||
if _, err := newFile.maybeLoadEntry(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newFile, err
|
||||
|
||||
@@ -109,10 +102,6 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f
|
||||
|
||||
func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Symlink: %v/%v to %v", dir.FullPath(), req.NewName, req.Target)
|
||||
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
@@ -147,7 +136,7 @@ func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node,
|
||||
return nil
|
||||
})
|
||||
|
||||
symlink := dir.newFile(req.NewName, request.Entry)
|
||||
symlink := dir.newFile(req.NewName)
|
||||
|
||||
return symlink, err
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@ import (
|
||||
|
||||
func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirectory fs.Node) error {
|
||||
|
||||
if dir.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
newDir := newDirectory.(*Dir)
|
||||
|
||||
newPath := util.NewFullPath(newDir.FullPath(), req.NewName)
|
||||
@@ -68,19 +64,28 @@ func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirector
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
// fmt.Printf("rename path: %v => %v\n", oldPath, newPath)
|
||||
dir.wfs.fsNodeCache.Move(oldPath, newPath)
|
||||
oldFsNode := NodeWithId(oldPath.AsInode())
|
||||
newFsNode := NodeWithId(newPath.AsInode())
|
||||
dir.wfs.Server.InvalidateInternalNode(oldFsNode, newFsNode, func(internalNode fs.Node) {
|
||||
if file, ok := internalNode.(*File); ok {
|
||||
glog.V(4).Infof("internal node %s", file.Name)
|
||||
file.Name = req.NewName
|
||||
file.id = uint64(newFsNode)
|
||||
}
|
||||
})
|
||||
|
||||
// change file handle
|
||||
dir.wfs.handlesLock.Lock()
|
||||
defer dir.wfs.handlesLock.Unlock()
|
||||
inodeId := oldPath.AsInode()
|
||||
existingHandle, found := dir.wfs.handles[inodeId]
|
||||
glog.V(4).Infof("has open filehandle %s: %v", oldPath, found)
|
||||
if !found || existingHandle == nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
glog.V(4).Infof("opened filehandle %s => %s", oldPath, newPath)
|
||||
delete(dir.wfs.handles, inodeId)
|
||||
dir.wfs.handles[newPath.AsInode()] = existingHandle
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
+28
-74
@@ -2,10 +2,8 @@ package filesys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/fuse"
|
||||
@@ -20,6 +18,7 @@ import (
|
||||
const blockSize = 512
|
||||
|
||||
var _ = fs.Node(&File{})
|
||||
var _ = fs.NodeIdentifier(&File{})
|
||||
var _ = fs.NodeOpener(&File{})
|
||||
var _ = fs.NodeFsyncer(&File{})
|
||||
var _ = fs.NodeSetattrer(&File{})
|
||||
@@ -30,37 +29,37 @@ var _ = fs.NodeListxattrer(&File{})
|
||||
var _ = fs.NodeForgetter(&File{})
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
dir *Dir
|
||||
wfs *WFS
|
||||
entry *filer_pb.Entry
|
||||
entryLock sync.RWMutex
|
||||
entryViewCache []filer.VisibleInterval
|
||||
isOpen int
|
||||
reader io.ReaderAt
|
||||
dirtyMetadata bool
|
||||
Name string
|
||||
dir *Dir
|
||||
wfs *WFS
|
||||
entry *filer_pb.Entry
|
||||
isOpen int
|
||||
dirtyMetadata bool
|
||||
id uint64
|
||||
}
|
||||
|
||||
func (file *File) fullpath() util.FullPath {
|
||||
return util.NewFullPath(file.dir.FullPath(), file.Name)
|
||||
}
|
||||
|
||||
func (file *File) Id() uint64 {
|
||||
return file.id
|
||||
}
|
||||
|
||||
func (file *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
|
||||
|
||||
glog.V(4).Infof("file Attr %s, open:%v existing:%v", file.fullpath(), file.isOpen, attr)
|
||||
|
||||
entry := file.getEntry()
|
||||
if file.isOpen <= 0 || entry == nil {
|
||||
if entry, err = file.maybeLoadEntry(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
entry, err := file.maybeLoadEntry(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
return fuse.ENOENT
|
||||
}
|
||||
|
||||
// attr.Inode = file.fullpath().AsInode()
|
||||
attr.Inode = file.Id()
|
||||
attr.Valid = time.Second
|
||||
attr.Mode = os.FileMode(entry.Attributes.FileMode)
|
||||
attr.Size = filer.FileSize(entry)
|
||||
@@ -110,10 +109,6 @@ func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.Op
|
||||
|
||||
func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
|
||||
|
||||
if file.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("%v file setattr %+v", file.fullpath(), req)
|
||||
|
||||
entry, err := file.maybeLoadEntry(ctx)
|
||||
@@ -122,7 +117,7 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f
|
||||
}
|
||||
if file.isOpen > 0 {
|
||||
file.wfs.handlesLock.Lock()
|
||||
fileHandle := file.wfs.handles[file.fullpath().AsInode()]
|
||||
fileHandle := file.wfs.handles[file.Id()]
|
||||
file.wfs.handlesLock.Unlock()
|
||||
|
||||
if fileHandle != nil {
|
||||
@@ -154,8 +149,6 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f
|
||||
}
|
||||
}
|
||||
entry.Chunks = chunks
|
||||
file.entryViewCache, _ = filer.NonOverlappingVisibleIntervals(file.wfs.LookupFn(), chunks)
|
||||
file.setReader(nil)
|
||||
}
|
||||
entry.Attributes.FileSize = req.Size
|
||||
file.dirtyMetadata = true
|
||||
@@ -204,10 +197,6 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f
|
||||
|
||||
func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
|
||||
|
||||
if file.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("file Setxattr %s: %s", file.fullpath(), req.Name)
|
||||
|
||||
entry, err := file.maybeLoadEntry(ctx)
|
||||
@@ -225,10 +214,6 @@ func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error
|
||||
|
||||
func (file *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
|
||||
|
||||
if file.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
glog.V(4).Infof("file Removexattr %s: %s", file.fullpath(), req.Name)
|
||||
|
||||
entry, err := file.maybeLoadEntry(ctx)
|
||||
@@ -272,16 +257,20 @@ func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
|
||||
func (file *File) Forget() {
|
||||
t := util.NewFullPath(file.dir.FullPath(), file.Name)
|
||||
glog.V(4).Infof("Forget file %s", t)
|
||||
file.wfs.fsNodeCache.DeleteFsNode(t)
|
||||
file.wfs.ReleaseHandle(t, 0)
|
||||
file.setReader(nil)
|
||||
file.wfs.ReleaseHandle(t, fuse.HandleID(t.AsInode()))
|
||||
}
|
||||
|
||||
func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, err error) {
|
||||
entry = file.getEntry()
|
||||
if file.isOpen > 0 {
|
||||
return entry, nil
|
||||
|
||||
file.wfs.handlesLock.Lock()
|
||||
handle, found := file.wfs.handles[file.Id()]
|
||||
file.wfs.handlesLock.Unlock()
|
||||
entry = file.entry
|
||||
if found {
|
||||
glog.V(4).Infof("maybeLoadEntry found opened file %s/%s: %v %v", file.dir.FullPath(), file.Name, handle.f.entry, entry)
|
||||
entry = handle.f.entry
|
||||
}
|
||||
|
||||
if entry != nil {
|
||||
if len(entry.HardLinkId) == 0 {
|
||||
// only always reload hard link
|
||||
@@ -294,7 +283,7 @@ func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, er
|
||||
return entry, err
|
||||
}
|
||||
if entry != nil {
|
||||
file.setEntry(entry)
|
||||
// file.entry = entry
|
||||
} else {
|
||||
glog.Warningf("maybeLoadEntry not found entry %s/%s: %v", file.dir.FullPath(), file.Name, err)
|
||||
}
|
||||
@@ -336,44 +325,11 @@ func (file *File) addChunks(chunks []*filer_pb.FileChunk) {
|
||||
return lessThan(chunks[i], chunks[j])
|
||||
})
|
||||
|
||||
// add to entry view cache
|
||||
for _, chunk := range chunks {
|
||||
file.entryViewCache = filer.MergeIntoVisibles(file.entryViewCache, chunk)
|
||||
}
|
||||
|
||||
file.setReader(nil)
|
||||
|
||||
glog.V(4).Infof("%s existing %d chunks adds %d more", file.fullpath(), len(entry.Chunks), len(chunks))
|
||||
|
||||
entry.Chunks = append(entry.Chunks, newChunks...)
|
||||
}
|
||||
|
||||
func (file *File) setReader(reader io.ReaderAt) {
|
||||
r := file.reader
|
||||
if r != nil {
|
||||
if closer, ok := r.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
file.reader = reader
|
||||
}
|
||||
|
||||
func (file *File) setEntry(entry *filer_pb.Entry) {
|
||||
file.entryLock.Lock()
|
||||
defer file.entryLock.Unlock()
|
||||
file.entry = entry
|
||||
file.entryViewCache, _ = filer.NonOverlappingVisibleIntervals(file.wfs.LookupFn(), entry.Chunks)
|
||||
file.setReader(nil)
|
||||
}
|
||||
|
||||
func (file *File) clearEntry() {
|
||||
file.entryLock.Lock()
|
||||
defer file.entryLock.Unlock()
|
||||
file.entry = nil
|
||||
file.entryViewCache = nil
|
||||
file.setReader(nil)
|
||||
}
|
||||
|
||||
func (file *File) saveEntry(entry *filer_pb.Entry) error {
|
||||
return file.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
@@ -400,7 +356,5 @@ func (file *File) saveEntry(entry *filer_pb.Entry) error {
|
||||
}
|
||||
|
||||
func (file *File) getEntry() *filer_pb.Entry {
|
||||
file.entryLock.RLock()
|
||||
defer file.entryLock.RUnlock()
|
||||
return file.entry
|
||||
}
|
||||
|
||||
+21
-23
@@ -20,9 +20,11 @@ import (
|
||||
|
||||
type FileHandle struct {
|
||||
// cache file has been written to
|
||||
dirtyPages *ContinuousDirtyPages
|
||||
contentType string
|
||||
handle uint64
|
||||
dirtyPages *ContinuousDirtyPages
|
||||
entryViewCache []filer.VisibleInterval
|
||||
reader io.ReaderAt
|
||||
contentType string
|
||||
handle uint64
|
||||
sync.Mutex
|
||||
|
||||
f *File
|
||||
@@ -125,20 +127,20 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) {
|
||||
}
|
||||
|
||||
var chunkResolveErr error
|
||||
if fh.f.entryViewCache == nil {
|
||||
fh.f.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(fh.f.wfs.LookupFn(), entry.Chunks)
|
||||
if fh.entryViewCache == nil {
|
||||
fh.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(fh.f.wfs.LookupFn(), entry.Chunks)
|
||||
if chunkResolveErr != nil {
|
||||
return 0, fmt.Errorf("fail to resolve chunk manifest: %v", chunkResolveErr)
|
||||
}
|
||||
fh.f.setReader(nil)
|
||||
fh.reader = nil
|
||||
}
|
||||
|
||||
reader := fh.f.reader
|
||||
reader := fh.reader
|
||||
if reader == nil {
|
||||
chunkViews := filer.ViewFromVisibleIntervals(fh.f.entryViewCache, 0, math.MaxInt64)
|
||||
chunkViews := filer.ViewFromVisibleIntervals(fh.entryViewCache, 0, math.MaxInt64)
|
||||
reader = filer.NewChunkReaderAtFromClient(fh.f.wfs.LookupFn(), chunkViews, fh.f.wfs.chunkCache, fileSize)
|
||||
}
|
||||
fh.f.setReader(reader)
|
||||
fh.reader = reader
|
||||
|
||||
totalRead, err := reader.ReadAt(buff, offset)
|
||||
|
||||
@@ -154,10 +156,6 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) {
|
||||
// Write to the file handle
|
||||
func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
|
||||
|
||||
if fh.f.wfs.option.ReadOnly {
|
||||
return fuse.EPERM
|
||||
}
|
||||
|
||||
fh.Lock()
|
||||
defer fh.Unlock()
|
||||
|
||||
@@ -195,27 +193,27 @@ func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *f
|
||||
|
||||
func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
||||
|
||||
glog.V(4).Infof("Release %v fh %d", fh.f.fullpath(), fh.handle)
|
||||
glog.V(4).Infof("Release %v fh %d open=%d", fh.f.fullpath(), fh.handle, fh.f.isOpen)
|
||||
|
||||
fh.Lock()
|
||||
defer fh.Unlock()
|
||||
|
||||
fh.f.entryViewCache = nil
|
||||
fh.f.isOpen--
|
||||
|
||||
if fh.f.isOpen <= 0 {
|
||||
fh.f.entry = nil
|
||||
fh.entryViewCache = nil
|
||||
fh.reader = nil
|
||||
|
||||
fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle))
|
||||
}
|
||||
|
||||
if fh.f.isOpen < 0 {
|
||||
glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0)
|
||||
fh.f.isOpen = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
if fh.f.isOpen == 1 {
|
||||
|
||||
fh.f.isOpen--
|
||||
|
||||
fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle))
|
||||
fh.f.setReader(nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+32
-32
@@ -41,9 +41,7 @@ type Option struct {
|
||||
CacheDir string
|
||||
CacheSizeMB int64
|
||||
DataCenter string
|
||||
EntryCacheTtl time.Duration
|
||||
Umask os.FileMode
|
||||
ReadOnly bool
|
||||
|
||||
MountUid uint32
|
||||
MountGid uint32
|
||||
@@ -105,24 +103,16 @@ func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
}
|
||||
|
||||
wfs.metaCache = meta_cache.NewMetaCache(path.Join(cacheDir, "meta"), util.FullPath(option.FilerMountRootPath), option.UidGidMapper, func(filePath util.FullPath) {
|
||||
fsNode := wfs.fsNodeCache.GetFsNode(filePath)
|
||||
if fsNode != nil {
|
||||
if file, ok := fsNode.(*File); ok {
|
||||
if err := wfs.Server.InvalidateNodeData(file); err != nil {
|
||||
glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err)
|
||||
}
|
||||
file.clearEntry()
|
||||
}
|
||||
|
||||
fsNode := NodeWithId(filePath.AsInode())
|
||||
if err := wfs.Server.InvalidateNodeData(fsNode); err != nil {
|
||||
glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err)
|
||||
}
|
||||
|
||||
dir, name := filePath.DirAndName()
|
||||
parent := wfs.root
|
||||
if dir != "/" {
|
||||
parent = wfs.fsNodeCache.GetFsNode(util.FullPath(dir))
|
||||
}
|
||||
if parent != nil {
|
||||
if err := wfs.Server.InvalidateEntry(parent, name); err != nil {
|
||||
glog.V(4).Infof("InvalidateEntry %s : %v", filePath, err)
|
||||
}
|
||||
parent := NodeWithId(util.FullPath(dir).AsInode())
|
||||
if err := wfs.Server.InvalidateEntry(parent, name); err != nil {
|
||||
glog.V(4).Infof("InvalidateEntry %s : %v", filePath, err)
|
||||
}
|
||||
})
|
||||
startTime := time.Now()
|
||||
@@ -131,8 +121,7 @@ func NewSeaweedFileSystem(option *Option) *WFS {
|
||||
wfs.metaCache.Shutdown()
|
||||
})
|
||||
|
||||
entry, _ := filer_pb.GetEntry(wfs, util.FullPath(wfs.option.FilerMountRootPath))
|
||||
wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs, entry: entry}
|
||||
wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs}
|
||||
wfs.fsNodeCache = newFsCache(wfs.root)
|
||||
|
||||
if wfs.option.ConcurrentWriters > 0 {
|
||||
@@ -151,25 +140,28 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand
|
||||
fullpath := file.fullpath()
|
||||
glog.V(4).Infof("AcquireHandle %s uid=%d gid=%d", fullpath, uid, gid)
|
||||
|
||||
wfs.handlesLock.Lock()
|
||||
defer wfs.handlesLock.Unlock()
|
||||
inodeId := file.Id()
|
||||
|
||||
inodeId := file.fullpath().AsInode()
|
||||
if file.isOpen > 0 {
|
||||
existingHandle, found := wfs.handles[inodeId]
|
||||
if found && existingHandle != nil {
|
||||
file.isOpen++
|
||||
return existingHandle
|
||||
}
|
||||
wfs.handlesLock.Lock()
|
||||
existingHandle, found := wfs.handles[inodeId]
|
||||
wfs.handlesLock.Unlock()
|
||||
if found && existingHandle != nil {
|
||||
existingHandle.f.isOpen++
|
||||
glog.V(4).Infof("Acquired Handle %s open %d", fullpath, existingHandle.f.isOpen)
|
||||
return existingHandle
|
||||
}
|
||||
|
||||
entry, _ := file.maybeLoadEntry(context.Background())
|
||||
file.entry = entry
|
||||
fileHandle = newFileHandle(file, uid, gid)
|
||||
file.maybeLoadEntry(context.Background())
|
||||
file.isOpen++
|
||||
|
||||
wfs.handlesLock.Lock()
|
||||
wfs.handles[inodeId] = fileHandle
|
||||
wfs.handlesLock.Unlock()
|
||||
fileHandle.handle = inodeId
|
||||
|
||||
glog.V(4).Infof("Acquired new Handle %s open %d", fullpath, file.isOpen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,9 +169,9 @@ func (wfs *WFS) ReleaseHandle(fullpath util.FullPath, handleId fuse.HandleID) {
|
||||
wfs.handlesLock.Lock()
|
||||
defer wfs.handlesLock.Unlock()
|
||||
|
||||
glog.V(4).Infof("%s ReleaseHandle id %d current handles length %d", fullpath, handleId, len(wfs.handles))
|
||||
glog.V(4).Infof("ReleaseHandle %s id %d current handles length %d", fullpath, handleId, len(wfs.handles))
|
||||
|
||||
delete(wfs.handles, fullpath.AsInode())
|
||||
delete(wfs.handles, uint64(handleId))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -269,3 +261,11 @@ func (wfs *WFS) LookupFn() wdclient.LookupFileIdFunctionType {
|
||||
return filer.LookupFn(wfs)
|
||||
|
||||
}
|
||||
|
||||
type NodeWithId uint64
|
||||
func (n NodeWithId) Id() uint64 {
|
||||
return uint64(n)
|
||||
}
|
||||
func (n NodeWithId) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -398,7 +398,7 @@ type flushSyncWriter interface {
|
||||
func init() {
|
||||
flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files")
|
||||
flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", true, "log to standard error as well as files")
|
||||
flag.Var(&logging.verbosity, "v", "log level for V logs")
|
||||
flag.Var(&logging.verbosity, "v", "log levels [0|1|2|3|4], default to 0")
|
||||
flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr")
|
||||
flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging")
|
||||
flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace")
|
||||
|
||||
@@ -50,20 +50,25 @@ func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL
|
||||
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||
}
|
||||
|
||||
func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string) {
|
||||
func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string, msg error) {
|
||||
errCode := err.Error()
|
||||
errorResp := ErrorResponse{}
|
||||
errorResp.Error.Type = "Sender"
|
||||
errorResp.Error.Code = &errCode
|
||||
if msg != nil {
|
||||
errMsg := msg.Error()
|
||||
errorResp.Error.Message = &errMsg
|
||||
}
|
||||
glog.Errorf("Response %+v", err)
|
||||
switch errCode {
|
||||
case iam.ErrCodeNoSuchEntityException:
|
||||
msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value)
|
||||
errorResp.Error.Message = &msg
|
||||
writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML)
|
||||
case iam.ErrCodeServiceFailureException:
|
||||
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
||||
default:
|
||||
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -19,27 +20,77 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
|
||||
charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
|
||||
policyDocumentVersion = "2012-10-17"
|
||||
StatementActionAdmin = "*"
|
||||
StatementActionWrite = "Put*"
|
||||
StatementActionRead = "Get*"
|
||||
StatementActionList = "List*"
|
||||
StatementActionTagging = "Tagging*"
|
||||
)
|
||||
|
||||
var (
|
||||
seededRand *rand.Rand = rand.New(
|
||||
rand.NewSource(time.Now().UnixNano()))
|
||||
policyDocuments = map[string]*PolicyDocument{}
|
||||
policyLock = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func MapToStatementAction(action string) string {
|
||||
switch action {
|
||||
case StatementActionAdmin:
|
||||
return s3_constants.ACTION_ADMIN
|
||||
case StatementActionWrite:
|
||||
return s3_constants.ACTION_WRITE
|
||||
case StatementActionRead:
|
||||
return s3_constants.ACTION_READ
|
||||
case StatementActionList:
|
||||
return s3_constants.ACTION_LIST
|
||||
case StatementActionTagging:
|
||||
return s3_constants.ACTION_TAGGING
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func MapToIdentitiesAction(action string) string {
|
||||
switch action {
|
||||
case s3_constants.ACTION_ADMIN:
|
||||
return StatementActionAdmin
|
||||
case s3_constants.ACTION_WRITE:
|
||||
return StatementActionWrite
|
||||
case s3_constants.ACTION_READ:
|
||||
return StatementActionRead
|
||||
case s3_constants.ACTION_LIST:
|
||||
return StatementActionList
|
||||
case s3_constants.ACTION_TAGGING:
|
||||
return StatementActionTagging
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type Statement struct {
|
||||
Effect string `json:"Effect"`
|
||||
Action []string `json:"Action"`
|
||||
Resource []string `json:"Resource"`
|
||||
}
|
||||
|
||||
type Policies struct {
|
||||
Policies map[string]PolicyDocument `json:"policies"`
|
||||
}
|
||||
|
||||
type PolicyDocument struct {
|
||||
Version string `json:"Version"`
|
||||
Statement []*Statement `json:"Statement"`
|
||||
}
|
||||
|
||||
func (p PolicyDocument) String() string {
|
||||
b, _ := json.Marshal(p)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func Hash(s *string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(*s))
|
||||
@@ -63,7 +114,11 @@ func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url
|
||||
|
||||
func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) {
|
||||
status := iam.StatusTypeActive
|
||||
userName := values.Get("UserName")
|
||||
for _, ident := range s3cfg.Identities {
|
||||
if userName != "" && userName != ident.Name {
|
||||
continue
|
||||
}
|
||||
for _, cred := range ident.Credentials {
|
||||
resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
|
||||
&iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
|
||||
@@ -83,7 +138,7 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur
|
||||
func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
|
||||
for i, ident := range s3cfg.Identities {
|
||||
if userName == ident.Name {
|
||||
ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
|
||||
s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
@@ -119,7 +174,16 @@ func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values
|
||||
resp.CreatePolicyResult.Policy.PolicyName = &policyName
|
||||
resp.CreatePolicyResult.Policy.Arn = &arn
|
||||
resp.CreatePolicyResult.Policy.PolicyId = &policyId
|
||||
policyDocuments[policyName] = &policyDocument
|
||||
policies := Policies{}
|
||||
policyLock.Lock()
|
||||
defer policyLock.Unlock()
|
||||
if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
policies.Policies[policyName] = policyDocument
|
||||
if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -144,19 +208,69 @@ func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func MapAction(action string) string {
|
||||
switch action {
|
||||
case "*":
|
||||
return s3_constants.ACTION_ADMIN
|
||||
case "Put*":
|
||||
return s3_constants.ACTION_WRITE
|
||||
case "Get*":
|
||||
return s3_constants.ACTION_READ
|
||||
case "List*":
|
||||
return s3_constants.ACTION_LIST
|
||||
default:
|
||||
return s3_constants.ACTION_TAGGING
|
||||
func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) {
|
||||
userName := values.Get("UserName")
|
||||
policyName := values.Get("PolicyName")
|
||||
for _, ident := range s3cfg.Identities {
|
||||
if userName != ident.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
resp.GetUserPolicyResult.UserName = userName
|
||||
resp.GetUserPolicyResult.PolicyName = policyName
|
||||
if len(ident.Actions) == 0 {
|
||||
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||
}
|
||||
|
||||
policyDocument := PolicyDocument{Version: policyDocumentVersion}
|
||||
statements := make(map[string][]string)
|
||||
for _, action := range ident.Actions {
|
||||
// parse "Read:EXAMPLE-BUCKET"
|
||||
act := strings.Split(action, ":")
|
||||
|
||||
resource := "*"
|
||||
if len(act) == 2 {
|
||||
resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1])
|
||||
}
|
||||
statements[resource] = append(statements[resource],
|
||||
fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])),
|
||||
)
|
||||
}
|
||||
for resource, actions := range statements {
|
||||
isEqAction := false
|
||||
for i, statement := range policyDocument.Statement {
|
||||
if reflect.DeepEqual(statement.Action, actions) {
|
||||
policyDocument.Statement[i].Resource = append(
|
||||
policyDocument.Statement[i].Resource, resource)
|
||||
isEqAction = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isEqAction {
|
||||
continue
|
||||
}
|
||||
policyDocumentStatement := Statement{
|
||||
Effect: "Allow",
|
||||
Action: actions,
|
||||
}
|
||||
policyDocumentStatement.Resource = append(policyDocumentStatement.Resource, resource)
|
||||
policyDocument.Statement = append(policyDocument.Statement, &policyDocumentStatement)
|
||||
}
|
||||
resp.GetUserPolicyResult.PolicyDocument = policyDocument.String()
|
||||
return resp, nil
|
||||
}
|
||||
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||
}
|
||||
|
||||
func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
|
||||
userName := values.Get("UserName")
|
||||
for i, ident := range s3cfg.Identities {
|
||||
if ident.Name == userName {
|
||||
s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||
}
|
||||
|
||||
func GetActions(policy *PolicyDocument) (actions []string) {
|
||||
@@ -178,8 +292,9 @@ func GetActions(policy *PolicyDocument) (actions []string) {
|
||||
glog.Infof("not match action: %s", act)
|
||||
continue
|
||||
}
|
||||
statementAction := MapToStatementAction(act[1])
|
||||
if res[5] == "*" {
|
||||
actions = append(actions, MapAction(act[1]))
|
||||
actions = append(actions, statementAction)
|
||||
continue
|
||||
}
|
||||
// Parse my-bucket/shared/*
|
||||
@@ -188,7 +303,7 @@ func GetActions(policy *PolicyDocument) (actions []string) {
|
||||
glog.Infof("not match bucket: %s", path)
|
||||
continue
|
||||
}
|
||||
actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0]))
|
||||
actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,14 +392,15 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
||||
userName := values.Get("UserName")
|
||||
response, err = iama.GetUser(s3cfg, userName)
|
||||
if err != nil {
|
||||
writeIamErrorResponse(w, err, "user", userName)
|
||||
writeIamErrorResponse(w, err, "user", userName, nil)
|
||||
return
|
||||
}
|
||||
changed = false
|
||||
case "DeleteUser":
|
||||
userName := values.Get("UserName")
|
||||
response, err = iama.DeleteUser(s3cfg, userName)
|
||||
if err != nil {
|
||||
writeIamErrorResponse(w, err, "user", userName)
|
||||
writeIamErrorResponse(w, err, "user", userName, nil)
|
||||
return
|
||||
}
|
||||
case "CreateAccessKey":
|
||||
@@ -305,8 +421,23 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
||||
return
|
||||
}
|
||||
case "GetUserPolicy":
|
||||
response, err = iama.GetUserPolicy(s3cfg, values)
|
||||
if err != nil {
|
||||
writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
|
||||
return
|
||||
}
|
||||
changed = false
|
||||
case "DeleteUserPolicy":
|
||||
if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil {
|
||||
writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
|
||||
}
|
||||
default:
|
||||
writeErrorResponse(w, s3err.ErrNotImplemented, r.URL)
|
||||
errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
errorResponse := ErrorResponse{}
|
||||
errorResponse.Error.Code = &errNotImplemented.Code
|
||||
errorResponse.Error.Message = &errNotImplemented.Description
|
||||
writeResponse(w, errNotImplemented.HTTPStatusCode, encodeResponse(errorResponse), mimeXML)
|
||||
return
|
||||
}
|
||||
if changed {
|
||||
@@ -314,7 +445,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
||||
err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
|
||||
s3cfgLock.Unlock()
|
||||
if err != nil {
|
||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||
writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,16 @@ type PutUserPolicyResponse struct {
|
||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"`
|
||||
}
|
||||
|
||||
type GetUserPolicyResponse struct {
|
||||
CommonResponse
|
||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserPolicyResponse"`
|
||||
GetUserPolicyResult struct {
|
||||
UserName string `xml:"UserName"`
|
||||
PolicyName string `xml:"PolicyName"`
|
||||
PolicyDocument string `xml:"PolicyDocument"`
|
||||
} `xml:"GetUserPolicyResult"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
CommonResponse
|
||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"`
|
||||
|
||||
@@ -4,11 +4,14 @@ package iamapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api"
|
||||
. "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/grpc"
|
||||
@@ -19,6 +22,8 @@ import (
|
||||
type IamS3ApiConfig interface {
|
||||
GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||
PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||
GetPolicies(policies *Policies) (err error)
|
||||
PutPolicies(policies *Policies) (err error)
|
||||
}
|
||||
|
||||
type IamS3ApiConfigure struct {
|
||||
@@ -36,7 +41,7 @@ type IamServerOption struct {
|
||||
|
||||
type IamApiServer struct {
|
||||
s3ApiConfig IamS3ApiConfig
|
||||
filerclient *filer_pb.SeaweedFilerClient
|
||||
iam *s3api.IdentityAccessManagement
|
||||
}
|
||||
|
||||
var s3ApiConfigure IamS3ApiConfig
|
||||
@@ -46,9 +51,10 @@ func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer
|
||||
option: option,
|
||||
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")),
|
||||
}
|
||||
|
||||
s3Option := s3api.S3ApiServerOption{Filer: option.Filer}
|
||||
iamApiServer = &IamApiServer{
|
||||
s3ApiConfig: s3ApiConfigure,
|
||||
iam: s3api.NewIdentityAccessManagement(&s3Option),
|
||||
}
|
||||
|
||||
iamApiServer.registerRouter(router)
|
||||
@@ -62,7 +68,8 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) {
|
||||
// ListBuckets
|
||||
|
||||
// apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST"))
|
||||
apiRouter.Path("/").Methods("POST").HandlerFunc(iama.DoActions)
|
||||
apiRouter.Methods("POST").Path("/").HandlerFunc(iama.iam.Auth(iama.DoActions, ACTION_ADMIN))
|
||||
//
|
||||
// NotFound
|
||||
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
}
|
||||
@@ -102,3 +109,41 @@ func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfigurat
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (iam IamS3ApiConfigure) GetPolicies(policies *Policies) (err error) {
|
||||
var buf bytes.Buffer
|
||||
err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamPoliciesFile, &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
policies.Policies = make(map[string]PolicyDocument)
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(buf.Bytes(), policies); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam IamS3ApiConfigure) PutPolicies(policies *Policies) (err error) {
|
||||
var b []byte
|
||||
if b, err = json.Marshal(policies); err != nil {
|
||||
return err
|
||||
}
|
||||
return pb.WithGrpcFilerClient(
|
||||
iam.option.FilerGrpcAddress,
|
||||
iam.option.GrpcDialOption,
|
||||
func(client filer_pb.SeaweedFilerClient) error {
|
||||
if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamPoliciesFile, b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+42
-18
@@ -7,29 +7,43 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var S3config iam_pb.S3ApiConfiguration
|
||||
var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||
var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||
var GetPolicies func(policies *Policies) (err error)
|
||||
var PutPolicies func(policies *Policies) (err error)
|
||||
|
||||
var s3config = iam_pb.S3ApiConfiguration{}
|
||||
var policiesFile = Policies{Policies: make(map[string]PolicyDocument)}
|
||||
var ias = IamApiServer{s3ApiConfig: iamS3ApiConfigureMock{}}
|
||||
|
||||
type iamS3ApiConfigureMock struct{}
|
||||
|
||||
func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||
s3cfg = &S3config
|
||||
_ = copier.Copy(&s3cfg.Identities, &s3config.Identities)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||
S3config = *s3cfg
|
||||
_ = copier.Copy(&s3config.Identities, &s3cfg.Identities)
|
||||
return nil
|
||||
}
|
||||
|
||||
var a = IamApiServer{}
|
||||
func (iam iamS3ApiConfigureMock) GetPolicies(policies *Policies) (err error) {
|
||||
_ = copier.Copy(&policies, &policiesFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam iamS3ApiConfigureMock) PutPolicies(policies *Policies) (err error) {
|
||||
_ = copier.Copy(&policiesFile, &policies)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
userName := aws.String("Test")
|
||||
@@ -64,17 +78,6 @@ func TestListAccessKeys(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
userName := aws.String("Test")
|
||||
params := &iam.DeleteUserInput{UserName: userName}
|
||||
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
||||
_ = req.Build()
|
||||
out := DeleteUserResponse{}
|
||||
response, err := executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusNotFound, response.Code)
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
userName := aws.String("Test")
|
||||
params := &iam.GetUserInput{UserName: userName}
|
||||
@@ -83,7 +86,7 @@ func TestGetUser(t *testing.T) {
|
||||
out := GetUserResponse{}
|
||||
response, err := executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusNotFound, response.Code)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
// Todo flat statement
|
||||
@@ -147,11 +150,32 @@ func TestPutUserPolicy(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestGetUserPolicy(t *testing.T) {
|
||||
userName := aws.String("Test")
|
||||
params := &iam.GetUserPolicyInput{UserName: userName, PolicyName: aws.String("S3-read-only-example-bucket")}
|
||||
req, _ := iam.New(session.New()).GetUserPolicyRequest(params)
|
||||
_ = req.Build()
|
||||
out := GetUserPolicyResponse{}
|
||||
response, err := executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
userName := aws.String("Test")
|
||||
params := &iam.DeleteUserInput{UserName: userName}
|
||||
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
||||
_ = req.Build()
|
||||
out := DeleteUserResponse{}
|
||||
response, err := executeRequest(req.HTTPRequest, out)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorder, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
apiRouter := mux.NewRouter().SkipClean(true)
|
||||
a.s3ApiConfig = iamS3ApiConfigureMock{}
|
||||
apiRouter.Path("/").Methods("POST").HandlerFunc(a.DoActions)
|
||||
apiRouter.Path("/").Methods("POST").HandlerFunc(ias.DoActions)
|
||||
apiRouter.ServeHTTP(rr, req)
|
||||
return rr, xml.Unmarshal(rr.Body.Bytes(), &v)
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func Assign(masterFn GetMasterFn, grpcDialOption grpc.DialOption, primaryRequest
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return ret, lastError
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -40,7 +39,7 @@ func (uploadResult *UploadResult) ToPbFileChunk(fileId string, offset int64) *fi
|
||||
Offset: offset,
|
||||
Size: uint64(uploadResult.Size),
|
||||
Mtime: time.Now().UnixNano(),
|
||||
ETag: uploadResult.ETag,
|
||||
ETag: uploadResult.ContentMd5,
|
||||
CipherKey: uploadResult.CipherKey,
|
||||
IsCompressed: uploadResult.Gzip > 0,
|
||||
Fid: fid,
|
||||
@@ -235,10 +234,12 @@ func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error
|
||||
// print("+")
|
||||
resp, post_err := HttpClient.Do(req)
|
||||
if post_err != nil {
|
||||
if !strings.Contains(post_err.Error(), "connection reset by peer") {
|
||||
glog.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err)
|
||||
debug.PrintStack()
|
||||
if strings.Contains(post_err.Error(), "connection reset by peer") ||
|
||||
strings.Contains(post_err.Error(), "use of closed network connection") {
|
||||
resp, post_err = HttpClient.Do(req)
|
||||
}
|
||||
}
|
||||
if post_err != nil {
|
||||
return nil, fmt.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err)
|
||||
}
|
||||
// print("-")
|
||||
|
||||
@@ -288,6 +288,7 @@ message LeaseAdminTokenRequest {
|
||||
int64 previous_token = 1;
|
||||
int64 previous_lock_time = 2;
|
||||
string lock_name = 3;
|
||||
string client_name = 4;
|
||||
}
|
||||
message LeaseAdminTokenResponse {
|
||||
int64 token = 1;
|
||||
|
||||
+108
-98
@@ -2468,6 +2468,7 @@ type LeaseAdminTokenRequest struct {
|
||||
PreviousToken int64 `protobuf:"varint,1,opt,name=previous_token,json=previousToken,proto3" json:"previous_token,omitempty"`
|
||||
PreviousLockTime int64 `protobuf:"varint,2,opt,name=previous_lock_time,json=previousLockTime,proto3" json:"previous_lock_time,omitempty"`
|
||||
LockName string `protobuf:"bytes,3,opt,name=lock_name,json=lockName,proto3" json:"lock_name,omitempty"`
|
||||
ClientName string `protobuf:"bytes,4,opt,name=client_name,json=clientName,proto3" json:"client_name,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LeaseAdminTokenRequest) Reset() {
|
||||
@@ -2523,6 +2524,13 @@ func (x *LeaseAdminTokenRequest) GetLockName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LeaseAdminTokenRequest) GetClientName() string {
|
||||
if x != nil {
|
||||
return x.ClientName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type LeaseAdminTokenResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -3268,7 +3276,7 @@ var file_master_proto_rawDesc = []byte{
|
||||
0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
|
||||
0x0d, 0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x8a,
|
||||
0x0d, 0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0xab,
|
||||
0x01, 0x0a, 0x16, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65,
|
||||
0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
@@ -3277,103 +3285,105 @@ var file_master_proto_rawDesc = []byte{
|
||||
0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72,
|
||||
0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b,
|
||||
0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17, 0x4c,
|
||||
0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x0a,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x52,
|
||||
0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69,
|
||||
0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c,
|
||||
0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76,
|
||||
0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x6c,
|
||||
0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xca, 0x09, 0x0a, 0x07, 0x53, 0x65, 0x61, 0x77, 0x65,
|
||||
0x65, 0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62,
|
||||
0x65, 0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e,
|
||||
0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a,
|
||||
0x0d, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x1f,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x43,
|
||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75,
|
||||
0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01,
|
||||
0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65,
|
||||
0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f,
|
||||
0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f,
|
||||
0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, 0x2e,
|
||||
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69,
|
||||
0x63, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61,
|
||||
0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e,
|
||||
0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70,
|
||||
0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f, 0x6c,
|
||||
0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70,
|
||||
0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
|
||||
0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c,
|
||||
0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56,
|
||||
0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
||||
0x51, 0x0a, 0x0c, 0x56, 0x61, 0x63, 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12,
|
||||
0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, 0x75,
|
||||
0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, 0x75,
|
||||
0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x6f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
|
||||
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x60, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69,
|
||||
0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41,
|
||||
0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x63, 0x68, 0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f, 0x73, 0x65, 0x61, 0x77,
|
||||
0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17,
|
||||
0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a,
|
||||
0x0a, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18,
|
||||
0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76,
|
||||
0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
|
||||
0x2c, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65,
|
||||
0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a,
|
||||
0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65,
|
||||
0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xca, 0x09, 0x0a, 0x07, 0x53, 0x65, 0x61, 0x77,
|
||||
0x65, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74,
|
||||
0x62, 0x65, 0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51,
|
||||
0x0a, 0x0d, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12,
|
||||
0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70,
|
||||
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c,
|
||||
0x75, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30,
|
||||
0x01, 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d,
|
||||
0x65, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f,
|
||||
0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f,
|
||||
0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74,
|
||||
0x69, 0x63, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e,
|
||||
0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74,
|
||||
0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
|
||||
0x22, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e,
|
||||
0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f,
|
||||
0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75,
|
||||
0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f,
|
||||
0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63,
|
||||
0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x51, 0x0a, 0x0c, 0x56, 0x61, 0x63, 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65,
|
||||
0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63,
|
||||
0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63,
|
||||
0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e,
|
||||
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41,
|
||||
0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
|
||||
0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f, 0x73, 0x65, 0x61,
|
||||
0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,14 +3,14 @@ package s3api
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||
xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Action string
|
||||
@@ -255,11 +255,21 @@ func (identity *Identity) canDo(action Action, bucket string) bool {
|
||||
limitedByBucket := string(action) + ":" + bucket
|
||||
adminLimitedByBucket := s3_constants.ACTION_ADMIN + ":" + bucket
|
||||
for _, a := range identity.Actions {
|
||||
if string(a) == limitedByBucket {
|
||||
return true
|
||||
}
|
||||
if string(a) == adminLimitedByBucket {
|
||||
return true
|
||||
act := string(a)
|
||||
if strings.HasSuffix(act, "*") {
|
||||
if strings.HasPrefix(limitedByBucket, act[:len(act)-1]) {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(adminLimitedByBucket, act[:len(act)-1]) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if act == limitedByBucket {
|
||||
return true
|
||||
}
|
||||
if act == adminLimitedByBucket {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@@ -132,6 +133,17 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r
|
||||
// Query string.
|
||||
queryStr := req.URL.Query().Encode()
|
||||
|
||||
// Get hashed Payload
|
||||
if signV4Values.Credential.scope.service != "s3" && hashedPayload == emptySHA256 && r.Body != nil {
|
||||
buf, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||
b, _ := ioutil.ReadAll(bytes.NewBuffer(buf))
|
||||
if len(b) != 0 {
|
||||
bodyHash := sha256.Sum256(b)
|
||||
hashedPayload = hex.EncodeToString(bodyHash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// Get canonical request.
|
||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method)
|
||||
|
||||
@@ -139,7 +151,10 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r
|
||||
stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
|
||||
|
||||
// Get hmac signing key.
|
||||
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, signV4Values.Credential.scope.region)
|
||||
signingKey := getSigningKey(cred.SecretKey,
|
||||
signV4Values.Credential.scope.date,
|
||||
signV4Values.Credential.scope.region,
|
||||
signV4Values.Credential.scope.service)
|
||||
|
||||
// Calculate signature.
|
||||
newSignature := getSignature(signingKey, stringToSign)
|
||||
@@ -310,7 +325,7 @@ func (iam *IdentityAccessManagement) doesPolicySignatureV4Match(formValues http.
|
||||
}
|
||||
|
||||
// Get signing key.
|
||||
signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region)
|
||||
signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region, credHeader.scope.service)
|
||||
|
||||
// Get signature.
|
||||
newSignature := getSignature(signingKey, formValues.Get("Policy"))
|
||||
@@ -427,7 +442,10 @@ func (iam *IdentityAccessManagement) doesPresignedSignatureMatch(hashedPayload s
|
||||
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
|
||||
|
||||
// Get hmac presigned signing key.
|
||||
presignedSigningKey := getSigningKey(cred.SecretKey, pSignValues.Credential.scope.date, pSignValues.Credential.scope.region)
|
||||
presignedSigningKey := getSigningKey(cred.SecretKey,
|
||||
pSignValues.Credential.scope.date,
|
||||
pSignValues.Credential.scope.region,
|
||||
pSignValues.Credential.scope.service)
|
||||
|
||||
// Get new signature.
|
||||
newSignature := getSignature(presignedSigningKey, presignedStringToSign)
|
||||
@@ -655,11 +673,11 @@ func sumHMAC(key []byte, data []byte) []byte {
|
||||
}
|
||||
|
||||
// getSigningKey hmac seed to calculate final signature.
|
||||
func getSigningKey(secretKey string, t time.Time, region string) []byte {
|
||||
func getSigningKey(secretKey string, t time.Time, region string, service string) []byte {
|
||||
date := sumHMAC([]byte("AWS4"+secretKey), []byte(t.Format(yyyymmdd)))
|
||||
regionBytes := sumHMAC(date, []byte(region))
|
||||
service := sumHMAC(regionBytes, []byte("s3"))
|
||||
signingKey := sumHMAC(service, []byte("aws4_request"))
|
||||
serviceBytes := sumHMAC(regionBytes, []byte(service))
|
||||
signingKey := sumHMAC(serviceBytes, []byte("aws4_request"))
|
||||
return signingKey
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ func preSignV4(req *http.Request, accessKeyID, secretAccessKey string, expires i
|
||||
queryStr := strings.Replace(query.Encode(), "+", "%20", -1)
|
||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method)
|
||||
stringToSign := getStringToSign(canonicalRequest, date, scope)
|
||||
signingKey := getSigningKey(secretAccessKey, date, region)
|
||||
signingKey := getSigningKey(secretAccessKey, date, region, "s3")
|
||||
signature := getSignature(signingKey, stringToSign)
|
||||
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
@@ -45,7 +45,7 @@ func getChunkSignature(secretKey string, seedSignature string, region string, da
|
||||
hashedChunk
|
||||
|
||||
// Get hmac signing key.
|
||||
signingKey := getSigningKey(secretKey, date, region)
|
||||
signingKey := getSigningKey(secretKey, date, region, "s3")
|
||||
|
||||
// Calculate signature.
|
||||
newSignature := getSignature(signingKey, stringToSign)
|
||||
@@ -117,7 +117,7 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
|
||||
stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope())
|
||||
|
||||
// Get hmac signing key.
|
||||
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region)
|
||||
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region, "s3")
|
||||
|
||||
// Calculate signature.
|
||||
newSignature := getSignature(signingKey, stringToSign)
|
||||
|
||||
@@ -311,7 +311,7 @@ func (s3a *S3ApiServer) proxyToFiler(w http.ResponseWriter, r *http.Request, des
|
||||
}
|
||||
defer util.CloseResponse(resp)
|
||||
|
||||
if resp.ContentLength == -1 || resp.StatusCode == 404 {
|
||||
if (resp.ContentLength == -1 || resp.StatusCode == 404) && resp.StatusCode != 304 {
|
||||
if r.Method != "DELETE" {
|
||||
writeErrorResponse(w, s3err.ErrNoSuchKey, r.URL)
|
||||
return
|
||||
|
||||
@@ -63,7 +63,7 @@ func (fs *FilerServer) ListEntries(req *filer_pb.ListEntriesRequest, stream file
|
||||
var listErr error
|
||||
for limit > 0 {
|
||||
var hasEntries bool
|
||||
lastFileName, listErr = fs.filer.StreamListDirectoryEntries(stream.Context(), util.FullPath(req.Directory), lastFileName, includeLastFile, int64(paginationLimit), req.Prefix, "", func(entry *filer.Entry) bool {
|
||||
lastFileName, listErr = fs.filer.StreamListDirectoryEntries(stream.Context(), util.FullPath(req.Directory), lastFileName, includeLastFile, int64(paginationLimit), req.Prefix, "", "", func(entry *filer.Entry) bool {
|
||||
hasEntries = true
|
||||
if err = stream.Send(&filer_pb.ListEntriesResponse{
|
||||
Entry: &filer_pb.Entry{
|
||||
|
||||
@@ -74,7 +74,7 @@ func (fs *FilerServer) moveFolderSubEntries(ctx context.Context, oldParent util.
|
||||
includeLastFile := false
|
||||
for {
|
||||
|
||||
entries, hasMore, err := fs.filer.ListDirectoryEntries(ctx, currentDirPath, lastFileName, includeLastFile, 1024, "", "")
|
||||
entries, hasMore, err := fs.filer.ListDirectoryEntries(ctx, currentDirPath, lastFileName, includeLastFile, 1024, "", "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request,
|
||||
w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat))
|
||||
if r.Header.Get("If-Modified-Since") != "" {
|
||||
if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
|
||||
if t.After(entry.Attr.Mtime) {
|
||||
if !t.Before(entry.Attr.Mtime) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@ func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
lastFileName := r.FormValue("lastFileName")
|
||||
namePattern := r.FormValue("namePattern")
|
||||
namePatternExclude := r.FormValue("namePatternExclude")
|
||||
|
||||
entries, shouldDisplayLoadMore, err := fs.filer.ListDirectoryEntries(context.Background(), util.FullPath(path), lastFileName, false, int64(limit), "", namePattern)
|
||||
entries, shouldDisplayLoadMore, err := fs.filer.ListDirectoryEntries(context.Background(), util.FullPath(path), lastFileName, false, int64(limit), "", namePattern, namePatternExclude)
|
||||
|
||||
if err != nil {
|
||||
glog.V(0).Infof("listDirectory %s %s %d: %s", path, lastFileName, limit, err)
|
||||
|
||||
@@ -142,6 +142,14 @@ func (fs *FilerServer) saveMetaData(ctx context.Context, r *http.Request, fileNa
|
||||
if fileName != "" {
|
||||
path += fileName
|
||||
}
|
||||
} else {
|
||||
if fileName != "" {
|
||||
if possibleDirEntry, findDirErr := fs.filer.FindEntry(ctx, util.FullPath(path)); findDirErr == nil {
|
||||
if possibleDirEntry.IsDirectory() {
|
||||
path += "/" + fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entry *filer.Entry
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"hash"
|
||||
"io"
|
||||
@@ -71,6 +72,11 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque
|
||||
if uploadResult.Size == 0 {
|
||||
break
|
||||
}
|
||||
uploadedMd5 := util.Base64Md5ToBytes(uploadResult.ContentMd5)
|
||||
readedMd5 := md5Hash.Sum(nil)
|
||||
if !bytes.Equal(uploadedMd5, readedMd5) {
|
||||
glog.Errorf("md5 %x does not match %x uploaded chunk %s to the volume server", readedMd5, uploadedMd5, uploadResult.Name)
|
||||
}
|
||||
|
||||
// Save to chunk manifest structure
|
||||
fileChunks = append(fileChunks, uploadResult.ToPbFileChunk(fileId, chunkOffset))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package master_ui
|
||||
package filer_ui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package master_ui
|
||||
package filer_ui
|
||||
|
||||
import (
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
@@ -3,6 +3,7 @@ package weed_server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -60,6 +61,7 @@ const (
|
||||
type AdminLock struct {
|
||||
accessSecret int64
|
||||
accessLockTime time.Time
|
||||
lastClient string
|
||||
}
|
||||
|
||||
type AdminLocks struct {
|
||||
@@ -73,14 +75,15 @@ func NewAdminLocks() *AdminLocks {
|
||||
}
|
||||
}
|
||||
|
||||
func (locks *AdminLocks) isLocked(lockName string) bool {
|
||||
func (locks *AdminLocks) isLocked(lockName string) (clientName string, isLocked bool) {
|
||||
locks.RLock()
|
||||
defer locks.RUnlock()
|
||||
adminLock, found := locks.locks[lockName]
|
||||
if !found {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
return adminLock.accessLockTime.Add(LockDuration).After(time.Now())
|
||||
glog.V(4).Infof("isLocked %v", adminLock.lastClient)
|
||||
return adminLock.lastClient, adminLock.accessLockTime.Add(LockDuration).After(time.Now())
|
||||
}
|
||||
|
||||
func (locks *AdminLocks) isValidToken(lockName string, ts time.Time, token int64) bool {
|
||||
@@ -93,12 +96,13 @@ func (locks *AdminLocks) isValidToken(lockName string, ts time.Time, token int64
|
||||
return adminLock.accessLockTime.Equal(ts) && adminLock.accessSecret == token
|
||||
}
|
||||
|
||||
func (locks *AdminLocks) generateToken(lockName string) (ts time.Time, token int64) {
|
||||
func (locks *AdminLocks) generateToken(lockName string, clientName string) (ts time.Time, token int64) {
|
||||
locks.Lock()
|
||||
defer locks.Unlock()
|
||||
lock := &AdminLock{
|
||||
accessSecret: rand.Int63(),
|
||||
accessLockTime: time.Now(),
|
||||
lastClient: clientName,
|
||||
}
|
||||
locks.locks[lockName] = lock
|
||||
return lock.accessLockTime, lock.accessSecret
|
||||
@@ -113,18 +117,19 @@ func (locks *AdminLocks) deleteLock(lockName string) {
|
||||
func (ms *MasterServer) LeaseAdminToken(ctx context.Context, req *master_pb.LeaseAdminTokenRequest) (*master_pb.LeaseAdminTokenResponse, error) {
|
||||
resp := &master_pb.LeaseAdminTokenResponse{}
|
||||
|
||||
if ms.adminLocks.isLocked(req.LockName) {
|
||||
if lastClient, isLocked := ms.adminLocks.isLocked(req.LockName); isLocked {
|
||||
glog.V(4).Infof("LeaseAdminToken %v", lastClient)
|
||||
if req.PreviousToken != 0 && ms.adminLocks.isValidToken(req.LockName, time.Unix(0, req.PreviousLockTime), req.PreviousToken) {
|
||||
// for renew
|
||||
ts, token := ms.adminLocks.generateToken(req.LockName)
|
||||
ts, token := ms.adminLocks.generateToken(req.LockName, req.ClientName)
|
||||
resp.Token, resp.LockTsNs = token, ts.UnixNano()
|
||||
return resp, nil
|
||||
}
|
||||
// refuse since still locked
|
||||
return resp, fmt.Errorf("already locked")
|
||||
return resp, fmt.Errorf("already locked by " + lastClient)
|
||||
}
|
||||
// for fresh lease request
|
||||
ts, token := ms.adminLocks.generateToken(req.LockName)
|
||||
ts, token := ms.adminLocks.generateToken(req.LockName, req.ClientName)
|
||||
resp.Token, resp.LockTsNs = token, ts.UnixNano()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -14,17 +14,19 @@ func (ms *MasterServer) uiStatusHandler(w http.ResponseWriter, r *http.Request)
|
||||
infos := make(map[string]interface{})
|
||||
infos["Up Time"] = time.Now().Sub(startTime).String()
|
||||
args := struct {
|
||||
Version string
|
||||
Topology interface{}
|
||||
RaftServer raft.Server
|
||||
Stats map[string]interface{}
|
||||
Counters *stats.ServerStats
|
||||
Version string
|
||||
Topology interface{}
|
||||
RaftServer raft.Server
|
||||
Stats map[string]interface{}
|
||||
Counters *stats.ServerStats
|
||||
VolumeSizeLimitMB uint
|
||||
}{
|
||||
util.Version(),
|
||||
ms.Topo.ToMap(),
|
||||
ms.Topo.RaftServer,
|
||||
infos,
|
||||
serverStats,
|
||||
ms.option.VolumeSizeLimitMB,
|
||||
}
|
||||
ui.StatusTpl.Execute(w, args)
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@ var StatusTpl = template.Must(template.New("status").Parse(`<!DOCTYPE html>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h2>Cluster status</h2>
|
||||
<table class="table">
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Volume Size Limit</th>
|
||||
<td>{{ .VolumeSizeLimitMB }}MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Free</th>
|
||||
<td>{{ .Topology.Free }}</td>
|
||||
@@ -38,8 +42,8 @@ var StatusTpl = template.Must(template.New("status").Parse(`<!DOCTYPE html>
|
||||
<td><a href="http://{{ .Leader }}">{{ .Leader }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-sm-2 field-label"><label>Other Masters:</label></td>
|
||||
<td class="col-sm-10"><ul class="list-unstyled">
|
||||
<th>Other Masters</th>
|
||||
<td class="col-sm-5"><ul class="list-unstyled">
|
||||
{{ range $k, $p := .Peers }}
|
||||
<li><a href="http://{{ $p.Name }}/ui/index.html">{{ $p.Name }}</a></li>
|
||||
{{ end }}
|
||||
|
||||
@@ -43,7 +43,7 @@ type VolumeServer struct {
|
||||
|
||||
func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
|
||||
port int, publicUrl string,
|
||||
folders []string, maxCounts []int, minFreeSpacePercents []float32, diskTypes []types.DiskType,
|
||||
folders []string, maxCounts []int, minFreeSpaces []util.MinFreeSpace, diskTypes []types.DiskType,
|
||||
idxFolder string,
|
||||
needleMapKind storage.NeedleMapKind,
|
||||
masterNodes []string, pulseSeconds int,
|
||||
@@ -85,7 +85,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
|
||||
|
||||
vs.checkWithMaster()
|
||||
|
||||
vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpacePercents, idxFolder, vs.needleMapKind, diskTypes)
|
||||
vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpaces, idxFolder, vs.needleMapKind, diskTypes)
|
||||
vs.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec)
|
||||
|
||||
handleStaticResources(adminMux)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/stats"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
"github.com/chrislusf/seaweedfs/weed/topology"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -68,7 +67,7 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ret.Name = string(reqNeedle.Name)
|
||||
}
|
||||
ret.Size = uint32(originalSize)
|
||||
ret.ETag = fmt.Sprintf("%x", util.Base64Md5ToBytes(contentMd5))
|
||||
ret.ETag = reqNeedle.Etag()
|
||||
ret.Mime = string(reqNeedle.Mime)
|
||||
setEtag(w, ret.ETag)
|
||||
w.Header().Set("Content-MD5", contentMd5)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package master_ui
|
||||
package volume_server_ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -26,7 +27,7 @@ func (c *commandLock) Help() string {
|
||||
|
||||
func (c *commandLock) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
|
||||
|
||||
commandEnv.locker.RequestLock()
|
||||
commandEnv.locker.RequestLock(util.DetectedHostAddress())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
@@ -72,6 +73,7 @@ func (c *commandFsMetaLoad) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
return err
|
||||
}
|
||||
|
||||
fullEntry.Entry.Name = strings.ReplaceAll(fullEntry.Entry.Name, "/", "x")
|
||||
if err := filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{
|
||||
Directory: fullEntry.Dir,
|
||||
Entry: fullEntry.Entry,
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -46,6 +48,7 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
fsMetaSaveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
verbose := fsMetaSaveCommand.Bool("v", false, "print out each processed files")
|
||||
outputFileName := fsMetaSaveCommand.String("o", "", "output the meta data to this file")
|
||||
isObfuscate := fsMetaSaveCommand.Bool("obfuscate", false, "obfuscate the file names")
|
||||
// chunksFileName := fsMetaSaveCommand.String("chunks", "", "output all the chunks to this file")
|
||||
if err = fsMetaSaveCommand.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -69,6 +72,11 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
var cipherKey util.CipherKey
|
||||
if *isObfuscate {
|
||||
cipherKey = util.GenCipherKey()
|
||||
}
|
||||
|
||||
err = doTraverseBfsAndSaving(commandEnv, writer, path, *verbose, func(outputChan chan interface{}) {
|
||||
sizeBuf := make([]byte, 4)
|
||||
for item := range outputChan {
|
||||
@@ -78,6 +86,13 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io.
|
||||
dst.Write(b)
|
||||
}
|
||||
}, func(entry *filer_pb.FullEntry, outputChan chan interface{}) (err error) {
|
||||
if !entry.Entry.IsDirectory {
|
||||
ext := filepath.Ext(entry.Entry.Name)
|
||||
if encrypted, encErr := util.Encrypt([]byte(entry.Entry.Name), cipherKey); encErr == nil {
|
||||
entry.Entry.Name = util.Base64Encode(encrypted)[:len(entry.Entry.Name)] + ext
|
||||
entry.Entry.Name = strings.ReplaceAll(entry.Entry.Name, "/", "x")
|
||||
}
|
||||
}
|
||||
bytes, err := proto.Marshal(entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, "marshall error: %v\n", err)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (c *commandS3CleanUploads) Help() string {
|
||||
return `clean up stale multipart uploads
|
||||
|
||||
Example:
|
||||
s3.clean.uploads -replication 001
|
||||
s3.clean.uploads -timeAgo 1.5h
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ func (c *commandVolumeCheckDisk) Do(args []string, commandEnv *CommandEnv, write
|
||||
}
|
||||
if a.info.ReadOnly || b.info.ReadOnly {
|
||||
fmt.Fprintf(writer, "skipping readonly volume %d on %s and %s\n", a.info.Id, a.location.dataNode.Id, b.location.dataNode.Id)
|
||||
replicas = replicas[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,11 @@ func moveAwayOneEcVolume(commandEnv *CommandEnv, ecShardInfo *master_pb.VolumeEc
|
||||
|
||||
func moveAwayOneNormalVolume(commandEnv *CommandEnv, volumeReplicas map[uint32][]*VolumeReplica, vol *master_pb.VolumeInformationMessage, thisNode *Node, otherNodes []*Node, applyChange bool) (hasMoved bool, err error) {
|
||||
fn := capacityByFreeVolumeCount(types.ToDiskType(vol.DiskType))
|
||||
for _, n := range otherNodes {
|
||||
n.selectVolumes(func(v *master_pb.VolumeInformationMessage) bool {
|
||||
return v.DiskType == vol.DiskType
|
||||
})
|
||||
}
|
||||
sort.Slice(otherNodes, func(i, j int) bool {
|
||||
return otherNodes[i].localVolumeRatio(fn) > otherNodes[j].localVolumeRatio(fn)
|
||||
})
|
||||
|
||||
@@ -133,7 +133,7 @@ func doVolumeTierMove(commandEnv *CommandEnv, writer io.Writer, collection strin
|
||||
|
||||
// remove the remaining replicas
|
||||
for _, loc := range locations {
|
||||
if loc.Url != sourceVolumeServer {
|
||||
if loc.Url != dst.dataNode.Id {
|
||||
if err = deleteVolume(commandEnv.option.GrpcDialOption, vid, loc.Url); err != nil {
|
||||
fmt.Fprintf(writer, "failed to delete volume %d on %s\n", vid, loc.Url)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/grace"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@@ -25,6 +26,9 @@ func RunShell(options ShellOptions) {
|
||||
|
||||
line = liner.NewLiner()
|
||||
defer line.Close()
|
||||
grace.OnInterrupt(func() {
|
||||
line.Close()
|
||||
})
|
||||
|
||||
line.SetCtrlCAborts(true)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ type DiskLocation struct {
|
||||
DiskType types.DiskType
|
||||
MaxVolumeCount int
|
||||
OriginalMaxVolumeCount int
|
||||
MinFreeSpacePercent float32
|
||||
MinFreeSpace util.MinFreeSpace
|
||||
volumes map[needle.VolumeId]*Volume
|
||||
volumesLock sync.RWMutex
|
||||
|
||||
@@ -34,7 +34,7 @@ type DiskLocation struct {
|
||||
isDiskSpaceLow bool
|
||||
}
|
||||
|
||||
func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32, idxDir string, diskType types.DiskType) *DiskLocation {
|
||||
func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpace util.MinFreeSpace, idxDir string, diskType types.DiskType) *DiskLocation {
|
||||
dir = util.ResolvePath(dir)
|
||||
if idxDir == "" {
|
||||
idxDir = dir
|
||||
@@ -47,7 +47,7 @@ func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32
|
||||
DiskType: diskType,
|
||||
MaxVolumeCount: maxVolumeCount,
|
||||
OriginalMaxVolumeCount: maxVolumeCount,
|
||||
MinFreeSpacePercent: minFreeSpacePercent,
|
||||
MinFreeSpace: minFreeSpace,
|
||||
}
|
||||
location.volumes = make(map[needle.VolumeId]*Volume)
|
||||
location.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume)
|
||||
@@ -131,7 +131,7 @@ func (l *DiskLocation) loadExistingVolume(fileInfo os.FileInfo, needleMapKind Ne
|
||||
l.SetVolume(vid, v)
|
||||
|
||||
size, _, _ := v.FileStat()
|
||||
glog.V(0).Infof("data file %s, replicaPlacement=%s v=%d size=%d ttl=%s",
|
||||
glog.V(0).Infof("data file %s, replication=%s v=%d size=%d ttl=%s",
|
||||
l.Directory+"/"+volumeName+".dat", v.ReplicaPlacement, v.Version(), size, v.Ttl.String())
|
||||
return true
|
||||
}
|
||||
@@ -361,14 +361,18 @@ func (l *DiskLocation) CheckDiskSpace() {
|
||||
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "all").Set(float64(s.All))
|
||||
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "used").Set(float64(s.Used))
|
||||
stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "free").Set(float64(s.Free))
|
||||
if (s.PercentFree < l.MinFreeSpacePercent) != l.isDiskSpaceLow {
|
||||
|
||||
isLow, desc := l.MinFreeSpace.IsLow(s.Free, s.PercentFree)
|
||||
if isLow != l.isDiskSpaceLow {
|
||||
l.isDiskSpaceLow = !l.isDiskSpaceLow
|
||||
}
|
||||
|
||||
logLevel := glog.Level(4)
|
||||
if l.isDiskSpaceLow {
|
||||
glog.V(0).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow)
|
||||
} else {
|
||||
glog.V(4).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow)
|
||||
logLevel = glog.Level(0)
|
||||
}
|
||||
|
||||
glog.V(logLevel).Infof("dir %s %s", dir, desc)
|
||||
}
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -52,11 +53,12 @@ func (s *Store) String() (str string) {
|
||||
return
|
||||
}
|
||||
|
||||
func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, minFreeSpacePercents []float32, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) {
|
||||
func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int,
|
||||
minFreeSpaces []util.MinFreeSpace, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) {
|
||||
s = &Store{grpcDialOption: grpcDialOption, Port: port, Ip: ip, PublicUrl: publicUrl, NeedleMapKind: needleMapKind}
|
||||
s.Locations = make([]*DiskLocation, 0)
|
||||
for i := 0; i < len(dirnames); i++ {
|
||||
location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpacePercents[i], idxFolder, diskTypes[i])
|
||||
location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpaces[i], idxFolder, diskTypes[i])
|
||||
location.loadExistingVolumes(needleMapKind)
|
||||
s.Locations = append(s.Locations, location)
|
||||
stats.VolumeServerMaxVolumeCounter.Add(float64(maxVolumeCounts[i]))
|
||||
@@ -106,6 +108,9 @@ func (s *Store) FindFreeLocation(diskType DiskType) (ret *DiskLocation) {
|
||||
if diskType != location.DiskType {
|
||||
continue
|
||||
}
|
||||
if location.isDiskSpaceLow {
|
||||
continue
|
||||
}
|
||||
currentFreeCount := location.MaxVolumeCount - location.VolumesLen()
|
||||
currentFreeCount *= erasure_coding.DataShardsCount
|
||||
currentFreeCount -= location.EcVolumesLen()
|
||||
|
||||
@@ -200,7 +200,6 @@ func (s *Store) readOneEcShardInterval(needleId types.NeedleId, ecVolume *erasur
|
||||
return
|
||||
}
|
||||
glog.V(0).Infof("clearing ec shard %d.%d locations: %v", ecVolume.VolumeId, shardId, err)
|
||||
forgetShardId(ecVolume, shardId)
|
||||
}
|
||||
|
||||
// try reading by recovering from other shards
|
||||
@@ -303,7 +302,7 @@ func (s *Store) doReadRemoteEcShardInterval(sourceDataNode string, needleId type
|
||||
break
|
||||
}
|
||||
if receiveErr != nil {
|
||||
return fmt.Errorf("receiving ec shard %d.%d from %s: %v", vid, shardId, sourceDataNode, err)
|
||||
return fmt.Errorf("receiving ec shard %d.%d from %s: %v", vid, shardId, sourceDataNode, receiveErr)
|
||||
}
|
||||
if resp.IsDeleted {
|
||||
is_deleted = true
|
||||
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// BytesToHumanReadable returns the converted human readable representation of the bytes.
|
||||
@@ -161,3 +165,94 @@ func NewBytesReader(b []byte) *BytesReader {
|
||||
Reader: bytes.NewReader(b),
|
||||
}
|
||||
}
|
||||
|
||||
// EmptyTo returns to if s is empty.
|
||||
func EmptyTo(s, to string) string {
|
||||
if s == "" {
|
||||
return to
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// IfElse works like b ? this : that.
|
||||
func IfElse(b bool, this, that string) string {
|
||||
if b {
|
||||
return this
|
||||
}
|
||||
return that
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// ParseBytes("42MB") -> 42000000, nil
|
||||
// ParseBytes("42 MB") -> 42000000, nil
|
||||
// ParseBytes("42 mib") -> 44040192, nil
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte, "kib": KiByte, "kb": KByte, "mib": MiByte, "mb": MByte, "gib": GiByte, "gb": GByte,
|
||||
"tib": TiByte, "tb": TByte, "pib": PiByte, "pb": PByte, "eib": EiByte, "eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte, "ki": KiByte, "k": KByte, "mi": MiByte, "m": MByte, "gi": GiByte, "g": GByte,
|
||||
"ti": TiByte, "t": TByte, "pi": PiByte, "p": PByte, "ei": EiByte, "e": EByte,
|
||||
}
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestByteParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
exp uint64
|
||||
}{
|
||||
{"42", 42},
|
||||
{"42MB", 42000000},
|
||||
{"42MiB", 44040192},
|
||||
{"42mb", 42000000},
|
||||
{"42mib", 44040192},
|
||||
{"42MIB", 44040192},
|
||||
{"42 MB", 42000000},
|
||||
{"42 MiB", 44040192},
|
||||
{"42 mb", 42000000},
|
||||
{"42 mib", 44040192},
|
||||
{"42 MIB", 44040192},
|
||||
{"42.5MB", 42500000},
|
||||
{"42.5MiB", 44564480},
|
||||
{"42.5 MB", 42500000},
|
||||
{"42.5 MiB", 44564480},
|
||||
// No need to say B
|
||||
{"42M", 42000000},
|
||||
{"42Mi", 44040192},
|
||||
{"42m", 42000000},
|
||||
{"42mi", 44040192},
|
||||
{"42MI", 44040192},
|
||||
{"42 M", 42000000},
|
||||
{"42 Mi", 44040192},
|
||||
{"42 m", 42000000},
|
||||
{"42 mi", 44040192},
|
||||
{"42 MI", 44040192},
|
||||
{"42.5M", 42500000},
|
||||
{"42.5Mi", 44564480},
|
||||
{"42.5 M", 42500000},
|
||||
{"42.5 Mi", 44564480},
|
||||
// Bug #42
|
||||
{"1,005.03 MB", 1005030000},
|
||||
// Large testing, breaks when too much larger than
|
||||
// this.
|
||||
{"12.5 EB", uint64(12.5 * float64(EByte))},
|
||||
{"12.5 E", uint64(12.5 * float64(EByte))},
|
||||
{"12.5 EiB", uint64(12.5 * float64(EiByte))},
|
||||
}
|
||||
|
||||
for _, p := range tests {
|
||||
got, err := ParseBytes(p.in)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse %v: %v", p.in, err)
|
||||
}
|
||||
if got != p.exp {
|
||||
t.Errorf("Expected %v for %v, got %v",
|
||||
p.exp, p.in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package chunk_cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
)
|
||||
|
||||
var ErrorOutOfBounds = errors.New("attempt to read out of bounds")
|
||||
|
||||
type ChunkCache interface {
|
||||
GetChunk(fileId string, minSize uint64) (data []byte)
|
||||
GetChunkSlice(fileId string, offset, length uint64) []byte
|
||||
SetChunk(fileId string, data []byte)
|
||||
}
|
||||
|
||||
@@ -22,6 +26,8 @@ type TieredChunkCache struct {
|
||||
onDiskCacheSizeLimit2 uint64
|
||||
}
|
||||
|
||||
var _ ChunkCache = &TieredChunkCache{}
|
||||
|
||||
func NewTieredChunkCache(maxEntries int64, dir string, diskSizeInUnit int64, unitSize int64) *TieredChunkCache {
|
||||
|
||||
c := &TieredChunkCache{
|
||||
@@ -87,6 +93,58 @@ func (c *TieredChunkCache) doGetChunk(fileId string, minSize uint64) (data []byt
|
||||
|
||||
}
|
||||
|
||||
func (c *TieredChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
return c.doGetChunkSlice(fileId, offset, length)
|
||||
}
|
||||
|
||||
func (c *TieredChunkCache) doGetChunkSlice(fileId string, offset, length uint64) (data []byte) {
|
||||
|
||||
minSize := offset + length
|
||||
if minSize <= c.onDiskCacheSizeLimit0 {
|
||||
data, err := c.memCache.getChunkSlice(fileId, offset, length)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to read from memcache: %s", err)
|
||||
}
|
||||
if len(data) >= int(minSize) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
fid, err := needle.ParseFileIdFromString(fileId)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to parse file id %s", fileId)
|
||||
return nil
|
||||
}
|
||||
|
||||
if minSize <= c.onDiskCacheSizeLimit0 {
|
||||
data = c.diskCaches[0].getChunkSlice(fid.Key, offset, length)
|
||||
if len(data) >= int(minSize) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
if minSize <= c.onDiskCacheSizeLimit1 {
|
||||
data = c.diskCaches[1].getChunkSlice(fid.Key, offset, length)
|
||||
if len(data) >= int(minSize) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
{
|
||||
data = c.diskCaches[2].getChunkSlice(fid.Key, offset, length)
|
||||
if len(data) >= int(minSize) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TieredChunkCache) SetChunk(fileId string, data []byte) {
|
||||
if c == nil {
|
||||
return
|
||||
@@ -131,3 +189,10 @@ func (c *TieredChunkCache) Shutdown() {
|
||||
diskCache.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
@@ -31,6 +31,20 @@ func (c *ChunkCacheInMemory) GetChunk(fileId string) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *ChunkCacheInMemory) getChunkSlice(fileId string, offset, length uint64) ([]byte, error) {
|
||||
item := c.cache.Get(fileId)
|
||||
if item == nil {
|
||||
return nil, nil
|
||||
}
|
||||
data := item.Value().([]byte)
|
||||
item.Extend(time.Hour)
|
||||
wanted := min(int(length), len(data)-int(offset))
|
||||
if wanted < 0 {
|
||||
return nil, ErrorOutOfBounds
|
||||
}
|
||||
return data[offset : int(offset)+wanted], nil
|
||||
}
|
||||
|
||||
func (c *ChunkCacheInMemory) SetChunk(fileId string, data []byte) {
|
||||
localCopy := make([]byte, len(data))
|
||||
copy(localCopy, data)
|
||||
|
||||
@@ -88,15 +88,17 @@ func (v *ChunkCacheVolume) Shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *ChunkCacheVolume) destroy() {
|
||||
func (v *ChunkCacheVolume) doReset() {
|
||||
v.Shutdown()
|
||||
os.Remove(v.fileName + ".dat")
|
||||
os.Remove(v.fileName + ".idx")
|
||||
os.Truncate(v.fileName+".dat", 0)
|
||||
os.Truncate(v.fileName+".idx", 0)
|
||||
glog.V(4).Infof("cache removeAll %s ...", v.fileName+".ldb")
|
||||
os.RemoveAll(v.fileName + ".ldb")
|
||||
glog.V(4).Infof("cache removed %s", v.fileName+".ldb")
|
||||
}
|
||||
|
||||
func (v *ChunkCacheVolume) Reset() (*ChunkCacheVolume, error) {
|
||||
v.destroy()
|
||||
v.doReset()
|
||||
return LoadOrCreateChunkCacheVolume(v.fileName, v.sizeLimit)
|
||||
}
|
||||
|
||||
@@ -119,6 +121,29 @@ func (v *ChunkCacheVolume) GetNeedle(key types.NeedleId) ([]byte, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (v *ChunkCacheVolume) getNeedleSlice(key types.NeedleId, offset, length uint64) ([]byte, error) {
|
||||
nv, ok := v.nm.Get(key)
|
||||
if !ok {
|
||||
return nil, storage.ErrorNotFound
|
||||
}
|
||||
wanted := min(int(length), int(nv.Size)-int(offset))
|
||||
if wanted < 0 {
|
||||
// should never happen, but better than panicing
|
||||
return nil, ErrorOutOfBounds
|
||||
}
|
||||
data := make([]byte, wanted)
|
||||
if readSize, readErr := v.DataBackend.ReadAt(data, nv.Offset.ToActualOffset()+int64(offset)); readErr != nil {
|
||||
return nil, fmt.Errorf("read %s.dat [%d,%d): %v",
|
||||
v.fileName, nv.Offset.ToActualOffset()+int64(offset), int(nv.Offset.ToActualOffset())+int(offset)+wanted, readErr)
|
||||
} else {
|
||||
if readSize != wanted {
|
||||
return nil, fmt.Errorf("read %d, expected %d", readSize, wanted)
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (v *ChunkCacheVolume) WriteNeedle(key types.NeedleId, data []byte) error {
|
||||
|
||||
offset := v.fileSize
|
||||
|
||||
@@ -82,6 +82,28 @@ func (c *OnDiskCacheLayer) getChunk(needleId types.NeedleId) (data []byte) {
|
||||
|
||||
}
|
||||
|
||||
func (c *OnDiskCacheLayer) getChunkSlice(needleId types.NeedleId, offset, length uint64) (data []byte) {
|
||||
|
||||
var err error
|
||||
|
||||
for _, diskCache := range c.diskCaches {
|
||||
data, err = diskCache.getNeedleSlice(needleId, offset, length)
|
||||
if err == storage.ErrorNotFound {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
glog.Errorf("failed to read cache file %s id %d", diskCache.fileName, needleId)
|
||||
continue
|
||||
}
|
||||
if len(data) != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *OnDiskCacheLayer) shutdown() {
|
||||
|
||||
for _, diskCache := range c.diskCaches {
|
||||
|
||||
@@ -126,7 +126,7 @@ func IsZstdContent(data []byte) bool {
|
||||
|
||||
// by file name extension
|
||||
switch ext {
|
||||
case ".zip", ".rar", ".gz", ".bz2", ".xz", ".zst":
|
||||
case ".zip", ".rar", ".gz", ".bz2", ".xz", ".zst", ".br":
|
||||
return false, true
|
||||
case ".pdf", ".txt", ".html", ".htm", ".css", ".js", ".json":
|
||||
return true, true
|
||||
@@ -147,6 +147,9 @@ func IsZstdContent(data []byte) bool {
|
||||
if strings.HasSuffix(mtype, "script") {
|
||||
return true, true
|
||||
}
|
||||
if strings.HasSuffix(mtype, "vnd.rar") {
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(mtype, "audio/") {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 39)
|
||||
VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 42)
|
||||
COMMIT = ""
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MinFreeSpaceType is the type of MinFreeSpace.
|
||||
type MinFreeSpaceType int
|
||||
|
||||
const (
|
||||
// AsPercent set the MinFreeSpaceType to a percentage value from 0 to 100.
|
||||
AsPercent MinFreeSpaceType = iota
|
||||
// AsBytes set the MinFreeSpaceType to a absolute value bytes.
|
||||
AsBytes
|
||||
)
|
||||
|
||||
// MinFreeSpace is type that defines the limit for the minimum free space.
|
||||
type MinFreeSpace struct {
|
||||
Type MinFreeSpaceType
|
||||
Bytes uint64
|
||||
Percent float32
|
||||
Raw string
|
||||
}
|
||||
|
||||
// IsLow tells whether the free space is low or not.
|
||||
func (s MinFreeSpace) IsLow(freeBytes uint64, freePercent float32) (yes bool, desc string) {
|
||||
switch s.Type {
|
||||
case AsPercent:
|
||||
yes = freePercent < s.Percent
|
||||
op := IfElse(yes, "<", ">=")
|
||||
return yes, fmt.Sprintf("disk free %.2f%% %s required %.2f%%", freePercent, op, s.Percent)
|
||||
case AsBytes:
|
||||
yes = freeBytes < s.Bytes
|
||||
op := IfElse(yes, "<", ">=")
|
||||
return yes, fmt.Sprintf("disk free %s %s required %s",
|
||||
BytesToHumanReadable(freeBytes), op, BytesToHumanReadable(s.Bytes))
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// String returns a string representation of MinFreeSpace.
|
||||
func (s MinFreeSpace) String() string {
|
||||
switch s.Type {
|
||||
case AsPercent:
|
||||
return fmt.Sprintf("%.2f%%", s.Percent)
|
||||
default:
|
||||
return s.Raw
|
||||
}
|
||||
}
|
||||
|
||||
// MustParseMinFreeSpace parses comma-separated argument for min free space setting.
|
||||
// minFreeSpace has the high priority than minFreeSpacePercent if it is set.
|
||||
func MustParseMinFreeSpace(minFreeSpace string, minFreeSpacePercent string) (spaces []MinFreeSpace) {
|
||||
ss := strings.Split(EmptyTo(minFreeSpace, minFreeSpacePercent), ",")
|
||||
for _, freeString := range ss {
|
||||
if vv, e := ParseMinFreeSpace(freeString); e == nil {
|
||||
spaces = append(spaces, *vv)
|
||||
} else {
|
||||
glog.Fatalf("The value specified in -minFreeSpace not a valid value %s", freeString)
|
||||
}
|
||||
}
|
||||
|
||||
return spaces
|
||||
}
|
||||
|
||||
var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid")
|
||||
|
||||
// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G
|
||||
func ParseMinFreeSpace(s string) (*MinFreeSpace, error) {
|
||||
if percent, e := strconv.ParseFloat(s, 32); e == nil {
|
||||
if percent < 0 || percent > 100 {
|
||||
return nil, ErrMinFreeSpaceBadValue
|
||||
}
|
||||
return &MinFreeSpace{Type: AsPercent, Percent: float32(percent), Raw: s}, nil
|
||||
}
|
||||
|
||||
if directSize, e := ParseBytes(s); e == nil {
|
||||
if directSize <= 100 {
|
||||
return nil, ErrMinFreeSpaceBadValue
|
||||
}
|
||||
return &MinFreeSpace{Type: AsBytes, Bytes: directSize, Raw: s}, nil
|
||||
}
|
||||
|
||||
return nil, ErrMinFreeSpaceBadValue
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseMinFreeSpace(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
ok bool
|
||||
value *MinFreeSpace
|
||||
}{
|
||||
{in: "42", ok: true, value: &MinFreeSpace{Type: AsPercent, Percent: 42, Raw: "42"}},
|
||||
{in: "-1", ok: false, value: nil},
|
||||
{in: "101", ok: false, value: nil},
|
||||
{in: "100B", ok: false, value: nil},
|
||||
{in: "100Ki", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024, Raw: "100Ki"}},
|
||||
{in: "100GiB", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024 * 1024 * 1024, Raw: "100GiB"}},
|
||||
{in: "42M", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 42 * 1000 * 1000, Raw: "42M"}},
|
||||
}
|
||||
|
||||
for _, p := range tests {
|
||||
got, err := ParseMinFreeSpace(p.in)
|
||||
if p.ok != (err == nil) {
|
||||
t.Errorf("failed to test %v", p.in)
|
||||
}
|
||||
if p.ok && err == nil && *got != *p.value {
|
||||
t.Errorf("failed to test %v", p.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (l *ExclusiveLocker) GetToken() (token int64, lockTsNs int64) {
|
||||
return atomic.LoadInt64(&l.token), atomic.LoadInt64(&l.lockTsNs)
|
||||
}
|
||||
|
||||
func (l *ExclusiveLocker) RequestLock() {
|
||||
func (l *ExclusiveLocker) RequestLock(clientName string) {
|
||||
if l.isLocking {
|
||||
return
|
||||
}
|
||||
@@ -56,6 +56,7 @@ func (l *ExclusiveLocker) RequestLock() {
|
||||
PreviousToken: atomic.LoadInt64(&l.token),
|
||||
PreviousLockTime: atomic.LoadInt64(&l.lockTsNs),
|
||||
LockName: AdminLockName,
|
||||
ClientName: clientName,
|
||||
})
|
||||
if err == nil {
|
||||
atomic.StoreInt64(&l.token, resp.Token)
|
||||
@@ -63,7 +64,7 @@ func (l *ExclusiveLocker) RequestLock() {
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
// println("leasing problem", err.Error())
|
||||
println("lock:", err.Error())
|
||||
time.Sleep(InitLockInteval)
|
||||
} else {
|
||||
break
|
||||
@@ -83,6 +84,7 @@ func (l *ExclusiveLocker) RequestLock() {
|
||||
PreviousToken: atomic.LoadInt64(&l.token),
|
||||
PreviousLockTime: atomic.LoadInt64(&l.lockTsNs),
|
||||
LockName: AdminLockName,
|
||||
ClientName: clientName,
|
||||
})
|
||||
if err == nil {
|
||||
atomic.StoreInt64(&l.token, resp.Token)
|
||||
|
||||
Reference in New Issue
Block a user