mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-01 20:26:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42766bccc8 | ||
|
|
e9fda774f4 | ||
|
|
4867aa03ab | ||
|
|
d75a7b7f62 | ||
|
|
fafff5896e | ||
|
|
a36967982d | ||
|
|
3e714a53cf | ||
|
|
5a46005ddf | ||
|
|
54440f0d63 | ||
|
|
fab0ab5440 | ||
|
|
f352616b7d | ||
|
|
18686b7375 | ||
|
|
68f1cc34a5 | ||
|
|
e1b92bc701 | ||
|
|
ff7b6d779e | ||
|
|
2e240704ab | ||
|
|
19245dde50 | ||
|
|
4131874fa8 | ||
|
|
9f55c7c90e | ||
|
|
a408b46d95 | ||
|
|
0a22eea55d | ||
|
|
d50d736e68 | ||
|
|
ae9388723f | ||
|
|
31db90942b | ||
|
|
839dd438bc | ||
|
|
bf3d0c4850 | ||
|
|
ea5bbf30bb | ||
|
|
a9b4be50af | ||
|
|
8ecdf958ab | ||
|
|
0f653d2153 | ||
|
|
0122e022ea | ||
|
|
5614ad0000 | ||
|
|
e60a7af644 | ||
|
|
88b2bbadd1 | ||
|
|
095cdb0c7f | ||
|
|
44ad07276c | ||
|
|
ccc030b245 | ||
|
|
130bc3e668 | ||
|
|
d1ce89950e | ||
|
|
da1d3b5a33 | ||
|
|
b5b5c67cf6 | ||
|
|
1d6a348d55 | ||
|
|
04a9dc898f | ||
|
|
adf80dc3f6 | ||
|
|
2d213d87e1 | ||
|
|
efef6e94bf | ||
|
|
d4dec6c730 | ||
|
|
080bfa7052 | ||
|
|
0f2a8b0af1 | ||
|
|
5aa89844b1 | ||
|
|
671ee6af6d |
@@ -20,7 +20,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v3
|
||||
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
|
||||
@@ -4,17 +4,17 @@ go 1.20
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/pubsub v1.28.0
|
||||
cloud.google.com/go/pubsub v1.30.0
|
||||
cloud.google.com/go/storage v1.30.1
|
||||
github.com/Azure/azure-pipeline-go v0.2.3
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
github.com/Shopify/sarama v1.38.1
|
||||
github.com/aws/aws-sdk-go v1.44.219
|
||||
github.com/aws/aws-sdk-go v1.44.248
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -52,17 +52,17 @@ require (
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/karlseguin/ccache/v2 v2.0.8
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.14 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.11.7
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/linxGnu/grocksdb v1.7.15
|
||||
github.com/lib/pq v1.10.8
|
||||
github.com/linxGnu/grocksdb v1.7.16
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.9 // indirect
|
||||
@@ -77,9 +77,9 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/posener/complete v1.2.3
|
||||
github.com/pquerna/cachecontrol v0.1.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/client_golang v1.15.0
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.39.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
@@ -105,36 +105,36 @@ require (
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.7
|
||||
go.mongodb.org/mongo-driver v1.11.3
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.8
|
||||
go.mongodb.org/mongo-driver v1.11.4
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
gocloud.dev v0.29.0
|
||||
gocloud.dev/pubsub/natspubsub v0.28.0
|
||||
gocloud.dev/pubsub/natspubsub v0.29.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.29.0
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874
|
||||
golang.org/x/image v0.6.0
|
||||
golang.org/x/net v0.8.0
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/oauth2 v0.6.0 // indirect
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.7.0
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.114.0
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/protobuf v1.29.1
|
||||
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 // indirect
|
||||
google.golang.org/grpc v1.54.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
modernc.org/b v1.0.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.3 // indirect
|
||||
modernc.org/libc v1.22.4 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/sqlite v1.21.0
|
||||
modernc.org/sqlite v1.22.0
|
||||
modernc.org/strutil v1.1.3
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
@@ -148,18 +148,29 @@ require (
|
||||
github.com/hanwen/go-fuse/v2 v2.2.0
|
||||
github.com/hashicorp/raft v1.4.0
|
||||
github.com/hashicorp/raft-boltdb/v2 v2.2.2
|
||||
github.com/rabbitmq/amqp091-go v1.7.0
|
||||
github.com/schollz/progressbar/v3 v3.13.0
|
||||
github.com/tikv/client-go/v2 v2.0.6
|
||||
github.com/rabbitmq/amqp091-go v1.8.0
|
||||
github.com/rclone/rclone v1.62.2
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/tikv/client-go/v2 v2.0.7
|
||||
github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.2
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.43.0
|
||||
google.golang.org/grpc/security/advancedtls v0.0.0-20220622233350-5cdb09fa29c1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.18.0 // indirect
|
||||
cloud.google.com/go/compute v1.19.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.12.0 // indirect
|
||||
cloud.google.com/go/iam v0.13.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Unknwon/goconfig v1.0.0 // indirect
|
||||
github.com/abbot/go-http-auth v0.4.0 // indirect
|
||||
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.12 // indirect
|
||||
@@ -177,67 +188,117 @@ require (
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/buengese/sgzip v0.1.1 // indirect
|
||||
github.com/calebcase/tmpfile v1.0.3 // indirect
|
||||
github.com/colinmarc/hdfs/v2 v2.3.0 // indirect
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
|
||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 // indirect
|
||||
github.com/elastic/gosigar v0.14.2 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fclairamb/go-log v0.4.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-msgpack v1.1.5 // indirect
|
||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/hirochachacha/go-smb2 v1.1.0 // indirect
|
||||
github.com/iguanesolutions/go-systemd/v5 v5.1.1 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jlaffaye/ftp v0.1.1-0.20230214004652-d84bf4be2b6e // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b // indirect
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
||||
github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6 // indirect
|
||||
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/montanaflynn/stats v0.6.6 // indirect
|
||||
github.com/nats-io/nats.go v1.20.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/nats-io/nats.go v1.23.0 // indirect
|
||||
github.com/nats-io/nkeys v0.3.0 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1 // indirect
|
||||
github.com/ncw/swift/v2 v2.0.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/oracle/oci-go-sdk/v65 v65.32.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
|
||||
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
|
||||
github.com/pingcap/kvproto v0.0.0-20230228041042-1e9aca94bab6 // indirect
|
||||
github.com/pingcap/kvproto v0.0.0-20230403051650-e166ae588106 // indirect
|
||||
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 // indirect
|
||||
github.com/pkg/xattr v0.4.9 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 // indirect
|
||||
github.com/rfjakob/eme v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.2 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.19 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stathat/consistent v1.0.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca // indirect
|
||||
github.com/tiancaiamao/gp v0.0.0-20221230034425-4025bc8a4d4a // indirect
|
||||
github.com/tikv/pd/client v0.0.0-20230301094509-c82b237672a0 // indirect
|
||||
github.com/tikv/pd/client v0.0.0-20230329114254-1948c247c2b1 // indirect
|
||||
github.com/tinylib/msgp v1.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/twmb/murmur3 v1.1.3 // indirect
|
||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e // indirect
|
||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20221215182650-986f9d10542f // indirect
|
||||
github.com/ydb-platform/ydb-go-yc v0.8.3 // indirect
|
||||
github.com/ydb-platform/ydb-go-yc-metadata v0.5.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
|
||||
github.com/yunify/qingstor-sdk-go/v3 v3.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
github.com/zeebo/errs v1.3.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.8 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.8 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
storj.io/common v0.0.0-20221123115229-fed3e6651b63 // indirect
|
||||
storj.io/drpc v0.0.32 // indirect
|
||||
storj.io/uplink v1.10.0 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/seaweedfs/raft => /Users/chrislu/go/src/github.com/seaweedfs/raft
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
appVersion: "3.45"
|
||||
version: "3.45"
|
||||
appVersion: "3.48"
|
||||
version: "3.48"
|
||||
|
||||
@@ -20,6 +20,12 @@ spec:
|
||||
port: {{ .Values.filer.grpcPort }}
|
||||
targetPort: {{ .Values.filer.grpcPort }}
|
||||
protocol: TCP
|
||||
{{- if .Values.filer.s3.enabled }}
|
||||
- name: "swfs-s3"
|
||||
port: {{ .Values.filer.s3.port }}
|
||||
targetPort: {{ .Values.filer.s3.port }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
- name: "metrics"
|
||||
port: {{ .Values.filer.metricsPort }}
|
||||
|
||||
@@ -175,7 +175,7 @@ spec:
|
||||
- mountPath: /etc/sw
|
||||
name: config-users
|
||||
readOnly: true
|
||||
{{- if .Values.filer.enablePVC }}
|
||||
{{- if (or .Values.filer.enablePVC (or (eq .Values.filer.data.type "hostPath") (eq .Values.filer.data.type "persistentVolumeClaim"))) }}
|
||||
- name: data-filer
|
||||
mountPath: /data
|
||||
{{- end }}
|
||||
|
||||
@@ -108,7 +108,7 @@ connection_max_open = 100
|
||||
connection_max_lifetime_seconds = 0
|
||||
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
|
||||
enableUpsert = true
|
||||
upsertQuery = """INSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4) ON CONFLICT (dirhash,name) DO UPDATE SET meta = EXCLUDED.meta WHERE "%[1]s".meta != EXCLUDED.meta"""
|
||||
upsertQuery = """UPSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4)"""
|
||||
|
||||
[postgres2]
|
||||
enabled = false
|
||||
@@ -133,7 +133,7 @@ connection_max_open = 100
|
||||
connection_max_lifetime_seconds = 0
|
||||
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
|
||||
enableUpsert = true
|
||||
upsertQuery = """INSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4) ON CONFLICT (dirhash,name) DO UPDATE SET meta = EXCLUDED.meta WHERE "%[1]s".meta != EXCLUDED.meta"""
|
||||
upsertQuery = """UPSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4)"""
|
||||
|
||||
[cassandra]
|
||||
# CREATE TABLE filemeta (
|
||||
@@ -344,4 +344,4 @@ cert_path=""
|
||||
# Set the private key path
|
||||
key_path=""
|
||||
# The name list used to verify the cn name
|
||||
verify_cn=""
|
||||
verify_cn=""
|
||||
|
||||
@@ -13,7 +13,8 @@ type FileChunkSection struct {
|
||||
visibleIntervals *IntervalList[*VisibleInterval]
|
||||
chunkViews *IntervalList[*ChunkView]
|
||||
reader *ChunkReadAt
|
||||
lock sync.Mutex
|
||||
lock sync.RWMutex
|
||||
isPrepared bool
|
||||
}
|
||||
|
||||
func NewFileChunkSection(si SectionIndex) *FileChunkSection {
|
||||
@@ -61,6 +62,19 @@ func removeGarbageChunks(section *FileChunkSection, garbageFileIds map[string]st
|
||||
}
|
||||
|
||||
func (section *FileChunkSection) setupForRead(group *ChunkGroup, fileSize int64) {
|
||||
if section.isPrepared {
|
||||
section.reader.fileSize = fileSize
|
||||
return
|
||||
}
|
||||
|
||||
section.lock.Lock()
|
||||
defer section.lock.Unlock()
|
||||
|
||||
if section.isPrepared {
|
||||
section.reader.fileSize = fileSize
|
||||
return
|
||||
}
|
||||
|
||||
if section.visibleIntervals == nil {
|
||||
section.visibleIntervals = readResolvedChunks(section.chunks, int64(section.sectionIndex)*SectionSize, (int64(section.sectionIndex)+1)*SectionSize)
|
||||
section.chunks, _ = SeparateGarbageChunks(section.visibleIntervals, section.chunks)
|
||||
@@ -76,23 +90,25 @@ func (section *FileChunkSection) setupForRead(group *ChunkGroup, fileSize int64)
|
||||
if section.reader == nil {
|
||||
section.reader = NewChunkReaderAtFromClient(group.readerCache, section.chunkViews, min(int64(section.sectionIndex+1)*SectionSize, fileSize))
|
||||
}
|
||||
|
||||
section.isPrepared = true
|
||||
section.reader.fileSize = fileSize
|
||||
}
|
||||
|
||||
func (section *FileChunkSection) readDataAt(group *ChunkGroup, fileSize int64, buff []byte, offset int64) (n int, tsNs int64, err error) {
|
||||
section.lock.Lock()
|
||||
defer section.lock.Unlock()
|
||||
|
||||
section.setupForRead(group, fileSize)
|
||||
section.lock.RLock()
|
||||
defer section.lock.RUnlock()
|
||||
|
||||
return section.reader.ReadAtWithTime(buff, offset)
|
||||
}
|
||||
|
||||
func (section *FileChunkSection) DataStartOffset(group *ChunkGroup, offset int64, fileSize int64) int64 {
|
||||
section.lock.Lock()
|
||||
defer section.lock.Unlock()
|
||||
|
||||
section.setupForRead(group, fileSize)
|
||||
section.lock.RLock()
|
||||
defer section.lock.RUnlock()
|
||||
|
||||
for x := section.visibleIntervals.Front(); x != nil; x = x.Next {
|
||||
visible := x.Value
|
||||
@@ -108,10 +124,10 @@ func (section *FileChunkSection) DataStartOffset(group *ChunkGroup, offset int64
|
||||
}
|
||||
|
||||
func (section *FileChunkSection) NextStopOffset(group *ChunkGroup, offset int64, fileSize int64) int64 {
|
||||
section.lock.Lock()
|
||||
defer section.lock.Unlock()
|
||||
|
||||
section.setupForRead(group, fileSize)
|
||||
section.lock.RLock()
|
||||
defer section.lock.RUnlock()
|
||||
|
||||
isAfterOffset := false
|
||||
for x := section.visibleIntervals.Front(); x != nil; x = x.Next {
|
||||
|
||||
@@ -2,16 +2,23 @@ package filer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"strings"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func (f *Filer) CanRename(source, target util.FullPath) error {
|
||||
func (f *Filer) CanRename(source, target util.FullPath, oldName string) error {
|
||||
sourcePath := source.Child(oldName)
|
||||
if strings.HasPrefix(string(target), string(sourcePath)) {
|
||||
return fmt.Errorf("mv: can not move directory to a subdirectory of itself")
|
||||
}
|
||||
|
||||
sourceBucket := f.DetectBucket(source)
|
||||
targetBucket := f.DetectBucket(target)
|
||||
if sourceBucket != targetBucket {
|
||||
return fmt.Errorf("can not move across collection %s => %s", sourceBucket, targetBucket)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func (interval *Interval[T]) Size() int64 {
|
||||
type IntervalList[T IntervalValue] struct {
|
||||
head *Interval[T]
|
||||
tail *Interval[T]
|
||||
Lock sync.Mutex
|
||||
Lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewIntervalList[T IntervalValue]() *IntervalList[T] {
|
||||
@@ -248,8 +248,8 @@ func (list *IntervalList[T]) overlayInterval(interval *Interval[T]) {
|
||||
}
|
||||
|
||||
func (list *IntervalList[T]) Len() int {
|
||||
list.Lock.Lock()
|
||||
defer list.Lock.Unlock()
|
||||
list.Lock.RLock()
|
||||
defer list.Lock.RUnlock()
|
||||
|
||||
var count int
|
||||
for t := list.head; t != nil; t = t.Next {
|
||||
|
||||
@@ -106,8 +106,8 @@ func (c *ChunkReadAt) ReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
c.readerPattern.MonitorReadAt(offset, len(p))
|
||||
|
||||
c.chunkViews.Lock.Lock()
|
||||
defer c.chunkViews.Lock.Unlock()
|
||||
c.chunkViews.Lock.RLock()
|
||||
defer c.chunkViews.Lock.RUnlock()
|
||||
|
||||
// glog.V(4).Infof("ReadAt [%d,%d) of total file size %d bytes %d chunk views", offset, offset+int64(len(p)), c.fileSize, len(c.chunkViews))
|
||||
n, _, err = c.doReadAt(p, offset)
|
||||
@@ -118,8 +118,8 @@ func (c *ChunkReadAt) ReadAtWithTime(p []byte, offset int64) (n int, ts int64, e
|
||||
|
||||
c.readerPattern.MonitorReadAt(offset, len(p))
|
||||
|
||||
c.chunkViews.Lock.Lock()
|
||||
defer c.chunkViews.Lock.Unlock()
|
||||
c.chunkViews.Lock.RLock()
|
||||
defer c.chunkViews.Lock.RUnlock()
|
||||
|
||||
// glog.V(4).Infof("ReadAt [%d,%d) of total file size %d bytes %d chunk views", offset, offset+int64(len(p)), c.fileSize, len(c.chunkViews))
|
||||
return c.doReadAt(p, offset)
|
||||
|
||||
@@ -17,7 +17,7 @@ type FileHandle struct {
|
||||
fh FileHandleId
|
||||
counter int64
|
||||
entry *LockedEntry
|
||||
entryLock sync.Mutex
|
||||
entryLock sync.RWMutex
|
||||
entryChunkGroup *filer.ChunkGroup
|
||||
inode uint64
|
||||
wfs *WFS
|
||||
@@ -27,8 +27,7 @@ type FileHandle struct {
|
||||
dirtyPages *PageWriter
|
||||
reader *filer.ChunkReadAt
|
||||
contentType string
|
||||
handle uint64
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
|
||||
isDeleted bool
|
||||
|
||||
@@ -103,6 +102,9 @@ func (fh *FileHandle) AddChunks(chunks []*filer_pb.FileChunk) {
|
||||
}
|
||||
|
||||
func (fh *FileHandle) ReleaseHandle() {
|
||||
fh.Lock()
|
||||
defer fh.Unlock()
|
||||
|
||||
fh.entryLock.Lock()
|
||||
defer fh.entryLock.Unlock()
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ func (fh *FileHandle) readFromDirtyPages(buff []byte, startOffset int64, tsNs in
|
||||
}
|
||||
|
||||
func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, int64, error) {
|
||||
fh.entryLock.Lock()
|
||||
defer fh.entryLock.Unlock()
|
||||
fh.entryLock.RLock()
|
||||
defer fh.entryLock.RUnlock()
|
||||
|
||||
fileFullPath := fh.FullPath()
|
||||
|
||||
|
||||
@@ -58,12 +58,12 @@ func (wfs *WFS) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name strin
|
||||
inode := wfs.inodeToPath.Lookup(fullFilePath, localEntry.Crtime.Unix(), localEntry.IsDirectory(), len(localEntry.HardLinkId) > 0, localEntry.Inode, true)
|
||||
|
||||
if fh, found := wfs.fhmap.FindFileHandle(inode); found {
|
||||
fh.entryLock.Lock()
|
||||
fh.entryLock.RLock()
|
||||
if entry := fh.GetEntry(); entry != nil {
|
||||
glog.V(4).Infof("lookup opened file %s size %d", dirPath.Child(localEntry.Name()), filer.FileSize(entry))
|
||||
localEntry = filer.FromPbEntry(string(dirPath), entry)
|
||||
}
|
||||
fh.entryLock.Unlock()
|
||||
fh.entryLock.RUnlock()
|
||||
}
|
||||
|
||||
wfs.outputFilerEntry(out, inode, localEntry)
|
||||
|
||||
@@ -46,18 +46,14 @@ func (wfs *WFS) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn)
|
||||
// lock source and target file handles
|
||||
fhOut.Lock()
|
||||
defer fhOut.Unlock()
|
||||
fhOut.entryLock.Lock()
|
||||
defer fhOut.entryLock.Unlock()
|
||||
|
||||
if fhOut.entry == nil {
|
||||
return 0, fuse.ENOENT
|
||||
}
|
||||
|
||||
if fhIn.fh != fhOut.fh {
|
||||
fhIn.Lock()
|
||||
defer fhIn.Unlock()
|
||||
fhIn.entryLock.Lock()
|
||||
defer fhIn.entryLock.Unlock()
|
||||
fhIn.RLock()
|
||||
defer fhIn.RUnlock()
|
||||
}
|
||||
|
||||
// directories are not supported
|
||||
|
||||
@@ -35,10 +35,10 @@ func (wfs *WFS) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekO
|
||||
}
|
||||
|
||||
// lock the file until the proper offset was calculated
|
||||
fh.Lock()
|
||||
defer fh.Unlock()
|
||||
fh.entryLock.Lock()
|
||||
defer fh.entryLock.Unlock()
|
||||
fh.RLock()
|
||||
defer fh.RUnlock()
|
||||
fh.entryLock.RLock()
|
||||
defer fh.entryLock.RUnlock()
|
||||
|
||||
fileSize := int64(filer.FileSize(fh.GetEntry()))
|
||||
offset := max(int64(in.Offset), 0)
|
||||
|
||||
@@ -41,8 +41,8 @@ func (wfs *WFS) Read(cancel <-chan struct{}, in *fuse.ReadIn, buff []byte) (fuse
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
fh.Lock()
|
||||
defer fh.Unlock()
|
||||
fh.RLock()
|
||||
defer fh.RUnlock()
|
||||
|
||||
offset := int64(in.Offset)
|
||||
totalRead, err := readDataByFileHandle(buff, fh, offset)
|
||||
|
||||
@@ -96,7 +96,7 @@ func (wfs *WFS) doFlush(fh *FileHandle, uid, gid uint32) fuse.Status {
|
||||
fileFullPath := fh.FullPath()
|
||||
dir, name := fileFullPath.DirAndName()
|
||||
// send the data to the OS
|
||||
glog.V(4).Infof("doFlush %s fh %d", fileFullPath, fh.handle)
|
||||
glog.V(4).Infof("doFlush %s fh %d", fileFullPath, fh.fh)
|
||||
|
||||
if !wfs.IsOverQuota {
|
||||
if err := fh.dirtyPages.FlushData(); err != nil {
|
||||
@@ -177,7 +177,7 @@ func (wfs *WFS) doFlush(fh *FileHandle, uid, gid uint32) fuse.Status {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("%v fh %d flush: %v", fileFullPath, fh.handle, err)
|
||||
glog.Errorf("%v fh %d flush: %v", fileFullPath, fh.fh, err)
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ type StorageOption struct {
|
||||
TtlSeconds int32
|
||||
Fsync bool
|
||||
VolumeGrowthCount uint32
|
||||
SaveInside bool
|
||||
}
|
||||
|
||||
func (so *StorageOption) TtlString() string {
|
||||
|
||||
@@ -5,11 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
@@ -19,6 +14,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
type UploadOption struct {
|
||||
@@ -120,7 +121,8 @@ func UploadWithRetry(filerClient filer_pb.FilerClient, assignRequest *filer_pb.A
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
err = util.Retry("uploadWithRetry", doUploadFunc)
|
||||
uploadErrList := []string{"transport", "is read only"}
|
||||
err = util.MultiRetry("uploadWithRetry", uploadErrList, doUploadFunc)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const cutoffTimeNewEmptyDir = 3
|
||||
|
||||
func (entry *Entry) IsInRemoteOnly() bool {
|
||||
return len(entry.GetChunks()) == 0 && entry.RemoteEntry != nil && entry.RemoteEntry.RemoteSize > 0
|
||||
}
|
||||
@@ -28,6 +31,10 @@ func (entry *Entry) FileMode() (fileMode os.FileMode) {
|
||||
return
|
||||
}
|
||||
|
||||
func (entry *Entry) IsOlderDir() bool {
|
||||
return entry.IsDirectory && entry.Attributes != nil && entry.Attributes.Mime == "" && entry.Attributes.GetCrtime() <= time.Now().Unix()-cutoffTimeNewEmptyDir
|
||||
}
|
||||
|
||||
func ToFileIdObject(fileIdStr string) (*FileId, error) {
|
||||
t, err := needle.ParseFileIdFromString(fileIdStr)
|
||||
if err != nil {
|
||||
@@ -143,18 +150,22 @@ var ErrNotFound = errors.New("filer: no entry is found in filer store")
|
||||
func IsEmpty(event *SubscribeMetadataResponse) bool {
|
||||
return event.EventNotification.NewEntry == nil && event.EventNotification.OldEntry == nil
|
||||
}
|
||||
|
||||
func IsCreate(event *SubscribeMetadataResponse) bool {
|
||||
return event.EventNotification.NewEntry != nil && event.EventNotification.OldEntry == nil
|
||||
}
|
||||
|
||||
func IsUpdate(event *SubscribeMetadataResponse) bool {
|
||||
return event.EventNotification.NewEntry != nil &&
|
||||
event.EventNotification.OldEntry != nil &&
|
||||
event.Directory == event.EventNotification.NewParentPath &&
|
||||
event.EventNotification.NewEntry.Name == event.EventNotification.OldEntry.Name
|
||||
}
|
||||
|
||||
func IsDelete(event *SubscribeMetadataResponse) bool {
|
||||
return event.EventNotification.NewEntry == nil && event.EventNotification.OldEntry != nil
|
||||
}
|
||||
|
||||
func IsRename(event *SubscribeMetadataResponse) bool {
|
||||
return event.EventNotification.NewEntry != nil &&
|
||||
event.EventNotification.OldEntry != nil &&
|
||||
|
||||
@@ -9,4 +9,5 @@ const (
|
||||
|
||||
SeaweedStorageDestinationHeader = "x-seaweedfs-destination"
|
||||
MultipartUploadsFolder = ".uploads"
|
||||
FolderMimeType = "httpd/unix-directory"
|
||||
)
|
||||
|
||||
@@ -94,12 +94,15 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request)
|
||||
defer dataReader.Close()
|
||||
|
||||
objectContentType := r.Header.Get("Content-Type")
|
||||
if strings.HasSuffix(object, "/") && r.ContentLength == 0 {
|
||||
if strings.HasSuffix(object, "/") && r.ContentLength <= 1024 {
|
||||
if err := s3a.mkdir(
|
||||
s3a.option.BucketsPath, bucket+strings.TrimSuffix(object, "/"),
|
||||
func(entry *filer_pb.Entry) {
|
||||
if objectContentType == "" {
|
||||
objectContentType = "httpd/unix-directory"
|
||||
objectContentType = s3_constants.FolderMimeType
|
||||
}
|
||||
if r.ContentLength > 0 {
|
||||
entry.Content, _ = io.ReadAll(r.Body)
|
||||
}
|
||||
entry.Attributes.Mime = objectContentType
|
||||
}); err != nil {
|
||||
|
||||
@@ -18,8 +18,6 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||
)
|
||||
|
||||
const cutoffTimeNewEmptyDir = 3
|
||||
|
||||
type ListBucketResultV2 struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult"`
|
||||
Name string `xml:"Name"`
|
||||
@@ -141,7 +139,8 @@ func (s3a *S3ApiServer) listFilerEntries(bucket string, originalPrefix string, m
|
||||
var doErr error
|
||||
var nextMarker string
|
||||
cursor := &ListingCursor{
|
||||
maxKeys: maxKeys,
|
||||
maxKeys: maxKeys,
|
||||
prefixEndsOnDelimiter: strings.HasSuffix(originalPrefix, "/") && len(originalMarker) == 0,
|
||||
}
|
||||
|
||||
// check filer
|
||||
@@ -151,14 +150,7 @@ func (s3a *S3ApiServer) listFilerEntries(bucket string, originalPrefix string, m
|
||||
nextMarker, doErr = s3a.doListFilerEntries(client, reqDir, prefix, cursor, marker, delimiter, false, func(dir string, entry *filer_pb.Entry) {
|
||||
empty = false
|
||||
if entry.IsDirectory {
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if delimiter == "/" { // A response can contain CommonPrefixes only if you specify a delimiter.
|
||||
commonPrefixes = append(commonPrefixes, PrefixEntry{
|
||||
Prefix: fmt.Sprintf("%s/%s/", dir, entry.Name)[len(bucketPrefix):],
|
||||
})
|
||||
//All of the keys (up to 1,000) rolled up into a common prefix count as a single return when calculating the number of returns.
|
||||
cursor.maxKeys--
|
||||
} else if entry.IsDirectoryKeyObject() {
|
||||
if entry.IsDirectoryKeyObject() {
|
||||
contents = append(contents, ListEntry{
|
||||
Key: fmt.Sprintf("%s/%s/", dir, entry.Name)[len(bucketPrefix):],
|
||||
LastModified: time.Unix(entry.Attributes.Mtime, 0).UTC(),
|
||||
@@ -170,6 +162,13 @@ func (s3a *S3ApiServer) listFilerEntries(bucket string, originalPrefix string, m
|
||||
StorageClass: "STANDARD",
|
||||
})
|
||||
cursor.maxKeys--
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
} else if delimiter == "/" { // A response can contain CommonPrefixes only if you specify a delimiter.
|
||||
commonPrefixes = append(commonPrefixes, PrefixEntry{
|
||||
Prefix: fmt.Sprintf("%s/%s/", dir, entry.Name)[len(bucketPrefix):],
|
||||
})
|
||||
//All of the keys (up to 1,000) rolled up into a common prefix count as a single return when calculating the number of returns.
|
||||
cursor.maxKeys--
|
||||
}
|
||||
} else {
|
||||
storageClass := "STANDARD"
|
||||
@@ -199,7 +198,7 @@ func (s3a *S3ApiServer) listFilerEntries(bucket string, originalPrefix string, m
|
||||
nextMarker = requestDir + "/" + nextMarker
|
||||
}
|
||||
break
|
||||
} else if empty {
|
||||
} else if empty || strings.HasSuffix(originalPrefix, "/") {
|
||||
nextMarker = ""
|
||||
break
|
||||
} else {
|
||||
@@ -227,8 +226,9 @@ func (s3a *S3ApiServer) listFilerEntries(bucket string, originalPrefix string, m
|
||||
}
|
||||
|
||||
type ListingCursor struct {
|
||||
maxKeys int
|
||||
isTruncated bool
|
||||
maxKeys int
|
||||
isTruncated bool
|
||||
prefixEndsOnDelimiter bool
|
||||
}
|
||||
|
||||
// the prefix and marker may be in different directories
|
||||
@@ -236,7 +236,11 @@ type ListingCursor struct {
|
||||
func normalizePrefixMarker(prefix, marker string) (alignedDir, alignedPrefix, alignedMarker string) {
|
||||
// alignedDir should not end with "/"
|
||||
// alignedDir, alignedPrefix, alignedMarker should only have "/" in middle
|
||||
prefix = strings.TrimLeft(prefix, "/")
|
||||
if len(marker) == 0 {
|
||||
prefix = strings.Trim(prefix, "/")
|
||||
} else {
|
||||
prefix = strings.TrimLeft(prefix, "/")
|
||||
}
|
||||
marker = strings.TrimLeft(marker, "/")
|
||||
if prefix == "" {
|
||||
return "", "", marker
|
||||
@@ -264,6 +268,7 @@ func normalizePrefixMarker(prefix, marker string) (alignedDir, alignedPrefix, al
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toDirAndName(dirAndName string) (dir, name string) {
|
||||
sepIndex := strings.LastIndex(dirAndName, "/")
|
||||
if sepIndex >= 0 {
|
||||
@@ -273,6 +278,7 @@ func toDirAndName(dirAndName string) (dir, name string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toParentAndDescendants(dirAndName string) (dir, name string) {
|
||||
sepIndex := strings.Index(dirAndName, "/")
|
||||
if sepIndex >= 0 {
|
||||
@@ -287,7 +293,7 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
|
||||
// invariants
|
||||
// prefix and marker should be under dir, marker may contain "/"
|
||||
// maxKeys should be updated for each recursion
|
||||
|
||||
// glog.V(4).Infof("doListFilerEntries dir: %s, prefix: %s, marker %s, maxKeys: %d, prefixEndsOnDelimiter: %+v", dir, prefix, marker, cursor.maxKeys, cursor.prefixEndsOnDelimiter)
|
||||
if prefix == "/" && delimiter == "/" {
|
||||
return
|
||||
}
|
||||
@@ -319,6 +325,9 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
|
||||
StartFromFileName: marker,
|
||||
InclusiveStartFrom: inclusiveStartFrom,
|
||||
}
|
||||
if cursor.prefixEndsOnDelimiter {
|
||||
request.Limit = uint32(1)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -344,13 +353,29 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
|
||||
}
|
||||
entry := resp.Entry
|
||||
nextMarker = entry.Name
|
||||
if cursor.prefixEndsOnDelimiter {
|
||||
if entry.Name == prefix && entry.IsDirectory {
|
||||
if delimiter != "/" {
|
||||
cursor.prefixEndsOnDelimiter = false
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if entry.IsDirectory {
|
||||
// println("ListEntries", dir, "dir:", entry.Name)
|
||||
// glog.V(4).Infof("List Dir Entries %s, file: %s, maxKeys %d", dir, entry.Name, cursor.maxKeys)
|
||||
if entry.Name == s3_constants.MultipartUploadsFolder { // FIXME no need to apply to all directories. this extra also affects maxKeys
|
||||
continue
|
||||
}
|
||||
if delimiter != "/" {
|
||||
eachEntryFn(dir, entry)
|
||||
if delimiter != "/" || cursor.prefixEndsOnDelimiter {
|
||||
if cursor.prefixEndsOnDelimiter {
|
||||
cursor.prefixEndsOnDelimiter = false
|
||||
if entry.IsDirectoryKeyObject() {
|
||||
eachEntryFn(dir, entry)
|
||||
}
|
||||
} else {
|
||||
eachEntryFn(dir, entry)
|
||||
}
|
||||
subNextMarker, subErr := s3a.doListFilerEntries(client, dir+"/"+entry.Name, "", cursor, "", delimiter, false, eachEntryFn)
|
||||
if subErr != nil {
|
||||
err = fmt.Errorf("doListFilerEntries2: %v", subErr)
|
||||
@@ -364,7 +389,7 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
|
||||
// println("doListFilerEntries2 nextMarker", nextMarker)
|
||||
} else {
|
||||
var isEmpty bool
|
||||
if !s3a.option.AllowEmptyFolder && !entry.IsDirectoryKeyObject() {
|
||||
if !s3a.option.AllowEmptyFolder && entry.IsOlderDir() {
|
||||
if isEmpty, err = s3a.ensureDirectoryAllEmpty(client, dir, entry.Name); err != nil {
|
||||
glog.Errorf("check empty folder %s: %v", dir, err)
|
||||
}
|
||||
@@ -375,7 +400,10 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
|
||||
}
|
||||
} else {
|
||||
eachEntryFn(dir, entry)
|
||||
// println("ListEntries", dir, "file:", entry.Name, "maxKeys", cursor.maxKeys)
|
||||
// glog.V(4).Infof("List File Entries %s, file: %s, maxKeys %d", dir, entry.Name, cursor.maxKeys)
|
||||
}
|
||||
if cursor.prefixEndsOnDelimiter {
|
||||
cursor.prefixEndsOnDelimiter = false
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -417,16 +445,11 @@ func (s3a *S3ApiServer) ensureDirectoryAllEmpty(filerClient filer_pb.SeaweedFile
|
||||
var startFrom string
|
||||
var isExhausted bool
|
||||
var foundEntry bool
|
||||
cutOffTimeAtSec := time.Now().Unix() + cutoffTimeNewEmptyDir
|
||||
for fileCounter == 0 && !isExhausted && err == nil {
|
||||
err = filer_pb.SeaweedList(filerClient, currentDir, "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
foundEntry = true
|
||||
if entry.IsDirectory {
|
||||
if entry.Attributes != nil && cutOffTimeAtSec >= entry.Attributes.GetCrtime() {
|
||||
fileCounter++
|
||||
} else {
|
||||
subDirs = append(subDirs, entry.Name)
|
||||
}
|
||||
if entry.IsOlderDir() {
|
||||
subDirs = append(subDirs, entry.Name)
|
||||
} else {
|
||||
fileCounter++
|
||||
}
|
||||
@@ -459,7 +482,7 @@ func (s3a *S3ApiServer) ensureDirectoryAllEmpty(filerClient filer_pb.SeaweedFile
|
||||
}
|
||||
|
||||
glog.V(1).Infof("deleting empty folder %s", currentDir)
|
||||
if err = doDeleteEntry(filerClient, parentDir, name, true, true); err != nil {
|
||||
if err = doDeleteEntry(filerClient, parentDir, name, true, false); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ func Test_normalizePrefixMarker(t *testing.T) {
|
||||
{"prefix is a directory",
|
||||
args{"/parentDir/data/",
|
||||
""},
|
||||
"parentDir/data",
|
||||
"",
|
||||
"parentDir",
|
||||
"data",
|
||||
"",
|
||||
},
|
||||
{"normal case",
|
||||
|
||||
+11
-2
@@ -15,6 +15,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@@ -30,6 +31,10 @@ import (
|
||||
|
||||
var serverStats *stats.ServerStats
|
||||
var startTime = time.Now()
|
||||
var writePool = sync.Pool{New: func() interface{} {
|
||||
return bufio.NewWriterSize(nil, 128*1024)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serverStats = stats.NewServerStats()
|
||||
@@ -279,8 +284,12 @@ func adjustHeaderContentDisposition(w http.ResponseWriter, r *http.Request, file
|
||||
|
||||
func processRangeRequest(r *http.Request, w http.ResponseWriter, totalSize int64, mimeType string, writeFn func(writer io.Writer, offset int64, size int64) error) error {
|
||||
rangeReq := r.Header.Get("Range")
|
||||
bufferedWriter := bufio.NewWriterSize(w, 128*1024)
|
||||
defer bufferedWriter.Flush()
|
||||
bufferedWriter := writePool.Get().(*bufio.Writer)
|
||||
bufferedWriter.Reset(w)
|
||||
defer func() {
|
||||
bufferedWriter.Flush()
|
||||
writePool.Put(bufferedWriter)
|
||||
}()
|
||||
|
||||
if rangeReq == "" {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
|
||||
|
||||
@@ -19,7 +19,7 @@ func (fs *FilerServer) AtomicRenameEntry(ctx context.Context, req *filer_pb.Atom
|
||||
oldParent := util.FullPath(filepath.ToSlash(req.OldDirectory))
|
||||
newParent := util.FullPath(filepath.ToSlash(req.NewDirectory))
|
||||
|
||||
if err := fs.filer.CanRename(oldParent, newParent); err != nil {
|
||||
if err := fs.filer.CanRename(oldParent, newParent, req.OldName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (fs *FilerServer) StreamRenameEntry(req *filer_pb.StreamRenameEntryRequest,
|
||||
oldParent := util.FullPath(filepath.ToSlash(req.OldDirectory))
|
||||
newParent := util.FullPath(filepath.ToSlash(req.NewDirectory))
|
||||
|
||||
if err := fs.filer.CanRename(oldParent, newParent); err != nil {
|
||||
if err := fs.filer.CanRename(oldParent, newParent, req.OldName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/images"
|
||||
@@ -120,7 +118,8 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
writeJsonQuiet(w, r, http.StatusOK, entry)
|
||||
return
|
||||
}
|
||||
if slices.Contains([]string{"httpd/unix-directory", ""}, entry.Attr.Mime) {
|
||||
if entry.Attr.Mime == "" || (entry.Attr.Mime == s3_constants.FolderMimeType && r.Header.Get(s3_constants.AmzIdentityId) == "") {
|
||||
// return index of directory for non s3 gateway
|
||||
fs.listDirectoryHandler(w, r)
|
||||
return
|
||||
}
|
||||
@@ -128,7 +127,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Set(s3_constants.X_SeaweedFS_Header_Directory_Key, "true")
|
||||
}
|
||||
|
||||
if isForDirectory {
|
||||
if isForDirectory && entry.Attr.Mime != s3_constants.FolderMimeType {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request, conte
|
||||
query.Get("dataCenter"),
|
||||
query.Get("rack"),
|
||||
query.Get("dataNode"),
|
||||
query.Get("saveInside"),
|
||||
)
|
||||
if err != nil {
|
||||
if err == ErrReadOnly {
|
||||
@@ -103,6 +104,10 @@ func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request, conte
|
||||
so.DiskType = fs.option.DiskType
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/etc") {
|
||||
so.SaveInside = true
|
||||
}
|
||||
|
||||
if query.Has("mv.from") {
|
||||
fs.move(ctx, w, r, so)
|
||||
} else {
|
||||
@@ -246,7 +251,7 @@ func (fs *FilerServer) detectStorageOption(requestURI, qCollection, qReplication
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *FilerServer) detectStorageOption0(requestURI, qCollection, qReplication string, qTtl string, diskType string, fsync string, dataCenter, rack, dataNode string) (*operation.StorageOption, error) {
|
||||
func (fs *FilerServer) detectStorageOption0(requestURI, qCollection, qReplication string, qTtl string, diskType string, fsync string, dataCenter, rack, dataNode, saveInside string) (*operation.StorageOption, error) {
|
||||
|
||||
ttl, err := needle.ReadTTL(qTtl)
|
||||
if err != nil {
|
||||
@@ -260,6 +265,11 @@ func (fs *FilerServer) detectStorageOption0(requestURI, qCollection, qReplicatio
|
||||
} else if fsync == "true" {
|
||||
so.Fsync = true
|
||||
}
|
||||
if saveInside == "true" {
|
||||
so.SaveInside = true
|
||||
} else {
|
||||
so.SaveInside = false
|
||||
}
|
||||
}
|
||||
|
||||
return so, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
//"github.com/seaweedfs/seaweedfs/weed/s3api"
|
||||
@@ -64,7 +65,6 @@ func (fs *FilerServer) autoChunk(ctx context.Context, w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
func (fs *FilerServer) doPostAutoChunk(ctx context.Context, w http.ResponseWriter, r *http.Request, chunkSize int32, contentLength int64, so *operation.StorageOption) (filerResult *FilerPostResult, md5bytes []byte, replyerr error) {
|
||||
|
||||
multipartReader, multipartReaderErr := r.MultipartReader()
|
||||
if multipartReaderErr != nil {
|
||||
return nil, nil, multipartReaderErr
|
||||
@@ -84,6 +84,15 @@ func (fs *FilerServer) doPostAutoChunk(ctx context.Context, w http.ResponseWrite
|
||||
contentType = ""
|
||||
}
|
||||
|
||||
if so.SaveInside {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
buf.ReadFrom(part1)
|
||||
filerResult, replyerr = fs.saveMetaData(ctx, r, fileName, contentType, so, nil, nil, 0, buf.Bytes())
|
||||
bufPool.Put(buf)
|
||||
return
|
||||
}
|
||||
|
||||
fileChunks, md5Hash, chunkOffset, err, smallContent := fs.uploadReaderToChunks(w, r, part1, chunkSize, fileName, contentType, contentLength, so)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -72,7 +72,7 @@ func (vs *VolumeServer) VolumeEcShardsGenerate(ctx context.Context, req *volume_
|
||||
|
||||
// write .vif files
|
||||
if err := volume_info.SaveVolumeInfo(baseFileName+".vif", &volume_server_pb.VolumeInfo{Version: uint32(v.Version())}); err != nil {
|
||||
return nil, fmt.Errorf("WriteEcFiles %s: %v", baseFileName, err)
|
||||
return nil, fmt.Errorf("SaveVolumeInfo %s: %v", baseFileName, err)
|
||||
}
|
||||
|
||||
shouldCleanup = false
|
||||
@@ -408,7 +408,9 @@ func (vs *VolumeServer) VolumeEcShardsToVolume(ctx context.Context, req *volume_
|
||||
|
||||
glog.V(0).Infof("VolumeEcShardsToVolume: %v", req)
|
||||
|
||||
v, found := vs.store.FindEcVolume(needle.VolumeId(req.VolumeId))
|
||||
// collect .ec00 ~ .ec09 files
|
||||
shardFileNames := make([]string, erasure_coding.DataShardsCount)
|
||||
v, found := vs.store.CollectEcShards(needle.VolumeId(req.VolumeId), shardFileNames)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("ec volume %d not found", req.VolumeId)
|
||||
}
|
||||
@@ -417,6 +419,12 @@ func (vs *VolumeServer) VolumeEcShardsToVolume(ctx context.Context, req *volume_
|
||||
return nil, fmt.Errorf("existing collection:%v unexpected input: %v", v.Collection, req.Collection)
|
||||
}
|
||||
|
||||
for shardId := 0; shardId < erasure_coding.DataShardsCount; shardId++ {
|
||||
if shardFileNames[shardId] == "" {
|
||||
return nil, fmt.Errorf("ec volume %d missing shard %d", req.VolumeId, shardId)
|
||||
}
|
||||
}
|
||||
|
||||
dataBaseFileName, indexBaseFileName := v.DataBaseFileName(), v.IndexBaseFileName()
|
||||
// calculate .dat file size
|
||||
datFileSize, err := erasure_coding.FindDatFileSize(dataBaseFileName, indexBaseFileName)
|
||||
@@ -425,8 +433,8 @@ func (vs *VolumeServer) VolumeEcShardsToVolume(ctx context.Context, req *volume_
|
||||
}
|
||||
|
||||
// write .dat file from .ec00 ~ .ec09 files
|
||||
if err := erasure_coding.WriteDatFile(dataBaseFileName, datFileSize); err != nil {
|
||||
return nil, fmt.Errorf("WriteEcFiles %s: %v", dataBaseFileName, err)
|
||||
if err := erasure_coding.WriteDatFile(dataBaseFileName, datFileSize, shardFileNames); err != nil {
|
||||
return nil, fmt.Errorf("WriteDatFile %s: %v", dataBaseFileName, err)
|
||||
}
|
||||
|
||||
// write .idx file from .ecx and .ecj files
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
@@ -51,7 +52,7 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
|
||||
locationPrefix := fsConfigureCommand.String("locationPrefix", "", "path prefix, required to update the path-specific configuration")
|
||||
collection := fsConfigureCommand.String("collection", "", "assign writes to this collection")
|
||||
replication := fsConfigureCommand.String("replication", "", "assign writes with this replication")
|
||||
ttl := fsConfigureCommand.String("ttl", "", "assign writes with this ttl")
|
||||
ttl := fsConfigureCommand.String("ttl", "", "assign writes with this ttl (e.g., 1m, 1h, 1d, 1w, 1y)")
|
||||
diskType := fsConfigureCommand.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
|
||||
fsync := fsConfigureCommand.Bool("fsync", false, "fsync for the writes")
|
||||
isReadOnly := fsConfigureCommand.Bool("readOnly", false, "disable writes")
|
||||
@@ -102,6 +103,16 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
|
||||
}
|
||||
}
|
||||
|
||||
// check ttl
|
||||
if *ttl != "" {
|
||||
regex := "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[mhdwMy]$"
|
||||
match, _ := regexp.MatchString(regex, *ttl)
|
||||
|
||||
if !match {
|
||||
return fmt.Errorf("ttl should be of the following format [1 to 255][unit] (e.g., 5m, 2h, 180d, 1w, 2y)")
|
||||
}
|
||||
}
|
||||
|
||||
// save it
|
||||
if *isDelete {
|
||||
fc.DeleteLocationConf(*locationPrefix)
|
||||
|
||||
@@ -182,6 +182,9 @@ func doSaveRemoteEntry(client filer_pb.SeaweedFilerClient, localDir string, exis
|
||||
existingEntry.RemoteEntry = remoteEntry
|
||||
existingEntry.Attributes.FileSize = uint64(remoteEntry.RemoteSize)
|
||||
existingEntry.Attributes.Mtime = remoteEntry.RemoteMtime
|
||||
existingEntry.Attributes.Md5 = nil
|
||||
existingEntry.Chunks = nil
|
||||
existingEntry.Content = nil
|
||||
_, updateErr := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
|
||||
Directory: localDir,
|
||||
Entry: existingEntry,
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package rclone_backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rclone/rclone/fs/config/configfile"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
_ "github.com/rclone/rclone/backend/all"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/accounting"
|
||||
"github.com/rclone/rclone/fs/object"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/backend"
|
||||
)
|
||||
|
||||
func init() {
|
||||
backend.BackendStorageFactories["rclone"] = &RcloneBackendFactory{}
|
||||
configfile.Install()
|
||||
}
|
||||
|
||||
type RcloneBackendFactory struct {
|
||||
}
|
||||
|
||||
func (factory *RcloneBackendFactory) StorageType() backend.StorageType {
|
||||
return "rclone"
|
||||
}
|
||||
|
||||
func (factory *RcloneBackendFactory) BuildStorage(configuration backend.StringProperties, configPrefix string, id string) (backend.BackendStorage, error) {
|
||||
return newRcloneBackendStorage(configuration, configPrefix, id)
|
||||
}
|
||||
|
||||
type RcloneBackendStorage struct {
|
||||
id string
|
||||
remoteName string
|
||||
fs fs.Fs
|
||||
}
|
||||
|
||||
func newRcloneBackendStorage(configuration backend.StringProperties, configPrefix string, id string) (s *RcloneBackendStorage, err error) {
|
||||
s = &RcloneBackendStorage{}
|
||||
s.id = id
|
||||
s.remoteName = configuration.GetString(configPrefix + "remote_name")
|
||||
|
||||
ctx := context.TODO()
|
||||
accounting.Start(ctx)
|
||||
|
||||
fsPath := fmt.Sprintf("%s:", s.remoteName)
|
||||
s.fs, err = fs.NewFs(ctx, fsPath)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to instantiate Rclone filesystem: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(0).Infof("created backend storage rclone.%s for remote name %s", s.id, s.remoteName)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *RcloneBackendStorage) ToProperties() map[string]string {
|
||||
m := make(map[string]string)
|
||||
m["remote_name"] = s.remoteName
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *RcloneBackendStorage) NewStorageFile(key string, tierInfo *volume_server_pb.VolumeInfo) backend.BackendStorageFile {
|
||||
f := &RcloneBackendStorageFile{
|
||||
backendStorage: s,
|
||||
key: key,
|
||||
tierInfo: tierInfo,
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (s *RcloneBackendStorage) CopyFile(f *os.File, fn func(progressed int64, percentage float32) error) (key string, size int64, err error) {
|
||||
randomUuid, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return key, 0, err
|
||||
}
|
||||
key = randomUuid.String()
|
||||
|
||||
glog.V(1).Infof("copy dat file of %s to remote rclone.%s as %s", f.Name(), s.id, key)
|
||||
|
||||
util.Retry("upload via Rclone", func() error {
|
||||
size, err = uploadViaRclone(s.fs, f.Name(), key, fn)
|
||||
return err
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func uploadViaRclone(rfs fs.Fs, filename string, key string, fn func(progressed int64, percentage float32) error) (fileSize int64, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(file)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
info := object.NewStaticObjectInfo(key, stat.ModTime(), stat.Size(), true, nil, rfs)
|
||||
|
||||
tr := accounting.NewStats(ctx).NewTransfer(info)
|
||||
defer tr.Done(ctx, err)
|
||||
acc := tr.Account(ctx, file)
|
||||
pr := ProgressReader{acc: acc, tr: tr, fn: fn}
|
||||
|
||||
obj, err := rfs.Put(ctx, &pr, info)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return obj.Size(), err
|
||||
}
|
||||
|
||||
func (s *RcloneBackendStorage) DownloadFile(filename string, key string, fn func(progressed int64, percentage float32) error) (size int64, err error) {
|
||||
glog.V(1).Infof("download dat file of %s from remote rclone.%s as %s", filename, s.id, key)
|
||||
|
||||
util.Retry("download via Rclone", func() error {
|
||||
size, err = downloadViaRclone(s.fs, filename, key, fn)
|
||||
return err
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func downloadViaRclone(fs fs.Fs, filename string, key string, fn func(progressed int64, percentage float32) error) (fileSize int64, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
obj, err := fs.NewObject(ctx, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rc, err := obj.Open(ctx)
|
||||
defer func(rc io.ReadCloser) {
|
||||
err := rc.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(rc)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
file, err := os.Create(filename)
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(file)
|
||||
|
||||
tr := accounting.NewStats(ctx).NewTransfer(obj)
|
||||
defer tr.Done(ctx, err)
|
||||
acc := tr.Account(ctx, rc)
|
||||
pr := ProgressReader{acc: acc, tr: tr, fn: fn}
|
||||
|
||||
written, err := io.Copy(file, &pr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (s *RcloneBackendStorage) DeleteFile(key string) (err error) {
|
||||
glog.V(1).Infof("delete dat file %s from remote", key)
|
||||
|
||||
util.Retry("delete via Rclone", func() error {
|
||||
err = deleteViaRclone(s.fs, key)
|
||||
return err
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func deleteViaRclone(fs fs.Fs, key string) (err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
obj, err := fs.NewObject(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return obj.Remove(ctx)
|
||||
}
|
||||
|
||||
type RcloneBackendStorageFile struct {
|
||||
backendStorage *RcloneBackendStorage
|
||||
key string
|
||||
tierInfo *volume_server_pb.VolumeInfo
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
obj, err := rcloneBackendStorageFile.backendStorage.fs.NewObject(ctx, rcloneBackendStorageFile.key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
opt := fs.RangeOption{Start: off, End: off + int64(len(p)) - 1}
|
||||
|
||||
rc, err := obj.Open(ctx, &opt)
|
||||
defer func(rc io.ReadCloser) {
|
||||
err := rc.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(rc)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return io.ReadFull(rc, p)
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) Truncate(off int64) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) GetStat() (datSize int64, modTime time.Time, err error) {
|
||||
files := rcloneBackendStorageFile.tierInfo.GetFiles()
|
||||
|
||||
if len(files) == 0 {
|
||||
err = fmt.Errorf("remote file info not found")
|
||||
return
|
||||
}
|
||||
|
||||
datSize = int64(files[0].FileSize)
|
||||
modTime = time.Unix(int64(files[0].ModifiedTime), 0)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) Name() string {
|
||||
return rcloneBackendStorageFile.key
|
||||
}
|
||||
|
||||
func (rcloneBackendStorageFile RcloneBackendStorageFile) Sync() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package rclone_backend
|
||||
|
||||
import "github.com/rclone/rclone/fs/accounting"
|
||||
|
||||
type ProgressReader struct {
|
||||
acc *accounting.Account
|
||||
tr *accounting.Transfer
|
||||
fn func(progressed int64, percentage float32) error
|
||||
}
|
||||
|
||||
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
|
||||
n, err = pr.acc.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
snap := pr.tr.Snapshot()
|
||||
err = pr.fn(snap.Bytes, 100*float32(snap.Bytes)/float32(snap.Size))
|
||||
return
|
||||
}
|
||||
@@ -38,6 +38,22 @@ func (l *DiskLocation) DestroyEcVolume(vid needle.VolumeId) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DiskLocation) CollectEcShards(vid needle.VolumeId, shardFileNames []string) (ecVolume *erasure_coding.EcVolume, found bool) {
|
||||
l.ecVolumesLock.RLock()
|
||||
defer l.ecVolumesLock.RUnlock()
|
||||
|
||||
ecVolume, found = l.ecVolumes[vid]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
for _, ecShard := range ecVolume.Shards {
|
||||
if ecShard.ShardId < erasure_coding.ShardId(len(shardFileNames)) {
|
||||
shardFileNames[ecShard.ShardId] = erasure_coding.EcShardFileName(ecVolume.Collection, l.Directory, int(ecVolume.VolumeId)) + erasure_coding.ToExt(int(ecShard.ShardId))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *DiskLocation) FindEcShard(vid needle.VolumeId, shardId erasure_coding.ShardId) (*erasure_coding.EcVolumeShard, bool) {
|
||||
l.ecVolumesLock.RLock()
|
||||
defer l.ecVolumesLock.RUnlock()
|
||||
|
||||
@@ -151,7 +151,7 @@ func iterateEcjFile(baseFileName string, processNeedleFn func(key types.NeedleId
|
||||
}
|
||||
|
||||
// WriteDatFile generates .dat from from .ec00 ~ .ec09 files
|
||||
func WriteDatFile(baseFileName string, datFileSize int64) error {
|
||||
func WriteDatFile(baseFileName string, datFileSize int64, shardFileNames []string) error {
|
||||
|
||||
datFile, openErr := os.OpenFile(baseFileName+".dat", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if openErr != nil {
|
||||
@@ -161,13 +161,19 @@ func WriteDatFile(baseFileName string, datFileSize int64) error {
|
||||
|
||||
inputFiles := make([]*os.File, DataShardsCount)
|
||||
|
||||
defer func() {
|
||||
for shardId := 0; shardId < DataShardsCount; shardId++ {
|
||||
if inputFiles[shardId] != nil {
|
||||
inputFiles[shardId].Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for shardId := 0; shardId < DataShardsCount; shardId++ {
|
||||
shardFileName := baseFileName + ToExt(shardId)
|
||||
inputFiles[shardId], openErr = os.OpenFile(shardFileName, os.O_RDONLY, 0)
|
||||
inputFiles[shardId], openErr = os.OpenFile(shardFileNames[shardId], os.O_RDONLY, 0)
|
||||
if openErr != nil {
|
||||
return openErr
|
||||
}
|
||||
defer inputFiles[shardId].Close()
|
||||
}
|
||||
|
||||
for datFileSize >= DataShardsCount*ErasureCodingLargeBlockSize {
|
||||
|
||||
@@ -116,6 +116,17 @@ func (s *Store) FindEcVolume(vid needle.VolumeId) (*erasure_coding.EcVolume, boo
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// shardFiles is a list of shard files, which is used to return the shard locations
|
||||
func (s *Store) CollectEcShards(vid needle.VolumeId, shardFileNames []string) (ecVolume *erasure_coding.EcVolume, found bool) {
|
||||
for _, location := range s.Locations {
|
||||
if s, foundShards := location.CollectEcShards(vid, shardFileNames); foundShards {
|
||||
ecVolume = s
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) DestroyEcVolume(vid needle.VolumeId) {
|
||||
for _, location := range s.Locations {
|
||||
location.DestroyEcVolume(vid)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
||||
_ "github.com/seaweedfs/seaweedfs/weed/storage/backend/rclone_backend"
|
||||
_ "github.com/seaweedfs/seaweedfs/weed/storage/backend/s3_backend"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/backend"
|
||||
_ "github.com/seaweedfs/seaweedfs/weed/storage/backend/rclone_backend"
|
||||
_ "github.com/seaweedfs/seaweedfs/weed/storage/backend/s3_backend"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
||||
|
||||
@@ -2,11 +2,12 @@ package topology
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
@@ -119,10 +120,10 @@ func (t *Topology) batchVacuumVolumeCompact(grpcDialOption grpc.DialOption, vl *
|
||||
return isVacuumSuccess
|
||||
}
|
||||
|
||||
func (t *Topology) batchVacuumVolumeCommit(grpcDialOption grpc.DialOption, vl *VolumeLayout, vid needle.VolumeId, locationlist *VolumeLocationList) bool {
|
||||
func (t *Topology) batchVacuumVolumeCommit(grpcDialOption grpc.DialOption, vl *VolumeLayout, vid needle.VolumeId, vacuumLocationList, locationList *VolumeLocationList) bool {
|
||||
isCommitSuccess := true
|
||||
isReadOnly := false
|
||||
for _, dn := range locationlist.list {
|
||||
for _, dn := range vacuumLocationList.list {
|
||||
glog.V(0).Infoln("Start Committing vacuum", vid, "on", dn.Url())
|
||||
err := operation.WithVolumeServerClient(false, dn.ServerAddress(), grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
|
||||
resp, err := volumeServerClient.VacuumVolumeCommit(context.Background(), &volume_server_pb.VacuumVolumeCommitRequest{
|
||||
@@ -140,8 +141,38 @@ func (t *Topology) batchVacuumVolumeCommit(grpcDialOption grpc.DialOption, vl *V
|
||||
glog.V(0).Infof("Complete Committing vacuum %d on %s", vid, dn.Url())
|
||||
}
|
||||
}
|
||||
|
||||
//we should check the status of all replicas
|
||||
if len(locationList.list) > len(vacuumLocationList.list) {
|
||||
for _, dn := range locationList.list {
|
||||
isFound := false
|
||||
for _, dnVaccum := range vacuumLocationList.list {
|
||||
if dn.id == dnVaccum.id {
|
||||
isFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isFound {
|
||||
err := operation.WithVolumeServerClient(false, dn.ServerAddress(), grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
|
||||
resp, err := volumeServerClient.VolumeStatus(context.Background(), &volume_server_pb.VolumeStatusRequest{
|
||||
VolumeId: uint32(vid),
|
||||
})
|
||||
if resp != nil && resp.IsReadOnly {
|
||||
isReadOnly = true
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
glog.Errorf("Error when checking volume %d status on %s: %v", vid, dn.Url(), err)
|
||||
//we mark volume read-only, since the volume state is unknown
|
||||
isReadOnly = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCommitSuccess {
|
||||
for _, dn := range locationlist.list {
|
||||
for _, dn := range vacuumLocationList.list {
|
||||
vl.SetVolumeAvailable(dn, vid, isReadOnly)
|
||||
}
|
||||
}
|
||||
@@ -226,11 +257,11 @@ func (t *Topology) vacuumOneVolumeId(grpcDialOption grpc.DialOption, volumeLayou
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(2).Infof("check vacuum on collection:%s volume:%d", c.Name, vid)
|
||||
glog.V(1).Infof("check vacuum on collection:%s volume:%d", c.Name, vid)
|
||||
if vacuumLocationList, needVacuum := t.batchVacuumVolumeCheck(
|
||||
grpcDialOption, vid, locationList, garbageThreshold); needVacuum {
|
||||
if t.batchVacuumVolumeCompact(grpcDialOption, volumeLayout, vid, vacuumLocationList, preallocate) {
|
||||
t.batchVacuumVolumeCommit(grpcDialOption, volumeLayout, vid, vacuumLocationList)
|
||||
t.batchVacuumVolumeCommit(grpcDialOption, volumeLayout, vid, vacuumLocationList, locationList)
|
||||
} else {
|
||||
t.batchVacuumVolumeCleanup(grpcDialOption, volumeLayout, vid, vacuumLocationList)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
VERSION_NUMBER = fmt.Sprintf("%.02f", 3.45)
|
||||
VERSION_NUMBER = fmt.Sprintf("%.02f", 3.48)
|
||||
VERSION = sizeLimit + " " + VERSION_NUMBER
|
||||
COMMIT = ""
|
||||
)
|
||||
|
||||
@@ -1059,11 +1059,13 @@ func (f *FlagSet) ParseEnv(environ []string) error {
|
||||
}
|
||||
|
||||
envKey := strings.ToUpper(flag.Name)
|
||||
|
||||
if f.envPrefix != "" {
|
||||
envKey = f.envPrefix + "_" + envKey
|
||||
}
|
||||
envKey = strings.Replace(envKey, "-", "_", -1)
|
||||
|
||||
envKey = strings.Replace(envKey, ".", "_", -1)
|
||||
|
||||
value, isSet := env[envKey]
|
||||
if !isSet {
|
||||
continue
|
||||
|
||||
@@ -32,6 +32,29 @@ func Retry(name string, job func() error) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func MultiRetry(name string, errList []string, job func() error) (err error) {
|
||||
waitTime := time.Second
|
||||
hasErr := false
|
||||
for waitTime < RetryWaitTime {
|
||||
err = job()
|
||||
if err == nil {
|
||||
if hasErr {
|
||||
glog.V(0).Infof("retry %s successfully", name)
|
||||
}
|
||||
break
|
||||
}
|
||||
if containErr(err.Error(), errList) {
|
||||
hasErr = true
|
||||
glog.V(0).Infof("retry %s: err: %v", name, err)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
time.Sleep(waitTime)
|
||||
waitTime += waitTime / 2
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func RetryForever(name string, job func() error, onErrFn func(err error) (shouldContinue bool)) {
|
||||
waitTime := time.Second
|
||||
for {
|
||||
@@ -62,3 +85,12 @@ func Nvl(values ...string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func containErr(err string, errList []string) bool {
|
||||
for _, e := range errList {
|
||||
if strings.Contains(err, e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user