mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-01 20:26:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2ead72918 | ||
|
|
2e6cf541e7 | ||
|
|
6d2c979999 | ||
|
|
90df52d001 | ||
|
|
da7e5aaa65 | ||
|
|
2ffc87b86e | ||
|
|
7458ff9523 | ||
|
|
7a70caac3c | ||
|
|
561a2ff0bc | ||
|
|
aa020ee7e7 | ||
|
|
da87f6b265 | ||
|
|
0a7c5f85a9 | ||
|
|
515b7632d7 | ||
|
|
0a067944cc | ||
|
|
1b1d182469 | ||
|
|
090f85be4b | ||
|
|
75613b2cc7 | ||
|
|
0e016bc7bd | ||
|
|
8e48a235e2 | ||
|
|
1620de08ae | ||
|
|
a09cd28986 | ||
|
|
75c6edba9e | ||
|
|
9bf6c10505 | ||
|
|
2fea8cfa0f | ||
|
|
53bc1ea25b | ||
|
|
b5e2be635a | ||
|
|
c4a202ec41 | ||
|
|
32955b04b8 | ||
|
|
c3d1b3b5aa | ||
|
|
94e3757c08 | ||
|
|
64c48c9724 | ||
|
|
3be3635799 | ||
|
|
0ca9d89589 | ||
|
|
dc19bc2ace | ||
|
|
385a4dec30 | ||
|
|
da134a2eb7 | ||
|
|
beb3b8ddb0 | ||
|
|
4cf3176cbb | ||
|
|
a1009e8044 | ||
|
|
a0990b929d | ||
|
|
9cd26c6ca5 | ||
|
|
0ed5345954 | ||
|
|
97e3432dfe | ||
|
|
4f31c1bb94 | ||
|
|
6c4f32d173 | ||
|
|
90d785a15f | ||
|
|
fe46411cd4 | ||
|
|
c74bede730 | ||
|
|
adf8cb4000 | ||
|
|
0823bde8d8 | ||
|
|
8ab70ccf76 | ||
|
|
65cc6dc636 | ||
|
|
488c2680e8 | ||
|
|
f5a19a87be | ||
|
|
f30de7f827 | ||
|
|
6b4e9eef57 | ||
|
|
75629954d1 | ||
|
|
af8dffd66f | ||
|
|
60b4dedfb7 | ||
|
|
6912bf94ae | ||
|
|
41c0f3ad24 | ||
|
|
0d5683fb0e | ||
|
|
3269fd7eaf | ||
|
|
e605f1e001 | ||
|
|
23903aa95b | ||
|
|
4e6b316913 | ||
|
|
85411b8cc3 | ||
|
|
252e554e18 | ||
|
|
dbf46b3aa1 | ||
|
|
8c00c46f9b | ||
|
|
47f5ec0cde | ||
|
|
9fc07feb00 | ||
|
|
7af9415696 | ||
|
|
1b4237795a | ||
|
|
160d46dbcc | ||
|
|
8e3b798ea0 | ||
|
|
738c4fd203 | ||
|
|
e2076201d7 | ||
|
|
986cbdf7d9 | ||
|
|
99990a3c91 | ||
|
|
a37f069296 | ||
|
|
b789767cd1 | ||
|
|
8cb67952db | ||
|
|
45f902a9b7 | ||
|
|
4e58a4f24e | ||
|
|
200e56215a | ||
|
|
b0a4da7111 | ||
|
|
0154cca306 | ||
|
|
deb77e3b4a | ||
|
|
fc05963fa3 | ||
|
|
2ac31da89e | ||
|
|
3c7f9633eb | ||
|
|
3d47c38262 | ||
|
|
b7e3ca9172 | ||
|
|
021358749b | ||
|
|
c4459249ef | ||
|
|
38fc89d041 | ||
|
|
23280257df | ||
|
|
813453f3e1 | ||
|
|
16cd6fb278 | ||
|
|
5c465293e9 | ||
|
|
14910d035c | ||
|
|
f930c713fc | ||
|
|
316d1b4e69 | ||
|
|
03637d6f57 | ||
|
|
37075a414d | ||
|
|
ae655033ac | ||
|
|
d986c7196d | ||
|
|
3fedfec1e7 | ||
|
|
c2f18a10cb | ||
|
|
4b0c2a846b | ||
|
|
83078ac6ce | ||
|
|
26731694f8 | ||
|
|
765b3ef1be | ||
|
|
97c942b5ee | ||
|
|
02a160c3fd | ||
|
|
a9c619c270 | ||
|
|
4211601eab | ||
|
|
b52ae9cef8 | ||
|
|
8e78187a97 | ||
|
|
900d22c6ec | ||
|
|
06bb7bf6c0 | ||
|
|
fce8803087 | ||
|
|
606051cc79 | ||
|
|
ac22f1dd26 | ||
|
|
eed87791b7 | ||
|
|
01e2da5782 | ||
|
|
77286f8bea | ||
|
|
57578a6cd1 | ||
|
|
78f3ab439c | ||
|
|
9acda432fe | ||
|
|
ae5eb85a06 | ||
|
|
5931a2f53f | ||
|
|
4c72482536 | ||
|
|
46b91228af | ||
|
|
263eb29e9f | ||
|
|
d171d9f988 | ||
|
|
eadbba5c95 | ||
|
|
cc839f935d | ||
|
|
b219ccfe68 | ||
|
|
a83b8e7b01 | ||
|
|
5fb60b7135 | ||
|
|
4d2855476c | ||
|
|
3857f9c840 | ||
|
|
14699dfcef | ||
|
|
a3d4b50a49 | ||
|
|
003b6245e7 | ||
|
|
f3bb645018 | ||
|
|
dc0bc48257 | ||
|
|
45dba088cd | ||
|
|
a9efaa6385 | ||
|
|
04062c56c7 | ||
|
|
03620776ec | ||
|
|
005a6123e9 | ||
|
|
f4abd01adf | ||
|
|
a9c6be5fc3 | ||
|
|
141ce67c09 | ||
|
|
0e99531dbf | ||
|
|
965413c21b | ||
|
|
96c48bc8a8 | ||
|
|
c57a7839ec | ||
|
|
8438fb0cc3 | ||
|
|
536202e4e0 | ||
|
|
99c4e50d3d | ||
|
|
2c913dde04 | ||
|
|
9ac4935f22 | ||
|
|
85554bea38 | ||
|
|
6d30b21b10 | ||
|
|
3c229eb677 | ||
|
|
c0fb4a3766 | ||
|
|
6783ae5f8f | ||
|
|
983fd90010 | ||
|
|
97f706f35b | ||
|
|
b11449f955 | ||
|
|
0d345ac97d | ||
|
|
cc2bd97ad9 | ||
|
|
0da7ecfd29 | ||
|
|
effa00ed08 | ||
|
|
513bcd6e0d | ||
|
|
1ae108efca | ||
|
|
a102157020 | ||
|
|
4e55baf5b1 | ||
|
|
52c8f2fc9a | ||
|
|
a26f1b2040 | ||
|
|
6206737df2 | ||
|
|
b3adb221b2 | ||
|
|
ec51d77dcf | ||
|
|
eab53ea80d | ||
|
|
d3be2ceedd | ||
|
|
c7ebadc25d | ||
|
|
9aa990f80f | ||
|
|
e8296104fc | ||
|
|
92f906b6fc | ||
|
|
025bd8d447 | ||
|
|
5f3546a688 | ||
|
|
176950ea78 | ||
|
|
7385103057 | ||
|
|
27e73de797 | ||
|
|
da04bb3d1b | ||
|
|
290b5e2cd0 | ||
|
|
781585b195 | ||
|
|
8cb8cd4cc5 | ||
|
|
ec052f75d0 | ||
|
|
7c9e592c2d | ||
|
|
a7258f024e | ||
|
|
4737df597d | ||
|
|
aec071187e | ||
|
|
dc304342b2 | ||
|
|
b04375a3d1 | ||
|
|
9add554feb | ||
|
|
c13db8de80 | ||
|
|
6204dc6e85 | ||
|
|
ef3e068c51 | ||
|
|
5f19e81dab | ||
|
|
ed1ce3f299 | ||
|
|
218561c206 | ||
|
|
c0d279c54e | ||
|
|
83527a8f55 | ||
|
|
fe01059675 | ||
|
|
61d96fde01 | ||
|
|
af658ea970 | ||
|
|
98827d68d9 | ||
|
|
ee2fa14dbe | ||
|
|
2bd6fd3bbe | ||
|
|
71056dae07 | ||
|
|
0ea5c087ce | ||
|
|
95c0de285d | ||
|
|
500bcab953 | ||
|
|
590f02179d | ||
|
|
0a406f652e | ||
|
|
68043cfcac | ||
|
|
0fc9ffc603 | ||
|
|
7cfa616843 | ||
|
|
57c770a7b4 | ||
|
|
442e092995 | ||
|
|
b2a72630da | ||
|
|
824e96ffcc | ||
|
|
3e362451d2 | ||
|
|
0d5355c614 | ||
|
|
7d24980840 | ||
|
|
56d8e5b540 | ||
|
|
b3f22a53b0 | ||
|
|
a9990a1dc6 | ||
|
|
e0d5207ed9 | ||
|
|
a2962604ad | ||
|
|
559dfacdac | ||
|
|
e6333da65a | ||
|
|
7094492428 | ||
|
|
c6a0704019 | ||
|
|
ed3b0f81c6 | ||
|
|
c4f0fd6e1b | ||
|
|
ab966410d2 | ||
|
|
a4dfa00030 | ||
|
|
2251a8e472 | ||
|
|
5a16f17e47 | ||
|
|
e06676f007 | ||
|
|
c32b6f4d9d | ||
|
|
0fbc27b640 | ||
|
|
d7cc0498e0 | ||
|
|
827930e249 | ||
|
|
0880aff224 | ||
|
|
4300ca8142 | ||
|
|
dbba8cb57e | ||
|
|
aa30604cec | ||
|
|
1eec5c8d5d | ||
|
|
fc7baef5bb | ||
|
|
745ee8d8f3 | ||
|
|
a29abd8600 | ||
|
|
8708e7a64d | ||
|
|
c6bd244ebd | ||
|
|
b73ef6aa95 | ||
|
|
dc26012a3b | ||
|
|
81024761cd | ||
|
|
86cdb2a3e5 | ||
|
|
73f934d5de | ||
|
|
5b636b3242 | ||
|
|
a38efe2e7b | ||
|
|
546f1bcb90 | ||
|
|
e1190b3224 | ||
|
|
843865f2ca | ||
|
|
cf7a1c722f | ||
|
|
ab1105c524 | ||
|
|
2c40f56e5a | ||
|
|
6e63e5eaf0 | ||
|
|
62af2d961d | ||
|
|
5c2e409ffe | ||
|
|
a911375798 | ||
|
|
2ff727a32d | ||
|
|
b9a446839a | ||
|
|
411e49f964 | ||
|
|
ac162fc857 | ||
|
|
95bfec4931 |
@@ -9,17 +9,39 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin ]
|
||||
goarch: [amd64, arm]
|
||||
exclude:
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Delete old release assets
|
||||
uses: mknejp/delete-release-assets@v1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
tag: dev
|
||||
fail-if-no-assets: false
|
||||
assets: |
|
||||
weed-*
|
||||
|
||||
- name: Set BUILD_TIME env
|
||||
run: echo BUILD_TIME=$(date -u +%Y-%m-%d-%H-%M) >> ${GITHUB_ENV}
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@feature/customize-target-release
|
||||
uses: wangyoucao577/go-release-action@feature/asset-name
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: linux # default is
|
||||
goarch: amd64 # default is
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
@@ -27,15 +49,18 @@ jobs:
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed-large-disk
|
||||
asset_name: "weed-large-disk-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@feature/customize-target-release
|
||||
uses: wangyoucao577/go-release-action@feature/asset-name
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: linux # default is
|
||||
goarch: amd64 # default is
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
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 }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||

|
||||
|
||||
<h2 align="center">Supporting SeaweedFS</h2>
|
||||
<h2 align="center"><a href="https://www.patreon.com/seaweedfs">Sponsor SeaweedFS via Patreon</a></h2>
|
||||
|
||||
SeaweedFS is an independent Apache-licensed open source project with its ongoing development made
|
||||
possible entirely thanks to the support of these awesome [backers](https://github.com/chrislusf/seaweedfs/blob/master/backers.md).
|
||||
@@ -17,8 +17,6 @@ If you'd like to grow SeaweedFS even stronger, please consider joining our
|
||||
|
||||
Your support will be really appreciated by me and other supporters!
|
||||
|
||||
<h3 align="center"><a href="https://www.patreon.com/seaweedfs">Sponsor SeaweedFS via Patreon</a></h3>
|
||||
|
||||
<!--
|
||||
<h4 align="center">Platinum</h4>
|
||||
|
||||
@@ -27,24 +25,12 @@ Your support will be really appreciated by me and other supporters!
|
||||
Add your name or icon here
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h4 align="center">Gold</h4>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="" target="_blank">
|
||||
Add your name or icon here
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
-->
|
||||
|
||||
|
||||
### Gold Sponsors
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -58,11 +44,11 @@ Your support will be really appreciated by me and other supporters!
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
* [Quick Start](#quick-start)
|
||||
* [Introduction](#introduction)
|
||||
* [Features](#features)
|
||||
* [Additional Features](#additional-features)
|
||||
* [Filer Features](#filer-features)
|
||||
* [Quick Start](#quick-start)
|
||||
* [Example: Using Seaweed Object Store](#example-Using-Seaweed-Object-Store)
|
||||
* [Architecture](#architecture)
|
||||
* [Compared to Other File Systems](#compared-to-other-file-systems)
|
||||
@@ -76,6 +62,13 @@ Table of Contents
|
||||
* [Benchmark](#Benchmark)
|
||||
* [License](#license)
|
||||
|
||||
|
||||
## Quick Start ##
|
||||
* 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 `weed volume -dir="/some/data/dir2" -mserver="<master_host>:9333" -port=8081` locally or a different machine. That is it!
|
||||
|
||||
## Introduction ##
|
||||
|
||||
SeaweedFS is a simple and highly scalable distributed file system. There are two objectives:
|
||||
@@ -91,7 +84,7 @@ There is only 40 bytes of disk storage overhead for each file's metadata. It is
|
||||
|
||||
SeaweedFS started by implementing [Facebook's Haystack design paper](http://www.usenix.org/event/osdi10/tech/full_papers/Beaver.pdf). Also, SeaweedFS implements erasure coding with ideas from [f4: Facebook’s Warm BLOB Storage System](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-muralidhar.pdf)
|
||||
|
||||
On top of the object store, optional [Filer] can support directories and POSIX attributes. Filer is a separate linearly-scalable stateless server with customizable metadata stores, e.g., MySql, Postgres, Mongodb, Redis, Cassandra, Elastic Search, LevelDB, MemSql, TiDB, Etcd, CockroachDB, etc.
|
||||
On top of the object store, optional [Filer] can support directories and POSIX attributes. Filer is a separate linearly-scalable stateless server with customizable metadata stores, e.g., MySql, Postgres, Redis, Cassandra, HBase, Mongodb, Elastic Search, LevelDB, MemSql, TiDB, Etcd, CockroachDB, etc.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
@@ -114,15 +107,16 @@ On top of the object store, optional [Filer] can support directories and POSIX a
|
||||
|
||||
## Filer Features ##
|
||||
* [Filer server][Filer] provides "normal" directories and files via http.
|
||||
* [Super Large Files][SuperLargeFiles] stores large or super large files in tens of TB.
|
||||
* [File TTL][FilerTTL] automatically expires file metadata and actual file data.
|
||||
* [Mount filer][Mount] reads and writes files directly as a local directory via FUSE.
|
||||
* [Filer Store Replication][FilerStoreReplication] enables HA for filer meta data stores.
|
||||
* [Active-Active Replication][ActiveActiveAsyncReplication] enables asynchronous one-way or two-way cross cluster continuous replication.
|
||||
* [Amazon S3 compatible API][AmazonS3API] accesses files with S3 tooling.
|
||||
* [Hadoop Compatible File System][Hadoop] accesses files from Hadoop/Spark/Flink/etc or even runs HBase.
|
||||
* [Async Replication To Cloud][BackupToCloud] has extremely fast local access and backups to Amazon S3, Google Cloud Storage, Azure, BackBlaze.
|
||||
* [WebDAV] accesses as a mapped drive on Mac and Windows, or from mobile devices.
|
||||
* [AES256-GCM Encrypted Storage][FilerDataEncryption] safely stores the encrypted data.
|
||||
* [File TTL][FilerTTL] automatically purges file metadata and actual file data.
|
||||
* [Super Large Files][SuperLargeFiles] stores large or super large files in tens of TB.
|
||||
* [Kubernetes CSI Driver][SeaweedFsCsiDriver] A Container Storage Interface (CSI) Driver. [](https://hub.docker.com/r/chrislusf/seaweedfs-csi-driver/)
|
||||
|
||||
[Filer]: https://github.com/chrislusf/seaweedfs/wiki/Directories-and-Files
|
||||
@@ -139,15 +133,10 @@ On top of the object store, optional [Filer] can support directories and POSIX a
|
||||
[VolumeServerTTL]: https://github.com/chrislusf/seaweedfs/wiki/Store-file-with-a-Time-To-Live
|
||||
[SeaweedFsCsiDriver]: https://github.com/seaweedfs/seaweedfs-csi-driver
|
||||
[ActiveActiveAsyncReplication]: https://github.com/chrislusf/seaweedfs/wiki/Filer-Active-Active-cross-cluster-continuous-synchronization
|
||||
[FilerStoreReplication]: https://github.com/chrislusf/seaweedfs/wiki/Filer-Store-Replication
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
## Quick Start ##
|
||||
* 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 `weed volume -dir="/some/data/dir2" -mserver="<master_host>:9333" -port=8081` locally or a different machine. That is it!
|
||||
|
||||
## Example: Using Seaweed Object Store ##
|
||||
|
||||
By default, the master node runs on port 9333, and the volume nodes run on port 8080.
|
||||
@@ -376,7 +365,7 @@ The architectures are mostly the same. SeaweedFS aims to store and read files fa
|
||||
|
||||
* SeaweedFS optimizes for small files, ensuring O(1) disk seek operation, and can also handle large files.
|
||||
* SeaweedFS statically assigns a volume id for a file. Locating file content becomes just a lookup of the volume id, which can be easily cached.
|
||||
* SeaweedFS Filer metadata store can be any well-known and proven data stores, e.g., Cassandra, Mongodb, Redis, Elastic Search, MySql, Postgres, MemSql, TiDB, CockroachDB, Etcd etc, and is easy to customized.
|
||||
* SeaweedFS Filer metadata store can be any well-known and proven data stores, e.g., Redis, Cassandra, HBase, Mongodb, Elastic Search, MySql, Postgres, MemSql, TiDB, CockroachDB, Etcd etc, and is easy to customized.
|
||||
* SeaweedFS Volume server also communicates directly with clients via HTTP, supporting range queries, direct uploads, etc.
|
||||
|
||||
| System | File Metadata | File Content Read| POSIX | REST API | Optimized for large number of small files |
|
||||
@@ -418,7 +407,7 @@ Ceph uses CRUSH hashing to automatically manage the data placement. SeaweedFS pl
|
||||
|
||||
SeaweedFS is optimized for small files. Small files are stored as one continuous block of content, with at most 8 unused bytes between files. Small file access is O(1) disk read.
|
||||
|
||||
SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Redis, Elastic Search, Cassandra, MemSql, TiDB, CockroachCB, Etcd, to manage file directories. These stores are proven, scalable, and easier to manage.
|
||||
SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Redis, Elastic Search, Cassandra, HBase, MemSql, TiDB, CockroachCB, Etcd, to manage file directories. These stores are proven, scalable, and easier to manage.
|
||||
|
||||
| SeaweedFS | comparable to Ceph | advantage |
|
||||
| ------------- | ------------- | ---------------- |
|
||||
|
||||
@@ -12,6 +12,7 @@ RUN \
|
||||
SUPERCRONIC=supercronic-linux-$ARCH && \
|
||||
# Install SeaweedFS and Supercronic ( for cron job mode )
|
||||
apk add --no-cache --virtual build-dependencies --update wget curl ca-certificates && \
|
||||
apk add fuse && \
|
||||
wget -P /tmp https://github.com/$(curl -s -L https://github.com/chrislusf/seaweedfs/releases/latest | egrep -o "chrislusf/seaweedfs/releases/download/.*/linux_$ARCH.tar.gz") && \
|
||||
tar -C /usr/bin/ -xzvf /tmp/linux_$ARCH.tar.gz && \
|
||||
curl -fsSLO "$SUPERCRONIC_URL" && \
|
||||
|
||||
@@ -2,6 +2,8 @@ FROM frolvlad/alpine-glibc as builder
|
||||
RUN apk add git go g++
|
||||
RUN mkdir -p /go/src/github.com/chrislusf/
|
||||
RUN git clone https://github.com/chrislusf/seaweedfs /go/src/github.com/chrislusf/seaweedfs
|
||||
ARG BRANCH=${BRANCH:-master}
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs && git checkout $BRANCH
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs/weed \
|
||||
&& export LDFLAGS="-X github.com/chrislusf/seaweedfs/weed/util.COMMIT=$(git rev-parse --short HEAD)" \
|
||||
&& go install -ldflags "${LDFLAGS}"
|
||||
|
||||
@@ -2,6 +2,8 @@ FROM frolvlad/alpine-glibc as builder
|
||||
RUN apk add git go g++
|
||||
RUN mkdir -p /go/src/github.com/chrislusf/
|
||||
RUN git clone https://github.com/chrislusf/seaweedfs /go/src/github.com/chrislusf/seaweedfs
|
||||
ARG BRANCH=${BRANCH:-master}
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs && git checkout $BRANCH
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs/weed \
|
||||
&& export LDFLAGS="-X github.com/chrislusf/seaweedfs/weed/util.COMMIT=$(git rev-parse --short HEAD)" \
|
||||
&& go install -tags 5BytesOffset -ldflags "${LDFLAGS}"
|
||||
|
||||
@@ -12,6 +12,12 @@ build:
|
||||
dev: build
|
||||
docker-compose -f local-dev-compose.yml -p seaweedfs up
|
||||
|
||||
k8s: build
|
||||
docker-compose -f local-k8s-compose.yml -p seaweedfs up
|
||||
|
||||
dev_registry: build
|
||||
docker-compose -f local-registry-compose.yml -p seaweedfs up
|
||||
|
||||
cluster: build
|
||||
docker-compose -f local-cluster-compose.yml -p seaweedfs up
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume"
|
||||
depends_on:
|
||||
- master
|
||||
mysql:
|
||||
image: percona/percona-server:5.7
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
- ./seaweedfs.sql:/docker-entrypoint-initdb.d/seaweedfs.sql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=secret
|
||||
- MYSQL_DATABASE=seaweedfs
|
||||
- MYSQL_PASSWORD=secret
|
||||
- MYSQL_USER=seaweedfs
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
environment:
|
||||
- WEED_MYSQL_HOSTNAME=mysql
|
||||
- WEED_MYSQL_PORT=3306
|
||||
- WEED_MYSQL_DATABASE=seaweedfs
|
||||
- WEED_MYSQL_USERNAME=seaweedfs
|
||||
- WEED_MYSQL_PASSWORD=secret
|
||||
- WEED_MYSQL_ENABLED=true
|
||||
- WEED_LEVELDB2_ENABLED=false
|
||||
command: 'filer -master="master:9333"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- mysql
|
||||
ingress:
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- /tmp/nginx:/etc/nginx/conf.d
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: 's3 -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
environment:
|
||||
- VIRTUAL_HOST=s3
|
||||
- VIRTUAL_PORT=8333
|
||||
@@ -0,0 +1,91 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master -volumeSizeLimitMB=1024"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume -max=0 -preStopSeconds=1"
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: 'filer -master="master:9333"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: '-v 9 s3 -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- 9000:9000
|
||||
command: 'minio server /data'
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: "some_access_key1"
|
||||
MINIO_SECRET_KEY: "some_secret_key1"
|
||||
depends_on:
|
||||
- master
|
||||
registry1:
|
||||
image: registry:2
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "0.0.0.0:5001" # seaweedfs s3
|
||||
REGISTRY_LOG_LEVEL: "debug"
|
||||
REGISTRY_STORAGE: "s3"
|
||||
REGISTRY_STORAGE_S3_REGION: "us-east-1"
|
||||
REGISTRY_STORAGE_S3_REGIONENDPOINT: "http://s3:8333"
|
||||
REGISTRY_STORAGE_S3_BUCKET: "registry"
|
||||
REGISTRY_STORAGE_S3_ACCESSKEY: "some_access_key1"
|
||||
REGISTRY_STORAGE_S3_SECRETKEY: "some_secret_key1"
|
||||
REGISTRY_STORAGE_S3_V4AUTH: "true"
|
||||
REGISTRY_STORAGE_S3_SECURE: "false"
|
||||
REGISTRY_STORAGE_S3_SKIPVERIFY: "true"
|
||||
REGISTRY_STORAGE_S3_ROOTDIRECTORY: "/"
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: "true"
|
||||
REGISTRY_STORAGE_REDIRECT_DISABLE: "true"
|
||||
REGISTRY_VALIDATION_DISABLED: "true"
|
||||
ports:
|
||||
- 5001:5001
|
||||
depends_on:
|
||||
- s3
|
||||
- minio
|
||||
registry2:
|
||||
image: registry:2
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "0.0.0.0:5002" # minio
|
||||
REGISTRY_LOG_LEVEL: "debug"
|
||||
REGISTRY_STORAGE: "s3"
|
||||
REGISTRY_STORAGE_S3_REGION: "us-east-1"
|
||||
REGISTRY_STORAGE_S3_REGIONENDPOINT: "http://minio:9000"
|
||||
REGISTRY_STORAGE_S3_BUCKET: "registry"
|
||||
REGISTRY_STORAGE_S3_ACCESSKEY: "some_access_key1"
|
||||
REGISTRY_STORAGE_S3_SECRETKEY: "some_secret_key1"
|
||||
REGISTRY_STORAGE_S3_V4AUTH: "true"
|
||||
REGISTRY_STORAGE_S3_SECURE: "false"
|
||||
REGISTRY_STORAGE_S3_SKIPVERIFY: "true"
|
||||
REGISTRY_STORAGE_S3_ROOTDIRECTORY: "/"
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: "true"
|
||||
REGISTRY_STORAGE_REDIRECT_DISABLE: "true"
|
||||
REGISTRY_VALIDATION_DISABLED: "true"
|
||||
ports:
|
||||
- 5002:5002
|
||||
depends_on:
|
||||
- s3
|
||||
- minio
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE DATABASE IF NOT EXISTS seaweedfs;
|
||||
CREATE USER IF NOT EXISTS 'seaweedfs'@'%' IDENTIFIED BY 'secret';
|
||||
GRANT ALL PRIVILEGES ON seaweedfs_fast.* TO 'seaweedfs'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
USE seaweedfs;
|
||||
CREATE TABLE IF NOT EXISTS filemeta (
|
||||
dirhash BIGINT COMMENT 'first 64 bits of MD5 hash value of directory field',
|
||||
name VARCHAR(1000) COMMENT 'directory or file name',
|
||||
directory TEXT COMMENT 'full path to parent directory',
|
||||
meta LONGBLOB,
|
||||
PRIMARY KEY (dirhash, name)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
@@ -11,7 +11,7 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.33.5
|
||||
github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011
|
||||
github.com/chrislusf/raft v1.0.3
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
@@ -24,6 +24,7 @@ require (
|
||||
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
|
||||
github.com/fclairamb/ftpserverlib v0.8.0
|
||||
github.com/frankban/quicktest v1.7.2 // indirect
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis v6.15.7+incompatible
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gocql/gocql v0.0.0-20190829130954-e163eff7a8c6
|
||||
@@ -39,12 +40,13 @@ require (
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/karlseguin/ccache v2.0.3+incompatible
|
||||
github.com/karlseguin/expect v1.0.1 // indirect
|
||||
github.com/klauspost/compress v1.10.9
|
||||
github.com/klauspost/compress v1.10.9 // indirect
|
||||
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||
github.com/klauspost/crc32 v1.2.0
|
||||
github.com/klauspost/reedsolomon v1.9.2
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190805055040-f9202b1cfdeb // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
@@ -56,7 +58,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.0.7
|
||||
github.com/seaweedfs/fuse v1.0.8
|
||||
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
|
||||
@@ -66,7 +68,11 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tidwall/gjson v1.3.2
|
||||
github.com/tidwall/match v1.0.1
|
||||
github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/viant/assertly v0.5.4 // indirect
|
||||
github.com/viant/ptrie v0.3.0
|
||||
github.com/viant/toolbox v0.33.2 // indirect
|
||||
github.com/willf/bitset v1.1.10 // indirect
|
||||
github.com/willf/bloom v2.0.3+incompatible
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect
|
||||
|
||||
@@ -84,8 +84,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011 h1:vN1GvfLgDg8kIPCdhuVKAjlYpxG1B86jiKejB6MC/Q0=
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68=
|
||||
github.com/chrislusf/raft v1.0.3 h1:11YrnzJtVa5z7m9lhY2p8VcPHoUlC1UswyoAo+U1m1k=
|
||||
github.com/chrislusf/raft v1.0.3/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@@ -159,6 +159,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -177,6 +179,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-zookeeper/zk v1.0.2 h1:4mx0EYENAdX/B/rbunjlt5+4RTA/a9SMHBRuSKdGxPM=
|
||||
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
@@ -221,6 +225,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
@@ -386,6 +391,8 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -535,6 +542,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhD
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -545,6 +553,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seaweedfs/fuse v1.0.7 h1:tESMXhI3gXzN+dlWsCUrkIZDiWA4dZX18rQMoqmvazw=
|
||||
github.com/seaweedfs/fuse v1.0.7/go.mod h1:W7ubwr1l7KQsMeUpxFFOFOSxUL/ucTRMAlVYs4xdfQ8=
|
||||
github.com/seaweedfs/fuse v1.0.8 h1:HBPJTC77OlxwSd2JiTwvLPn8bWTElqQp3xs9vf3C15s=
|
||||
github.com/seaweedfs/fuse v1.0.8/go.mod h1:W7ubwr1l7KQsMeUpxFFOFOSxUL/ucTRMAlVYs4xdfQ8=
|
||||
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=
|
||||
@@ -556,6 +566,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -615,12 +627,20 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365 h1:6iRwZdrFUzbcVYZwa8dXTIILGIxmmhjyUPJEcwzPGaU=
|
||||
github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365/go.mod h1:zj0GJHGvyf1ed3Jm/Tb4830c/ZKDq+YoLsCt2rGQuT0=
|
||||
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/viant/assertly v0.5.4 h1:5Hh4U3pLZa6uhCFAGpYOxck/8l9TZczEzoHNfJAhHEQ=
|
||||
github.com/viant/assertly v0.5.4/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/ptrie v0.3.0 h1:SDaRd7Gqr1+ItCNz0GpTxRdK21nOfqjV6YtBm9jGlMY=
|
||||
github.com/viant/ptrie v0.3.0/go.mod h1:VguMnbGfz95Zw+V5VarYSqtqslDxJbOv++xLzxkMhec=
|
||||
github.com/viant/toolbox v0.33.2 h1:Av844IIeGz81gT672qZemyptGfbrcxqGymA5RFnIPjE=
|
||||
github.com/viant/toolbox v0.33.2/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA=
|
||||
@@ -773,6 +793,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfru
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@@ -918,8 +939,14 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o=
|
||||
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
version: 2.10
|
||||
version: 2.14
|
||||
@@ -4,7 +4,7 @@ global:
|
||||
registry: ""
|
||||
repository: ""
|
||||
imageName: chrislusf/seaweedfs
|
||||
imageTag: "2.10"
|
||||
imageTag: "2.18"
|
||||
imagePullPolicy: IfNotPresent
|
||||
imagePullSecrets: imagepullsecret
|
||||
restartPolicy: Always
|
||||
@@ -302,10 +302,6 @@ filer:
|
||||
WEED_FILER_OPTIONS_RECURSIVE_DELETE: "false"
|
||||
# directories under this folder will be automatically creating a separate bucket
|
||||
WEED_FILER_BUCKETS_FOLDER: "/buckets"
|
||||
# directories under this folder will be store message queue data
|
||||
WEED_FILER_QUEUES_FOLDER: "/queues"
|
||||
# WEED_FILER_OPTIONS_BUCKETS_FSYNC a list of buckets names with all write requests fsync=true
|
||||
WEED_FILER_OPTIONS_BUCKETS_FSYNC: []
|
||||
|
||||
s3:
|
||||
enabled: true
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.6</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
@@ -68,6 +68,11 @@
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<distributionManagement>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.6</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.6</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
@@ -68,6 +68,11 @@
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -15,7 +15,6 @@ public class ChunkCache {
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.weakValues()
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -275,9 +275,9 @@ public class FilerClient {
|
||||
try {
|
||||
FilerProto.CreateEntryResponse createEntryResponse =
|
||||
filerGrpcClient.getBlockingStub().createEntry(FilerProto.CreateEntryRequest.newBuilder()
|
||||
.setDirectory(parent)
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
.setDirectory(parent)
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
if (Strings.isNullOrEmpty(createEntryResponse.getError())) {
|
||||
return true;
|
||||
}
|
||||
@@ -333,4 +333,13 @@ public class FilerClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Iterator<FilerProto.SubscribeMetadataResponse> watch(String prefix, String clientName, long sinceNs) {
|
||||
return filerGrpcClient.getBlockingStub().subscribeMetadata(FilerProto.SubscribeMetadataRequest.newBuilder()
|
||||
.setPathPrefix(prefix)
|
||||
.setClientName(clientName)
|
||||
.setSinceNs(sinceNs)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
public class SeaweedRead {
|
||||
@@ -19,24 +20,36 @@ public class SeaweedRead {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedRead.class);
|
||||
|
||||
static ChunkCache chunkCache = new ChunkCache(4);
|
||||
static VolumeIdCache volumeIdCache = new VolumeIdCache(4 * 1024);
|
||||
|
||||
// returns bytesRead
|
||||
public static long read(FilerGrpcClient filerGrpcClient, List<VisibleInterval> visibleIntervals,
|
||||
final long position, final byte[] buffer, final int bufferOffset,
|
||||
final int bufferLength, final long fileSize) throws IOException {
|
||||
final long position, final ByteBuffer buf, final long fileSize) throws IOException {
|
||||
|
||||
List<ChunkView> chunkViews = viewFromVisibles(visibleIntervals, position, bufferLength);
|
||||
List<ChunkView> chunkViews = viewFromVisibles(visibleIntervals, position, buf.remaining());
|
||||
|
||||
Map<String, FilerProto.Locations> knownLocations = new HashMap<>();
|
||||
|
||||
FilerProto.LookupVolumeRequest.Builder lookupRequest = FilerProto.LookupVolumeRequest.newBuilder();
|
||||
for (ChunkView chunkView : chunkViews) {
|
||||
String vid = parseVolumeId(chunkView.fileId);
|
||||
lookupRequest.addVolumeIds(vid);
|
||||
FilerProto.Locations locations = volumeIdCache.getLocations(vid);
|
||||
if (locations == null) {
|
||||
lookupRequest.addVolumeIds(vid);
|
||||
} else {
|
||||
knownLocations.put(vid, locations);
|
||||
}
|
||||
}
|
||||
|
||||
FilerProto.LookupVolumeResponse lookupResponse = filerGrpcClient
|
||||
.getBlockingStub().lookupVolume(lookupRequest.build());
|
||||
|
||||
Map<String, FilerProto.Locations> vid2Locations = lookupResponse.getLocationsMapMap();
|
||||
if (lookupRequest.getVolumeIdsCount() > 0) {
|
||||
FilerProto.LookupVolumeResponse lookupResponse = filerGrpcClient
|
||||
.getBlockingStub().lookupVolume(lookupRequest.build());
|
||||
Map<String, FilerProto.Locations> vid2Locations = lookupResponse.getLocationsMapMap();
|
||||
for (Map.Entry<String, FilerProto.Locations> entry : vid2Locations.entrySet()) {
|
||||
volumeIdCache.setLocations(entry.getKey(), entry.getValue());
|
||||
knownLocations.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
//TODO parallel this
|
||||
long readCount = 0;
|
||||
@@ -46,18 +59,19 @@ public class SeaweedRead {
|
||||
if (startOffset < chunkView.logicOffset) {
|
||||
long gap = chunkView.logicOffset - startOffset;
|
||||
LOG.debug("zero [{},{})", startOffset, startOffset + gap);
|
||||
buf.position(buf.position()+ (int)gap);
|
||||
readCount += gap;
|
||||
startOffset += gap;
|
||||
}
|
||||
|
||||
FilerProto.Locations locations = vid2Locations.get(parseVolumeId(chunkView.fileId));
|
||||
FilerProto.Locations locations = knownLocations.get(parseVolumeId(chunkView.fileId));
|
||||
if (locations == null || locations.getLocationsCount() == 0) {
|
||||
LOG.error("failed to locate {}", chunkView.fileId);
|
||||
// log here!
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = readChunkView(startOffset, buffer, bufferOffset + readCount, chunkView, locations);
|
||||
int len = readChunkView(startOffset, buf, chunkView, locations);
|
||||
|
||||
LOG.debug("read [{},{}) {} size {}", startOffset, startOffset + len, chunkView.fileId, chunkView.size);
|
||||
|
||||
@@ -66,11 +80,12 @@ public class SeaweedRead {
|
||||
|
||||
}
|
||||
|
||||
long limit = Math.min(bufferOffset + bufferLength, fileSize);
|
||||
long limit = Math.min(buf.limit(), fileSize);
|
||||
|
||||
if (startOffset < limit) {
|
||||
long gap = limit - startOffset;
|
||||
LOG.debug("zero2 [{},{})", startOffset, startOffset + gap);
|
||||
buf.position(buf.position()+ (int)gap);
|
||||
readCount += gap;
|
||||
startOffset += gap;
|
||||
}
|
||||
@@ -78,7 +93,7 @@ public class SeaweedRead {
|
||||
return readCount;
|
||||
}
|
||||
|
||||
private static int readChunkView(long startOffset, byte[] buffer, long bufOffset, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
private static int readChunkView(long startOffset, ByteBuffer buf, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
|
||||
byte[] chunkData = chunkCache.getChunk(chunkView.fileId);
|
||||
|
||||
@@ -88,9 +103,9 @@ public class SeaweedRead {
|
||||
}
|
||||
|
||||
int len = (int) chunkView.size;
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} chunkView[{};{}) buf[{},{})/{} startOffset:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, chunkView.logicOffset, chunkView.logicOffset + chunkView.size, bufOffset, bufOffset + len, buffer.length, startOffset);
|
||||
System.arraycopy(chunkData, (int) (startOffset - chunkView.logicOffset + chunkView.offset), buffer, (int) bufOffset, len);
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} chunkView[{};{}) startOffset:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, chunkView.logicOffset, chunkView.logicOffset + chunkView.size, startOffset);
|
||||
buf.put(chunkData, (int) (startOffset - chunkView.logicOffset + chunkView.offset), len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package seaweedfs.client;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VolumeIdCache {
|
||||
|
||||
private Cache<String, FilerProto.Locations> cache = null;
|
||||
|
||||
public VolumeIdCache(int maxEntries) {
|
||||
if (maxEntries == 0) {
|
||||
return;
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public FilerProto.Locations getLocations(String volumeId) {
|
||||
if (this.cache == null) {
|
||||
return null;
|
||||
}
|
||||
return this.cache.getIfPresent(volumeId);
|
||||
}
|
||||
|
||||
public void setLocations(String volumeId, FilerProto.Locations locations) {
|
||||
if (this.cache == null) {
|
||||
return;
|
||||
}
|
||||
this.cache.put(volumeId, locations);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -100,6 +100,7 @@ message Entry {
|
||||
map<string, bytes> extended = 5;
|
||||
bytes hard_link_id = 7;
|
||||
int32 hard_link_counter = 8; // only exists in hard link meta data
|
||||
bytes content = 9; // if not empty, the file content
|
||||
}
|
||||
|
||||
message FullEntry {
|
||||
@@ -348,3 +349,23 @@ message KvPutRequest {
|
||||
message KvPutResponse {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
// path-based configurations
|
||||
message FilerConf {
|
||||
int32 version = 1;
|
||||
message PathConf {
|
||||
string location_prefix = 1;
|
||||
string collection = 2;
|
||||
string replication = 3;
|
||||
string ttl = 4;
|
||||
enum DiskType {
|
||||
NONE = 0;
|
||||
HDD = 1;
|
||||
SSD = 2;
|
||||
}
|
||||
DiskType disk_type = 5;
|
||||
bool fsync = 6;
|
||||
uint32 volume_growth_count = 7;
|
||||
}
|
||||
repeated PathConf locations = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>unzip</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-hadoop2-client</artifactId>
|
||||
<version>1.5.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-common</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import seaweed.hdfs.SeaweedInputStream;
|
||||
import seaweedfs.client.FilerClient;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class UnzipFile {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
FilerGrpcClient filerGrpcClient = new FilerGrpcClient("localhost", 18888);
|
||||
FilerClient filerClient = new FilerClient(filerGrpcClient);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
parseZip("/Users/chris/tmp/test.zip");
|
||||
|
||||
long startTime2 = System.currentTimeMillis();
|
||||
|
||||
long localProcessTime = startTime2 - startTime;
|
||||
|
||||
SeaweedInputStream seaweedInputStream = new SeaweedInputStream(
|
||||
filerGrpcClient,
|
||||
new org.apache.hadoop.fs.FileSystem.Statistics(""),
|
||||
"/",
|
||||
filerClient.lookupEntry("/", "test.zip")
|
||||
);
|
||||
parseZip(seaweedInputStream);
|
||||
|
||||
long swProcessTime = System.currentTimeMillis() - startTime2;
|
||||
|
||||
System.out.println("Local time: " + localProcessTime);
|
||||
System.out.println("SeaweedFS time: " + swProcessTime);
|
||||
|
||||
}
|
||||
|
||||
public static void parseZip(String filename) throws IOException {
|
||||
FileInputStream fileInputStream = new FileInputStream(filename);
|
||||
parseZip(fileInputStream);
|
||||
}
|
||||
|
||||
public static void parseZip(InputStream is) throws IOException {
|
||||
ZipInputStream zin = new ZipInputStream(is);
|
||||
ZipEntry ze;
|
||||
while ((ze = zin.getNextEntry()) != null) {
|
||||
System.out.println(ze.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import seaweedfs.client.FilerClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class WatchFiles {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
FilerClient filerClient = new FilerClient("localhost", 18888);
|
||||
|
||||
long sinceNs = (System.currentTimeMillis() - 3600 * 1000) * 1000000L;
|
||||
|
||||
Iterator<FilerProto.SubscribeMetadataResponse> watch = filerClient.watch(
|
||||
"/buckets",
|
||||
"exampleClientName",
|
||||
sinceNs
|
||||
);
|
||||
|
||||
System.out.println("Connected to filer, subscribing from " + new Date());
|
||||
|
||||
while (watch.hasNext()) {
|
||||
FilerProto.SubscribeMetadataResponse event = watch.next();
|
||||
FilerProto.EventNotification notification = event.getEventNotification();
|
||||
if (!event.getDirectory().equals(notification.getNewParentPath())) {
|
||||
// move an entry to a new directory, possibly with a new name
|
||||
if (notification.hasOldEntry() && notification.hasNewEntry()) {
|
||||
System.out.println("moved " + event.getDirectory() + "/" + notification.getOldEntry().getName() + " to " + notification.getNewParentPath() + "/" + notification.getNewEntry().getName());
|
||||
} else {
|
||||
System.out.println("this should not happen.");
|
||||
}
|
||||
} else if (notification.hasNewEntry() && !notification.hasOldEntry()) {
|
||||
System.out.println("created entry " + event.getDirectory() + "/" + notification.getNewEntry().getName());
|
||||
} else if (!notification.hasNewEntry() && notification.hasOldEntry()) {
|
||||
System.out.println("deleted entry " + event.getDirectory() + "/" + notification.getOldEntry().getName());
|
||||
} else if (notification.hasNewEntry() && notification.hasOldEntry()) {
|
||||
System.out.println("updated entry " + event.getDirectory() + "/" + notification.getNewEntry().getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -301,7 +301,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.3</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.5.6</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.3</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.5.6</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
import org.apache.hadoop.fs.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BufferedByteBufferReadableInputStream extends BufferedFSInputStream implements ByteBufferReadable {
|
||||
|
||||
public BufferedByteBufferReadableInputStream(FSInputStream in, int size) {
|
||||
super(in, size);
|
||||
if (!(in instanceof Seekable) || !(in instanceof PositionedReadable)) {
|
||||
throw new IllegalArgumentException("In is not an instance of Seekable or PositionedReadable");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer buf) throws IOException {
|
||||
if (this.in instanceof ByteBufferReadable) {
|
||||
return ((ByteBufferReadable)this.in).read(buf);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Byte-buffer read unsupported by input stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
public static final String FS_SEAWEED_FILER_PORT = "fs.seaweed.filer.port";
|
||||
public static final int FS_SEAWEED_DEFAULT_PORT = 8888;
|
||||
public static final String FS_SEAWEED_BUFFER_SIZE = "fs.seaweed.buffer.size";
|
||||
public static final String FS_SEAWEED_REPLICATION = "fs.seaweed.replication";
|
||||
public static final int FS_SEAWEED_DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystem.class);
|
||||
@@ -67,8 +68,8 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.seaweedFileSystemStore.close();
|
||||
super.close();
|
||||
this.seaweedFileSystemStore.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,7 +82,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
return new FSDataInputStream(new BufferedByteBufferReadableInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
return null;
|
||||
@@ -97,7 +98,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
path = qualify(path);
|
||||
|
||||
try {
|
||||
String replicaPlacement = String.format("%03d", replication - 1);
|
||||
String replicaPlacement = this.getConf().get(FS_SEAWEED_REPLICATION, String.format("%03d", replication - 1));
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
OutputStream outputStream = seaweedFileSystemStore.createFile(path, overwrite, permission, seaweedBufferSize, replicaPlacement);
|
||||
return new FSDataOutputStream(outputStream, statistics);
|
||||
|
||||
@@ -193,6 +193,7 @@ public class SeaweedFileSystemStore {
|
||||
if (existingEntry != null) {
|
||||
entry = FilerProto.Entry.newBuilder();
|
||||
entry.mergeFrom(existingEntry);
|
||||
entry.clearContent();
|
||||
entry.getAttributesBuilder().setMtime(now);
|
||||
LOG.debug("createFile merged entry path:{} entry:{} from:{}", path, entry, existingEntry);
|
||||
writePosition = SeaweedRead.fileSize(existingEntry);
|
||||
|
||||
@@ -2,6 +2,7 @@ package seaweed.hdfs;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.ByteBufferReadable;
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
@@ -13,9 +14,10 @@ import seaweedfs.client.SeaweedRead;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public class SeaweedInputStream extends FSInputStream {
|
||||
public class SeaweedInputStream extends FSInputStream implements ByteBufferReadable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedInputStream.class);
|
||||
|
||||
@@ -63,14 +65,8 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
if (b == null) {
|
||||
throw new IllegalArgumentException("null byte array passed in to read() method");
|
||||
}
|
||||
@@ -84,7 +80,31 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
|
||||
}
|
||||
|
||||
long bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, b, off, len, SeaweedRead.fileSize(entry));
|
||||
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
|
||||
return read(buf);
|
||||
|
||||
}
|
||||
|
||||
// implement ByteBufferReadable
|
||||
@Override
|
||||
public synchronized int read(ByteBuffer buf) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
|
||||
long bytesRead = 0;
|
||||
int len = buf.remaining();
|
||||
int start = (int) this.position;
|
||||
if (start+len <= entry.getContent().size()) {
|
||||
entry.getContent().substring(start, start+len).copyTo(buf);
|
||||
} else {
|
||||
bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, buf, SeaweedRead.fileSize(entry));
|
||||
}
|
||||
|
||||
if (bytesRead > Integer.MAX_VALUE) {
|
||||
throw new IOException("Unexpected Content-Length");
|
||||
}
|
||||
@@ -97,7 +117,6 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
}
|
||||
|
||||
return (int) bytesRead;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.3</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.5.6</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.3</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.5.6</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
import org.apache.hadoop.fs.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BufferedByteBufferReadableInputStream extends BufferedFSInputStream implements ByteBufferReadable {
|
||||
|
||||
public BufferedByteBufferReadableInputStream(FSInputStream in, int size) {
|
||||
super(in, size);
|
||||
if (!(in instanceof Seekable) || !(in instanceof PositionedReadable)) {
|
||||
throw new IllegalArgumentException("In is not an instance of Seekable or PositionedReadable");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer buf) throws IOException {
|
||||
if (this.in instanceof ByteBufferReadable) {
|
||||
return ((ByteBufferReadable)this.in).read(buf);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Byte-buffer read unsupported by input stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
public static final String FS_SEAWEED_FILER_PORT = "fs.seaweed.filer.port";
|
||||
public static final int FS_SEAWEED_DEFAULT_PORT = 8888;
|
||||
public static final String FS_SEAWEED_BUFFER_SIZE = "fs.seaweed.buffer.size";
|
||||
public static final String FS_SEAWEED_REPLICATION = "fs.seaweed.replication";
|
||||
public static final int FS_SEAWEED_DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystem.class);
|
||||
@@ -67,8 +68,8 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.seaweedFileSystemStore.close();
|
||||
super.close();
|
||||
this.seaweedFileSystemStore.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,7 +82,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
return new FSDataInputStream(new BufferedByteBufferReadableInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
return null;
|
||||
@@ -97,7 +98,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
path = qualify(path);
|
||||
|
||||
try {
|
||||
String replicaPlacement = String.format("%03d", replication - 1);
|
||||
String replicaPlacement = this.getConf().get(FS_SEAWEED_REPLICATION, String.format("%03d", replication - 1));
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
OutputStream outputStream = seaweedFileSystemStore.createFile(path, overwrite, permission, seaweedBufferSize, replicaPlacement);
|
||||
return new FSDataOutputStream(outputStream, statistics);
|
||||
|
||||
@@ -193,6 +193,7 @@ public class SeaweedFileSystemStore {
|
||||
if (existingEntry != null) {
|
||||
entry = FilerProto.Entry.newBuilder();
|
||||
entry.mergeFrom(existingEntry);
|
||||
entry.clearContent();
|
||||
entry.getAttributesBuilder().setMtime(now);
|
||||
LOG.debug("createFile merged entry path:{} entry:{} from:{}", path, entry, existingEntry);
|
||||
writePosition = SeaweedRead.fileSize(existingEntry);
|
||||
|
||||
@@ -2,6 +2,7 @@ package seaweed.hdfs;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.ByteBufferReadable;
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
@@ -13,9 +14,10 @@ import seaweedfs.client.SeaweedRead;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public class SeaweedInputStream extends FSInputStream {
|
||||
public class SeaweedInputStream extends FSInputStream implements ByteBufferReadable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedInputStream.class);
|
||||
|
||||
@@ -63,14 +65,8 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
if (b == null) {
|
||||
throw new IllegalArgumentException("null byte array passed in to read() method");
|
||||
}
|
||||
@@ -84,7 +80,31 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
|
||||
}
|
||||
|
||||
long bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, b, off, len, SeaweedRead.fileSize(entry));
|
||||
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
|
||||
return read(buf);
|
||||
|
||||
}
|
||||
|
||||
// implement ByteBufferReadable
|
||||
@Override
|
||||
public synchronized int read(ByteBuffer buf) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
|
||||
long bytesRead = 0;
|
||||
int len = buf.remaining();
|
||||
int start = (int) this.position;
|
||||
if (start+len <= entry.getContent().size()) {
|
||||
entry.getContent().substring(start, start+len).copyTo(buf);
|
||||
} else {
|
||||
bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, buf, SeaweedRead.fileSize(entry));
|
||||
}
|
||||
|
||||
if (bytesRead > Integer.MAX_VALUE) {
|
||||
throw new IOException("Unexpected Content-Length");
|
||||
}
|
||||
@@ -97,7 +117,6 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
}
|
||||
|
||||
return (int) bytesRead;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -386,7 +386,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_filer_request_total[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_filer_request_total[1m])",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "{{type}}",
|
||||
@@ -761,7 +761,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_s3_request_total[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_s3_request_total[1m])",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "{{type}}",
|
||||
@@ -1853,4 +1853,4 @@
|
||||
"timezone": "browser",
|
||||
"title": "SeaweedFS",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\",type=~\"$method\"}[1m]) * 5) by (code)",
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\",type=~\"$method\"}[1m])) by (code)",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -210,7 +210,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m]) * 5) by (type)",
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m])) by (type)",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -1014,7 +1014,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_filer_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_filer_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m])",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -2359,4 +2359,4 @@
|
||||
"timezone": "browser",
|
||||
"title": "SeaweedFS",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
hsbench -a accesstoken -s secret -z 4K -d 10 -t 10 -b 10 -u http://localhost:8333 -m "cxipgdx" -bp "hsbench-"
|
||||
+5
-1
@@ -20,7 +20,7 @@ debug_mount:
|
||||
|
||||
debug_server:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- server -dir=/Volumes/mobile_disk/99 -filer -volume.port=8343 -s3 -volume.max=0
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- server -dir=/Volumes/mobile_disk/99 -filer -volume.port=8343 -s3 -volume.max=0 -master.volumeSizeLimitMB=1024 -volume.preStopSeconds=1
|
||||
|
||||
debug_volume:
|
||||
go build -gcflags="all=-N -l"
|
||||
@@ -29,3 +29,7 @@ debug_volume:
|
||||
debug_webdav:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- -v=4 webdav
|
||||
|
||||
debug_s3:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- -v=4 s3
|
||||
|
||||
@@ -112,7 +112,7 @@ func runBackup(cmd *Command, args []string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
v, err := storage.NewVolume(util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*s.dir), util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating or reading from volume %d: %v\n", vid, err)
|
||||
return true
|
||||
@@ -137,7 +137,7 @@ func runBackup(cmd *Command, args []string) bool {
|
||||
// remove the old data
|
||||
v.Destroy()
|
||||
// recreate an empty volume
|
||||
v, err = storage.NewVolume(util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
v, err = storage.NewVolume(util.ResolvePath(*s.dir), util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating or reading from volume %d: %v\n", vid, err)
|
||||
return true
|
||||
|
||||
@@ -125,7 +125,7 @@ func runBenchmark(cmd *Command, args []string) bool {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
b.masterClient = wdclient.NewMasterClient(b.grpcDialOption, "client", "", 0, strings.Split(*b.masters, ","))
|
||||
b.masterClient = wdclient.NewMasterClient(b.grpcDialOption, "client", "", 0, "", strings.Split(*b.masters, ","))
|
||||
go b.masterClient.KeepConnectedToMaster()
|
||||
b.masterClient.WaitUntilConnected()
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@ func runCompact(cmd *Command, args []string) bool {
|
||||
preallocate := *compactVolumePreallocate * (1 << 20)
|
||||
|
||||
vid := needle.VolumeId(*compactVolumeId)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*compactVolumePath), *compactVolumeCollection, vid,
|
||||
storage.NeedleMapInMemory, nil, nil, preallocate, 0)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*compactVolumePath), util.ResolvePath(*compactVolumePath), *compactVolumeCollection, vid, storage.NeedleMapInMemory, nil, nil, preallocate, 0)
|
||||
if err != nil {
|
||||
glog.Fatalf("Load Volume [ERROR] %s\n", err)
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ type FilerOptions struct {
|
||||
cipher *bool
|
||||
peers *string
|
||||
metricsHttpPort *int
|
||||
|
||||
// default leveldb directory, used in "weed server" mode
|
||||
cacheToFilerLimit *int
|
||||
defaultLevelDbDirectory *string
|
||||
}
|
||||
|
||||
@@ -59,12 +58,14 @@ func init() {
|
||||
f.disableDirListing = cmdFiler.Flag.Bool("disableDirListing", false, "turn off directory listing")
|
||||
f.maxMB = cmdFiler.Flag.Int("maxMB", 32, "split files larger than the limit")
|
||||
f.dirListingLimit = cmdFiler.Flag.Int("dirListLimit", 100000, "limit sub dir listing size")
|
||||
f.dataCenter = cmdFiler.Flag.String("dataCenter", "", "prefer to write to volumes in this data center")
|
||||
f.dataCenter = cmdFiler.Flag.String("dataCenter", "", "prefer to read and write to volumes in this data center")
|
||||
f.rack = cmdFiler.Flag.String("rack", "", "prefer to write to volumes in this rack")
|
||||
f.disableHttp = cmdFiler.Flag.Bool("disableHttp", false, "disable http request, only gRpc operations are allowed")
|
||||
f.cipher = cmdFiler.Flag.Bool("encryptVolumeData", false, "encrypt data on volume servers")
|
||||
f.peers = cmdFiler.Flag.String("peers", "", "all filers sharing the same filer store in comma separated ip:port list")
|
||||
f.metricsHttpPort = cmdFiler.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
f.cacheToFilerLimit = cmdFiler.Flag.Int("cacheToFilerLimit", 0, "Small files smaller than this limit can be cached in filer store.")
|
||||
f.defaultLevelDbDirectory = cmdFiler.Flag.String("defaultStoreDir", ".", "if filer.toml is empty, use an embedded filer store in the directory")
|
||||
|
||||
// start s3 on filer
|
||||
filerStartS3 = cmdFiler.Flag.Bool("s3", false, "whether to start S3 gateway")
|
||||
@@ -73,6 +74,7 @@ func init() {
|
||||
filerS3Options.tlsPrivateKey = cmdFiler.Flag.String("s3.key.file", "", "path to the TLS private key file")
|
||||
filerS3Options.tlsCertificate = cmdFiler.Flag.String("s3.cert.file", "", "path to the TLS certificate file")
|
||||
filerS3Options.config = cmdFiler.Flag.String("s3.config", "", "path to the config file")
|
||||
filerS3Options.allowEmptyFolder = cmdFiler.Flag.Bool("s3.allowEmptyFolder", false, "allow empty folders")
|
||||
}
|
||||
|
||||
var cmdFiler = &Command{
|
||||
@@ -89,7 +91,8 @@ var cmdFiler = &Command{
|
||||
//return a json format subdirectory and files listing
|
||||
GET /path/to/
|
||||
|
||||
The configuration file "filer.toml" is read from ".", "$HOME/.seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
The configuration file "filer.toml" is read from ".", "$HOME/.seaweedfs/", "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
If the "filer.toml" is not found, an embedded filer store will be craeted under "-defaultStoreDir".
|
||||
|
||||
The example filer.toml configuration file can be generated by "weed scaffold -config=filer"
|
||||
|
||||
@@ -125,10 +128,7 @@ func (fo *FilerOptions) startFiler() {
|
||||
publicVolumeMux = http.NewServeMux()
|
||||
}
|
||||
|
||||
defaultLevelDbDirectory := "./filerldb2"
|
||||
if fo.defaultLevelDbDirectory != nil {
|
||||
defaultLevelDbDirectory = util.ResolvePath(*fo.defaultLevelDbDirectory + "/filerldb2")
|
||||
}
|
||||
defaultLevelDbDirectory := util.ResolvePath(*fo.defaultLevelDbDirectory + "/filerldb2")
|
||||
|
||||
var peers []string
|
||||
if *fo.peers != "" {
|
||||
@@ -149,6 +149,7 @@ func (fo *FilerOptions) startFiler() {
|
||||
Host: *fo.ip,
|
||||
Port: uint32(*fo.port),
|
||||
Cipher: *fo.cipher,
|
||||
CacheToFilerLimit: int64(*fo.cacheToFilerLimit),
|
||||
Filers: peers,
|
||||
})
|
||||
if nfs_err != nil {
|
||||
|
||||
@@ -122,7 +122,7 @@ func runCopy(cmd *Command, args []string) bool {
|
||||
expectedBucket := restPath[:strings.Index(restPath, "/")]
|
||||
if *copy.collection == "" {
|
||||
*copy.collection = expectedBucket
|
||||
} else {
|
||||
} else if *copy.collection != expectedBucket {
|
||||
fmt.Printf("destination %s uses collection \"%s\": unexpected collection \"%v\"\n", urlPath, expectedBucket, *copy.collection)
|
||||
return true
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func runCopy(cmd *Command, args []string) bool {
|
||||
defer close(fileCopyTaskChan)
|
||||
for _, fileOrDir := range fileOrDirs {
|
||||
if err := genFileCopyTask(fileOrDir, urlPath, fileCopyTaskChan); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gen file list error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "genFileCopyTask : %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi
|
||||
|
||||
fi, err := os.Stat(fileOrDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get stat for file %s: %v\n", fileOrDir, err)
|
||||
fmt.Fprintf(os.Stderr, "Error: read file %s: %v\n", fileOrDir, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ var cmdMaster = &Command{
|
||||
Short: "start a master server",
|
||||
Long: `start a master server to provide volume=>location mapping service and sequence number of file ids
|
||||
|
||||
The configuration file "security.toml" is read from ".", "$HOME/.seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
The configuration file "security.toml" is read from ".", "$HOME/.seaweedfs/", "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
|
||||
The example security.toml configuration file can be generated by "weed scaffold -config=security"
|
||||
|
||||
|
||||
+10
-7
@@ -23,13 +23,14 @@ var (
|
||||
)
|
||||
|
||||
type S3Options struct {
|
||||
filer *string
|
||||
port *int
|
||||
config *string
|
||||
domainName *string
|
||||
tlsPrivateKey *string
|
||||
tlsCertificate *string
|
||||
metricsHttpPort *int
|
||||
filer *string
|
||||
port *int
|
||||
config *string
|
||||
domainName *string
|
||||
tlsPrivateKey *string
|
||||
tlsCertificate *string
|
||||
metricsHttpPort *int
|
||||
allowEmptyFolder *bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -41,6 +42,7 @@ func init() {
|
||||
s3StandaloneOptions.tlsPrivateKey = cmdS3.Flag.String("key.file", "", "path to the TLS private key file")
|
||||
s3StandaloneOptions.tlsCertificate = cmdS3.Flag.String("cert.file", "", "path to the TLS certificate file")
|
||||
s3StandaloneOptions.metricsHttpPort = cmdS3.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
s3StandaloneOptions.allowEmptyFolder = cmdS3.Flag.Bool("allowEmptyFolder", false, "allow empty folders")
|
||||
}
|
||||
|
||||
var cmdS3 = &Command{
|
||||
@@ -181,6 +183,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
DomainName: *s3opt.domainName,
|
||||
BucketsPath: filerBucketsPath,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
AllowEmptyFolder: *s3opt.allowEmptyFolder,
|
||||
})
|
||||
if s3ApiServer_err != nil {
|
||||
glog.Fatalf("S3 API Server startup error: %v", s3ApiServer_err)
|
||||
|
||||
@@ -76,10 +76,6 @@ const (
|
||||
recursive_delete = false
|
||||
# directories under this folder will be automatically creating a separate bucket
|
||||
buckets_folder = "/buckets"
|
||||
buckets_fsync = [ # a list of buckets with all write requests fsync=true
|
||||
"important_bucket",
|
||||
"should_always_fsync",
|
||||
]
|
||||
|
||||
####################################################
|
||||
# The following are filer store options
|
||||
@@ -142,12 +138,21 @@ hosts=[
|
||||
]
|
||||
username=""
|
||||
password=""
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[hbase]
|
||||
enabled = false
|
||||
zkquorum = ""
|
||||
table = "seaweedfs"
|
||||
|
||||
[redis2]
|
||||
enabled = false
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 0
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[redis_cluster2]
|
||||
enabled = false
|
||||
@@ -164,6 +169,8 @@ password = ""
|
||||
readOnly = true
|
||||
# automatically use the closest Redis server for reads
|
||||
routeByLatency = true
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[etcd]
|
||||
enabled = false
|
||||
@@ -189,6 +196,28 @@ sniff_enabled = false
|
||||
healthcheck_enabled = false
|
||||
# increase the value is recommend, be sure the value in Elastic is greater or equal here
|
||||
index.max_result_window = 10000
|
||||
|
||||
|
||||
|
||||
##########################
|
||||
##########################
|
||||
# To add path-specific filer store:
|
||||
#
|
||||
# 1. Add a name following the store type separated by a dot ".". E.g., cassandra.tmp
|
||||
# 2. Add a location configuraiton. E.g., location = "/tmp/"
|
||||
# 3. Copy and customize all other configurations.
|
||||
# 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
|
||||
##########################
|
||||
[redis2.tmp]
|
||||
enabled = false
|
||||
location = "/tmp/"
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 1
|
||||
|
||||
`
|
||||
|
||||
NOTIFICATION_TOML_EXAMPLE = `
|
||||
|
||||
@@ -94,6 +94,7 @@ func init() {
|
||||
filerOptions.dirListingLimit = cmdServer.Flag.Int("filer.dirListLimit", 1000, "limit sub dir listing size")
|
||||
filerOptions.cipher = cmdServer.Flag.Bool("filer.encryptVolumeData", false, "encrypt data on volume servers")
|
||||
filerOptions.peers = cmdServer.Flag.String("filer.peers", "", "all filers sharing the same filer store in comma separated ip:port list")
|
||||
filerOptions.cacheToFilerLimit = cmdServer.Flag.Int("filer.cacheToFilerLimit", 0, "Small files smaller than this limit can be cached in filer store.")
|
||||
|
||||
serverOptions.v.port = cmdServer.Flag.Int("volume.port", 8080, "volume server http listen port")
|
||||
serverOptions.v.publicPort = cmdServer.Flag.Int("volume.port.public", 0, "volume server public port")
|
||||
@@ -105,12 +106,14 @@ func init() {
|
||||
serverOptions.v.publicUrl = cmdServer.Flag.String("volume.publicUrl", "", "publicly accessible address")
|
||||
serverOptions.v.preStopSeconds = cmdServer.Flag.Int("volume.preStopSeconds", 10, "number of seconds between stop send heartbeats and stop volume server")
|
||||
serverOptions.v.pprof = cmdServer.Flag.Bool("volume.pprof", false, "enable pprof http handlers. precludes --memprofile and --cpuprofile")
|
||||
serverOptions.v.idxFolder = cmdServer.Flag.String("volume.dir.idx", "", "directory to store .idx files")
|
||||
|
||||
s3Options.port = cmdServer.Flag.Int("s3.port", 8333, "s3 server http listen port")
|
||||
s3Options.domainName = cmdServer.Flag.String("s3.domainName", "", "suffix of the host name in comma separated list, {bucket}.{domainName}")
|
||||
s3Options.tlsPrivateKey = cmdServer.Flag.String("s3.key.file", "", "path to the TLS private key file")
|
||||
s3Options.tlsCertificate = cmdServer.Flag.String("s3.cert.file", "", "path to the TLS certificate file")
|
||||
s3Options.config = cmdServer.Flag.String("s3.config", "", "path to the config file")
|
||||
s3Options.allowEmptyFolder = cmdServer.Flag.Bool("s3.allowEmptyFolder", false, "allow empty folders")
|
||||
|
||||
msgBrokerOptions.port = cmdServer.Flag.Int("msgBroker.port", 17777, "broker gRPC listen port")
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ type VolumeServerOptions struct {
|
||||
publicPort *int
|
||||
folders []string
|
||||
folderMaxLimits []int
|
||||
idxFolder *string
|
||||
ip *string
|
||||
publicUrl *string
|
||||
bindIp *string
|
||||
@@ -83,6 +84,7 @@ func init() {
|
||||
v.fileSizeLimitMB = cmdVolume.Flag.Int("fileSizeLimitMB", 256, "limit file size to avoid out of memory")
|
||||
v.pprof = cmdVolume.Flag.Bool("pprof", false, "enable pprof http handlers. precludes --memprofile and --cpuprofile")
|
||||
v.metricsHttpPort = cmdVolume.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
v.idxFolder = cmdVolume.Flag.String("dir.idx", "", "directory to store .idx files")
|
||||
}
|
||||
|
||||
var cmdVolume = &Command{
|
||||
@@ -211,6 +213,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,
|
||||
*v.idxFolder,
|
||||
volumeNeedleMapKind,
|
||||
strings.Split(masters, ","), 5, *v.dataCenter, *v.rack,
|
||||
v.whiteList,
|
||||
|
||||
@@ -77,7 +77,8 @@ func (store *AbstractSqlStore) InsertEntry(ctx context.Context, entry *filer.Ent
|
||||
}
|
||||
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "duplicate") {
|
||||
return fmt.Errorf("kv insert: %s", err)
|
||||
// return fmt.Errorf("insert: %s", err)
|
||||
// skip this since the error can be in a different language
|
||||
}
|
||||
|
||||
// now the insert failed possibly due to duplication constraints
|
||||
|
||||
@@ -5,11 +5,10 @@ import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (store *AbstractSqlStore) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
@@ -17,10 +16,13 @@ func (store *AbstractSqlStore) KvPut(ctx context.Context, key []byte, value []by
|
||||
dirStr, dirHash, name := genDirAndName(key)
|
||||
|
||||
res, err := store.getTxOrDB(ctx).ExecContext(ctx, store.SqlInsert, dirHash, name, dirStr, value)
|
||||
if err != nil {
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "duplicate") {
|
||||
return fmt.Errorf("kv insert: %s", err)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "duplicate") {
|
||||
// return fmt.Errorf("kv insert: %s", err)
|
||||
// skip this since the error can be in a different language
|
||||
}
|
||||
|
||||
// now the insert failed possibly due to duplication constraints
|
||||
|
||||
@@ -16,8 +16,9 @@ func init() {
|
||||
}
|
||||
|
||||
type CassandraStore struct {
|
||||
cluster *gocql.ClusterConfig
|
||||
session *gocql.Session
|
||||
cluster *gocql.ClusterConfig
|
||||
session *gocql.Session
|
||||
superLargeDirectoryHash map[string]string
|
||||
}
|
||||
|
||||
func (store *CassandraStore) GetName() string {
|
||||
@@ -30,10 +31,16 @@ func (store *CassandraStore) Initialize(configuration util.Configuration, prefix
|
||||
configuration.GetStringSlice(prefix+"hosts"),
|
||||
configuration.GetString(prefix+"username"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetStringSlice(prefix+"superLargeDirectories"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *CassandraStore) initialize(keyspace string, hosts []string, username string, password string) (err error) {
|
||||
func (store *CassandraStore) isSuperLargeDirectory(dir string) (dirHash string, isSuperLargeDirectory bool) {
|
||||
dirHash, isSuperLargeDirectory = store.superLargeDirectoryHash[dir]
|
||||
return
|
||||
}
|
||||
|
||||
func (store *CassandraStore) initialize(keyspace string, hosts []string, username string, password string, superLargeDirectories []string) (err error) {
|
||||
store.cluster = gocql.NewCluster(hosts...)
|
||||
if username != "" && password != "" {
|
||||
store.cluster.Authenticator = gocql.PasswordAuthenticator{Username: username, Password: password}
|
||||
@@ -44,6 +51,19 @@ func (store *CassandraStore) initialize(keyspace string, hosts []string, usernam
|
||||
if err != nil {
|
||||
glog.V(0).Infof("Failed to open cassandra store, hosts %v, keyspace %s", hosts, keyspace)
|
||||
}
|
||||
|
||||
// set directory hash
|
||||
store.superLargeDirectoryHash = make(map[string]string)
|
||||
existingHash := make(map[string]string)
|
||||
for _, dir := range superLargeDirectories {
|
||||
// adding dir hash to avoid duplicated names
|
||||
dirHash := util.Md5String([]byte(dir))[:4]
|
||||
store.superLargeDirectoryHash[dir] = dirHash
|
||||
if existingDir, found := existingHash[dirHash]; found {
|
||||
glog.Fatalf("directory %s has the same hash as %s", dir, existingDir)
|
||||
}
|
||||
existingHash[dirHash] = dir
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -60,6 +80,10 @@ func (store *CassandraStore) RollbackTransaction(ctx context.Context) error {
|
||||
func (store *CassandraStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
|
||||
dir, name := entry.FullPath.DirAndName()
|
||||
if dirHash, ok := store.isSuperLargeDirectory(dir); ok {
|
||||
dir, name = dirHash+name, ""
|
||||
}
|
||||
|
||||
meta, err := entry.EncodeAttributesAndChunks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode %s: %s", entry.FullPath, err)
|
||||
@@ -86,6 +110,10 @@ func (store *CassandraStore) UpdateEntry(ctx context.Context, entry *filer.Entry
|
||||
func (store *CassandraStore) FindEntry(ctx context.Context, fullpath util.FullPath) (entry *filer.Entry, err error) {
|
||||
|
||||
dir, name := fullpath.DirAndName()
|
||||
if dirHash, ok := store.isSuperLargeDirectory(dir); ok {
|
||||
dir, name = dirHash+name, ""
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if err := store.session.Query(
|
||||
"SELECT meta FROM filemeta WHERE directory=? AND name=?",
|
||||
@@ -113,6 +141,9 @@ func (store *CassandraStore) FindEntry(ctx context.Context, fullpath util.FullPa
|
||||
func (store *CassandraStore) DeleteEntry(ctx context.Context, fullpath util.FullPath) error {
|
||||
|
||||
dir, name := fullpath.DirAndName()
|
||||
if dirHash, ok := store.isSuperLargeDirectory(dir); ok {
|
||||
dir, name = dirHash+name, ""
|
||||
}
|
||||
|
||||
if err := store.session.Query(
|
||||
"DELETE FROM filemeta WHERE directory=? AND name=?",
|
||||
@@ -124,6 +155,9 @@ func (store *CassandraStore) DeleteEntry(ctx context.Context, fullpath util.Full
|
||||
}
|
||||
|
||||
func (store *CassandraStore) DeleteFolderChildren(ctx context.Context, fullpath util.FullPath) error {
|
||||
if _, ok := store.isSuperLargeDirectory(string(fullpath)); ok {
|
||||
return nil // filer.ErrUnsupportedSuperLargeDirectoryListing
|
||||
}
|
||||
|
||||
if err := store.session.Query(
|
||||
"DELETE FROM filemeta WHERE directory=?",
|
||||
@@ -141,6 +175,10 @@ func (store *CassandraStore) ListDirectoryPrefixedEntries(ctx context.Context, f
|
||||
func (store *CassandraStore) ListDirectoryEntries(ctx context.Context, fullpath util.FullPath, startFileName string, inclusive bool,
|
||||
limit int) (entries []*filer.Entry, err error) {
|
||||
|
||||
if _, ok := store.isSuperLargeDirectory(string(fullpath)); ok {
|
||||
return // nil, filer.ErrUnsupportedSuperLargeDirectoryListing
|
||||
}
|
||||
|
||||
cqlStr := "SELECT NAME, meta FROM filemeta WHERE directory=? AND name>? ORDER BY NAME ASC LIMIT ?"
|
||||
if inclusive {
|
||||
cqlStr = "SELECT NAME, meta FROM filemeta WHERE directory=? AND name>=? ORDER BY NAME ASC LIMIT ?"
|
||||
|
||||
+53
-10
@@ -1,10 +1,11 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,25 +16,67 @@ func (f *Filer) LoadConfiguration(config *viper.Viper) {
|
||||
|
||||
validateOneEnabledStore(config)
|
||||
|
||||
// load configuration for default filer store
|
||||
hasDefaultStoreConfigured := false
|
||||
for _, store := range Stores {
|
||||
if config.GetBool(store.GetName() + ".enabled") {
|
||||
store = reflect.New(reflect.ValueOf(store).Elem().Type()).Interface().(FilerStore)
|
||||
if err := store.Initialize(config, store.GetName()+"."); err != nil {
|
||||
glog.Fatalf("Failed to initialize store for %s: %+v",
|
||||
store.GetName(), err)
|
||||
glog.Fatalf("failed to initialize store for %s: %+v", store.GetName(), err)
|
||||
}
|
||||
f.SetStore(store)
|
||||
glog.V(0).Infof("Configure filer for %s", store.GetName())
|
||||
return
|
||||
glog.V(0).Infof("configured filer store to %s", store.GetName())
|
||||
hasDefaultStoreConfigured = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
println()
|
||||
println("Supported filer stores are:")
|
||||
if !hasDefaultStoreConfigured {
|
||||
println()
|
||||
println("Supported filer stores are:")
|
||||
for _, store := range Stores {
|
||||
println(" " + store.GetName())
|
||||
}
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// load path-specific filer store here
|
||||
// f.Store.AddPathSpecificStore(path, store)
|
||||
storeNames := make(map[string]FilerStore)
|
||||
for _, store := range Stores {
|
||||
println(" " + store.GetName())
|
||||
storeNames[store.GetName()] = store
|
||||
}
|
||||
allKeys := config.AllKeys()
|
||||
for _, key := range allKeys {
|
||||
if !strings.HasSuffix(key, ".enabled") {
|
||||
continue
|
||||
}
|
||||
key = key[:len(key)-len(".enabled")]
|
||||
if !strings.Contains(key, ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(key, ".")
|
||||
storeName, storeId := parts[0], parts[1]
|
||||
|
||||
store, found := storeNames[storeName]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
store = reflect.New(reflect.ValueOf(store).Elem().Type()).Interface().(FilerStore)
|
||||
if err := store.Initialize(config, key+"."); err != nil {
|
||||
glog.Fatalf("Failed to initialize store for %s: %+v", key, err)
|
||||
}
|
||||
location := config.GetString(key + ".location")
|
||||
if location == "" {
|
||||
glog.Errorf("path-specific filer store needs %s", key+".location")
|
||||
os.Exit(-1)
|
||||
}
|
||||
f.Store.AddPathSpecificStore(location, storeId, store)
|
||||
|
||||
glog.V(0).Infof("configure filer %s for %s", store.GetName(), location)
|
||||
}
|
||||
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
func validateOneEnabledStore(config *viper.Viper) {
|
||||
|
||||
+4
-1
@@ -40,10 +40,11 @@ type Entry struct {
|
||||
|
||||
HardLinkId HardLinkId
|
||||
HardLinkCounter int32
|
||||
Content []byte
|
||||
}
|
||||
|
||||
func (entry *Entry) Size() uint64 {
|
||||
return maxUint64(TotalSize(entry.Chunks), entry.FileSize)
|
||||
return maxUint64(maxUint64(TotalSize(entry.Chunks), entry.FileSize), uint64(len(entry.Content)))
|
||||
}
|
||||
|
||||
func (entry *Entry) Timestamp() time.Time {
|
||||
@@ -66,6 +67,7 @@ func (entry *Entry) ToProtoEntry() *filer_pb.Entry {
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
HardLinkCounter: entry.HardLinkCounter,
|
||||
Content: entry.Content,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +100,7 @@ func FromPbEntry(dir string, entry *filer_pb.Entry) *Entry {
|
||||
Chunks: entry.Chunks,
|
||||
HardLinkId: HardLinkId(entry.HardLinkId),
|
||||
HardLinkCounter: entry.HardLinkCounter,
|
||||
Content: entry.Content,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ func (entry *Entry) EncodeAttributesAndChunks() ([]byte, error) {
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
HardLinkCounter: entry.HardLinkCounter,
|
||||
Content: entry.Content,
|
||||
}
|
||||
return proto.Marshal(message)
|
||||
}
|
||||
@@ -38,6 +39,7 @@ func (entry *Entry) DecodeAttributesAndChunks(blob []byte) error {
|
||||
|
||||
entry.HardLinkId = message.HardLinkId
|
||||
entry.HardLinkCounter = message.HardLinkCounter
|
||||
entry.Content = message.Content
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -122,6 +124,9 @@ func EqualEntry(a, b *Entry) bool {
|
||||
if a.HardLinkCounter != b.HardLinkCounter {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(a.Content, b.Content) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+88
-107
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
const (
|
||||
LogFlushInterval = time.Minute
|
||||
PaginationSize = 1024 * 256
|
||||
PaginationSize = 1024
|
||||
FilerStoreId = "filer.store.id"
|
||||
)
|
||||
|
||||
@@ -41,14 +41,16 @@ type Filer struct {
|
||||
metaLogReplication string
|
||||
MetaAggregator *MetaAggregator
|
||||
Signature int32
|
||||
FilerConf *FilerConf
|
||||
}
|
||||
|
||||
func NewFiler(masters []string, grpcDialOption grpc.DialOption,
|
||||
filerHost string, filerGrpcPort uint32, collection string, replication string, notifyFn func()) *Filer {
|
||||
filerHost string, filerGrpcPort uint32, collection string, replication string, dataCenter string, notifyFn func()) *Filer {
|
||||
f := &Filer{
|
||||
MasterClient: wdclient.NewMasterClient(grpcDialOption, "filer", filerHost, filerGrpcPort, masters),
|
||||
MasterClient: wdclient.NewMasterClient(grpcDialOption, "filer", filerHost, filerGrpcPort, dataCenter, masters),
|
||||
fileIdDeletionQueue: util.NewUnboundedQueue(),
|
||||
GrpcDialOption: grpcDialOption,
|
||||
FilerConf: NewFilerConf(),
|
||||
}
|
||||
f.LocalMetaLogBuffer = log_buffer.NewLogBuffer(LogFlushInterval, f.logFlushFunc, notifyFn)
|
||||
f.metaLogCollection = collection
|
||||
@@ -132,69 +134,7 @@ func (f *Filer) CreateEntry(ctx context.Context, entry *Entry, o_excl bool, isFr
|
||||
return nil
|
||||
}
|
||||
|
||||
dirParts := strings.Split(string(entry.FullPath), "/")
|
||||
|
||||
// fmt.Printf("directory parts: %+v\n", dirParts)
|
||||
|
||||
var lastDirectoryEntry *Entry
|
||||
|
||||
for i := 1; i < len(dirParts); i++ {
|
||||
dirPath := "/" + util.Join(dirParts[:i]...)
|
||||
// fmt.Printf("%d directory: %+v\n", i, dirPath)
|
||||
|
||||
// check the store directly
|
||||
glog.V(4).Infof("find uncached directory: %s", dirPath)
|
||||
dirEntry, _ := f.FindEntry(ctx, util.FullPath(dirPath))
|
||||
|
||||
// no such existing directory
|
||||
if dirEntry == nil {
|
||||
|
||||
// create the directory
|
||||
now := time.Now()
|
||||
|
||||
dirEntry = &Entry{
|
||||
FullPath: util.FullPath(dirPath),
|
||||
Attr: Attr{
|
||||
Mtime: now,
|
||||
Crtime: now,
|
||||
Mode: os.ModeDir | entry.Mode | 0110,
|
||||
Uid: entry.Uid,
|
||||
Gid: entry.Gid,
|
||||
Collection: entry.Collection,
|
||||
Replication: entry.Replication,
|
||||
UserName: entry.UserName,
|
||||
GroupNames: entry.GroupNames,
|
||||
},
|
||||
}
|
||||
|
||||
glog.V(2).Infof("create directory: %s %v", dirPath, dirEntry.Mode)
|
||||
mkdirErr := f.Store.InsertEntry(ctx, dirEntry)
|
||||
if mkdirErr != nil {
|
||||
if _, err := f.FindEntry(ctx, util.FullPath(dirPath)); err == filer_pb.ErrNotFound {
|
||||
glog.V(3).Infof("mkdir %s: %v", dirPath, mkdirErr)
|
||||
return fmt.Errorf("mkdir %s: %v", dirPath, mkdirErr)
|
||||
}
|
||||
} else {
|
||||
f.maybeAddBucket(dirEntry)
|
||||
f.NotifyUpdateEvent(ctx, nil, dirEntry, false, isFromOtherCluster, nil)
|
||||
}
|
||||
|
||||
} else if !dirEntry.IsDirectory() {
|
||||
glog.Errorf("CreateEntry %s: %s should be a directory", entry.FullPath, dirPath)
|
||||
return fmt.Errorf("%s is a file", dirPath)
|
||||
}
|
||||
|
||||
// remember the direct parent directory entry
|
||||
if i == len(dirParts)-1 {
|
||||
lastDirectoryEntry = dirEntry
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if lastDirectoryEntry == nil {
|
||||
glog.Errorf("CreateEntry %s: lastDirectoryEntry is nil", entry.FullPath)
|
||||
return fmt.Errorf("parent folder not found: %v", entry.FullPath)
|
||||
}
|
||||
oldEntry, _ := f.FindEntry(ctx, entry.FullPath)
|
||||
|
||||
/*
|
||||
if !hasWritePermission(lastDirectoryEntry, entry) {
|
||||
@@ -204,9 +144,13 @@ func (f *Filer) CreateEntry(ctx context.Context, entry *Entry, o_excl bool, isFr
|
||||
}
|
||||
*/
|
||||
|
||||
oldEntry, _ := f.FindEntry(ctx, entry.FullPath)
|
||||
|
||||
if oldEntry == nil {
|
||||
|
||||
dirParts := strings.Split(string(entry.FullPath), "/")
|
||||
if err := f.ensureParentDirecotryEntry(ctx, entry, dirParts, len(dirParts)-1, isFromOtherCluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("InsertEntry %s: new entry: %v", entry.FullPath, entry.Name())
|
||||
if err := f.Store.InsertEntry(ctx, entry); err != nil {
|
||||
glog.Errorf("insert entry %s: %v", entry.FullPath, err)
|
||||
@@ -234,8 +178,68 @@ func (f *Filer) CreateEntry(ctx context.Context, entry *Entry, o_excl bool, isFr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Filer) ensureParentDirecotryEntry(ctx context.Context, entry *Entry, dirParts []string, level int, isFromOtherCluster bool) (err error) {
|
||||
|
||||
if level == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dirPath := "/" + util.Join(dirParts[:level]...)
|
||||
// fmt.Printf("%d directory: %+v\n", i, dirPath)
|
||||
|
||||
// check the store directly
|
||||
glog.V(4).Infof("find uncached directory: %s", dirPath)
|
||||
dirEntry, _ := f.FindEntry(ctx, util.FullPath(dirPath))
|
||||
|
||||
// no such existing directory
|
||||
if dirEntry == nil {
|
||||
|
||||
// ensure parent directory
|
||||
if err = f.ensureParentDirecotryEntry(ctx, entry, dirParts, level-1, isFromOtherCluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the directory
|
||||
now := time.Now()
|
||||
|
||||
dirEntry = &Entry{
|
||||
FullPath: util.FullPath(dirPath),
|
||||
Attr: Attr{
|
||||
Mtime: now,
|
||||
Crtime: now,
|
||||
Mode: os.ModeDir | entry.Mode | 0110,
|
||||
Uid: entry.Uid,
|
||||
Gid: entry.Gid,
|
||||
Collection: entry.Collection,
|
||||
Replication: entry.Replication,
|
||||
UserName: entry.UserName,
|
||||
GroupNames: entry.GroupNames,
|
||||
},
|
||||
}
|
||||
|
||||
glog.V(2).Infof("create directory: %s %v", dirPath, dirEntry.Mode)
|
||||
mkdirErr := f.Store.InsertEntry(ctx, dirEntry)
|
||||
if mkdirErr != nil {
|
||||
if _, err := f.FindEntry(ctx, util.FullPath(dirPath)); err == filer_pb.ErrNotFound {
|
||||
glog.V(3).Infof("mkdir %s: %v", dirPath, mkdirErr)
|
||||
return fmt.Errorf("mkdir %s: %v", dirPath, mkdirErr)
|
||||
}
|
||||
} else {
|
||||
f.maybeAddBucket(dirEntry)
|
||||
f.NotifyUpdateEvent(ctx, nil, dirEntry, false, isFromOtherCluster, nil)
|
||||
}
|
||||
|
||||
} else if !dirEntry.IsDirectory() {
|
||||
glog.Errorf("CreateEntry %s: %s should be a directory", entry.FullPath, dirPath)
|
||||
return fmt.Errorf("%s is a file", dirPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Filer) UpdateEntry(ctx context.Context, oldEntry, entry *Entry) (err error) {
|
||||
if oldEntry != nil {
|
||||
entry.Attr.Crtime = oldEntry.Attr.Crtime
|
||||
if oldEntry.IsDirectory() && !entry.IsDirectory() {
|
||||
glog.Errorf("existing %s is a directory", entry.FullPath)
|
||||
return fmt.Errorf("existing %s is a directory", entry.FullPath)
|
||||
@@ -248,26 +252,28 @@ func (f *Filer) UpdateEntry(ctx context.Context, oldEntry, entry *Entry) (err er
|
||||
return f.Store.UpdateEntry(ctx, entry)
|
||||
}
|
||||
|
||||
var (
|
||||
Root = &Entry{
|
||||
FullPath: "/",
|
||||
Attr: Attr{
|
||||
Mtime: time.Now(),
|
||||
Crtime: time.Now(),
|
||||
Mode: os.ModeDir | 0755,
|
||||
Uid: OS_UID,
|
||||
Gid: OS_GID,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (f *Filer) FindEntry(ctx context.Context, p util.FullPath) (entry *Entry, err error) {
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if string(p) == "/" {
|
||||
return &Entry{
|
||||
FullPath: p,
|
||||
Attr: Attr{
|
||||
Mtime: now,
|
||||
Crtime: now,
|
||||
Mode: os.ModeDir | 0755,
|
||||
Uid: OS_UID,
|
||||
Gid: OS_GID,
|
||||
},
|
||||
}, nil
|
||||
return Root, nil
|
||||
}
|
||||
entry, err = f.Store.FindEntry(ctx, p)
|
||||
if entry != nil && entry.TtlSec > 0 {
|
||||
if entry.Crtime.Add(time.Duration(entry.TtlSec) * time.Second).Before(time.Now()) {
|
||||
f.Store.DeleteEntry(ctx, p.Child(entry.Name()))
|
||||
f.Store.DeleteOneEntry(ctx, entry)
|
||||
return nil, filer_pb.ErrNotFound
|
||||
}
|
||||
}
|
||||
@@ -275,23 +281,6 @@ func (f *Filer) FindEntry(ctx context.Context, p util.FullPath) (entry *Entry, e
|
||||
|
||||
}
|
||||
|
||||
func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int, prefix string) ([]*Entry, error) {
|
||||
if strings.HasSuffix(string(p), "/") && len(p) > 1 {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
|
||||
var makeupEntries []*Entry
|
||||
entries, expiredCount, lastFileName, err := f.doListDirectoryEntries(ctx, p, startFileName, inclusive, limit, prefix)
|
||||
for expiredCount > 0 && err == nil {
|
||||
makeupEntries, expiredCount, lastFileName, err = f.doListDirectoryEntries(ctx, p, lastFileName, false, expiredCount, prefix)
|
||||
if err == nil {
|
||||
entries = append(entries, makeupEntries...)
|
||||
}
|
||||
}
|
||||
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (f *Filer) doListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*Entry, expiredCount int, lastFileName string, err error) {
|
||||
listedEntries, listErr := f.Store.ListDirectoryPrefixedEntries(ctx, p, startFileName, inclusive, limit, prefix)
|
||||
if listErr != nil {
|
||||
@@ -301,7 +290,7 @@ func (f *Filer) doListDirectoryEntries(ctx context.Context, p util.FullPath, sta
|
||||
lastFileName = entry.Name()
|
||||
if entry.TtlSec > 0 {
|
||||
if entry.Crtime.Add(time.Duration(entry.TtlSec) * time.Second).Before(time.Now()) {
|
||||
f.Store.DeleteEntry(ctx, p.Child(entry.Name()))
|
||||
f.Store.DeleteOneEntry(ctx, entry)
|
||||
expiredCount++
|
||||
continue
|
||||
}
|
||||
@@ -315,11 +304,3 @@ func (f *Filer) Shutdown() {
|
||||
f.LocalMetaLogBuffer.Shutdown()
|
||||
f.Store.Shutdown()
|
||||
}
|
||||
|
||||
func (f *Filer) maybeDeleteHardLinks(hardLinkIds []HardLinkId) {
|
||||
for _, hardLinkId := range hardLinkIds {
|
||||
if err := f.Store.DeleteHardLink(context.Background(), hardLinkId); err != nil {
|
||||
glog.Errorf("delete hard link id %d : %v", hardLinkId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/viant/ptrie"
|
||||
)
|
||||
|
||||
const (
|
||||
DirectoryEtcRoot = "/etc"
|
||||
DirectoryEtcSeaweedFS = "/etc/seaweedfs"
|
||||
FilerConfName = "filer.conf"
|
||||
IamConfigDirecotry = "/etc/iam"
|
||||
IamIdentityFile = "identity.json"
|
||||
)
|
||||
|
||||
type FilerConf struct {
|
||||
rules ptrie.Trie
|
||||
}
|
||||
|
||||
func NewFilerConf() (fc *FilerConf) {
|
||||
fc = &FilerConf{
|
||||
rules: ptrie.New(),
|
||||
}
|
||||
return fc
|
||||
}
|
||||
|
||||
func (fc *FilerConf) loadFromFiler(filer *Filer) (err error) {
|
||||
filerConfPath := util.NewFullPath(DirectoryEtcSeaweedFS, FilerConfName)
|
||||
entry, err := filer.FindEntry(context.Background(), filerConfPath)
|
||||
if err != nil {
|
||||
if err == filer_pb.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
glog.Errorf("read filer conf entry %s: %v", filerConfPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entry.Content) > 0 {
|
||||
return fc.LoadFromBytes(entry.Content)
|
||||
}
|
||||
|
||||
return fc.loadFromChunks(filer, entry.Chunks)
|
||||
}
|
||||
|
||||
func (fc *FilerConf) loadFromChunks(filer *Filer, chunks []*filer_pb.FileChunk) (err error) {
|
||||
data, err := filer.readEntry(chunks)
|
||||
if err != nil {
|
||||
glog.Errorf("read filer conf content: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return fc.LoadFromBytes(data)
|
||||
}
|
||||
|
||||
func (fc *FilerConf) LoadFromBytes(data []byte) (err error) {
|
||||
conf := &filer_pb.FilerConf{}
|
||||
|
||||
if err := jsonpb.Unmarshal(bytes.NewReader(data), conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fc.doLoadConf(conf)
|
||||
}
|
||||
|
||||
func (fc *FilerConf) doLoadConf(conf *filer_pb.FilerConf) (err error) {
|
||||
for _, location := range conf.Locations {
|
||||
err = fc.AddLocationConf(location)
|
||||
if err != nil {
|
||||
// this is not recoverable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fc *FilerConf) AddLocationConf(locConf *filer_pb.FilerConf_PathConf) (err error) {
|
||||
err = fc.rules.Put([]byte(locConf.LocationPrefix), locConf)
|
||||
if err != nil {
|
||||
glog.Errorf("put location prefix: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fc *FilerConf) DeleteLocationConf(locationPrefix string) {
|
||||
rules := ptrie.New()
|
||||
fc.rules.Walk(func(key []byte, value interface{}) bool {
|
||||
if string(key) == locationPrefix {
|
||||
return true
|
||||
}
|
||||
rules.Put(key, value)
|
||||
return true
|
||||
})
|
||||
fc.rules = rules
|
||||
return
|
||||
}
|
||||
|
||||
func (fc *FilerConf) MatchStorageRule(path string) (pathConf *filer_pb.FilerConf_PathConf) {
|
||||
pathConf = &filer_pb.FilerConf_PathConf{}
|
||||
fc.rules.MatchPrefix([]byte(path), func(key []byte, value interface{}) bool {
|
||||
t := value.(*filer_pb.FilerConf_PathConf)
|
||||
mergePathConf(pathConf, t)
|
||||
return true
|
||||
})
|
||||
return pathConf
|
||||
}
|
||||
|
||||
// merge if values in b is not empty, merge them into a
|
||||
func mergePathConf(a, b *filer_pb.FilerConf_PathConf) {
|
||||
a.Collection = util.Nvl(b.Collection, a.Collection)
|
||||
a.Replication = util.Nvl(b.Replication, a.Replication)
|
||||
a.Ttl = util.Nvl(b.Ttl, a.Ttl)
|
||||
if b.DiskType != filer_pb.FilerConf_PathConf_NONE {
|
||||
a.DiskType = b.DiskType
|
||||
}
|
||||
a.Fsync = b.Fsync || a.Fsync
|
||||
if b.VolumeGrowthCount > 0 {
|
||||
a.VolumeGrowthCount = b.VolumeGrowthCount
|
||||
}
|
||||
}
|
||||
|
||||
func (fc *FilerConf) ToProto() *filer_pb.FilerConf {
|
||||
m := &filer_pb.FilerConf{}
|
||||
fc.rules.Walk(func(key []byte, value interface{}) bool {
|
||||
pathConf := value.(*filer_pb.FilerConf_PathConf)
|
||||
m.Locations = append(m.Locations, pathConf)
|
||||
return true
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func (fc *FilerConf) ToText(writer io.Writer) error {
|
||||
|
||||
m := jsonpb.Marshaler{
|
||||
EmitDefaults: false,
|
||||
Indent: " ",
|
||||
}
|
||||
|
||||
return m.Marshal(writer, fc.ToProto())
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilerConf(t *testing.T) {
|
||||
|
||||
fc := NewFilerConf()
|
||||
|
||||
conf := &filer_pb.FilerConf{Locations: []*filer_pb.FilerConf_PathConf{
|
||||
{
|
||||
LocationPrefix: "/buckets/abc",
|
||||
Collection: "abc",
|
||||
},
|
||||
{
|
||||
LocationPrefix: "/buckets/abcd",
|
||||
Collection: "abcd",
|
||||
},
|
||||
{
|
||||
LocationPrefix: "/buckets/",
|
||||
Replication: "001",
|
||||
},
|
||||
}}
|
||||
fc.doLoadConf(conf)
|
||||
|
||||
assert.Equal(t, "abc", fc.MatchStorageRule("/buckets/abc/jasdf").Collection)
|
||||
assert.Equal(t, "abcd", fc.MatchStorageRule("/buckets/abcd/jasdf").Collection)
|
||||
assert.Equal(t, "001", fc.MatchStorageRule("/buckets/abc/jasdf").Replication)
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package filer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/master_pb"
|
||||
@@ -22,7 +21,7 @@ func (f *Filer) DeleteEntryMetaAndData(ctx context.Context, p util.FullPath, isR
|
||||
return findErr
|
||||
}
|
||||
|
||||
isCollection := f.isBucket(entry)
|
||||
isDeleteCollection := f.isBucket(entry)
|
||||
|
||||
var chunks []*filer_pb.FileChunk
|
||||
var hardLinkIds []HardLinkId
|
||||
@@ -31,7 +30,7 @@ func (f *Filer) DeleteEntryMetaAndData(ctx context.Context, p util.FullPath, isR
|
||||
// delete the folder children, not including the folder itself
|
||||
var dirChunks []*filer_pb.FileChunk
|
||||
var dirHardLinkIds []HardLinkId
|
||||
dirChunks, dirHardLinkIds, err = f.doBatchDeleteFolderMetaAndData(ctx, entry, isRecursive, ignoreRecursiveError, shouldDeleteChunks && !isCollection, isFromOtherCluster, signatures)
|
||||
dirChunks, dirHardLinkIds, err = f.doBatchDeleteFolderMetaAndData(ctx, entry, isRecursive, ignoreRecursiveError, shouldDeleteChunks && !isDeleteCollection, isFromOtherCluster, signatures)
|
||||
if err != nil {
|
||||
glog.V(0).Infof("delete directory %s: %v", p, err)
|
||||
return fmt.Errorf("delete directory %s: %v", p, err)
|
||||
@@ -46,8 +45,8 @@ func (f *Filer) DeleteEntryMetaAndData(ctx context.Context, p util.FullPath, isR
|
||||
return fmt.Errorf("delete file %s: %v", p, err)
|
||||
}
|
||||
|
||||
if shouldDeleteChunks && !isCollection {
|
||||
go f.DeleteChunks(chunks)
|
||||
if shouldDeleteChunks && !isDeleteCollection {
|
||||
f.DirectDeleteChunks(chunks)
|
||||
}
|
||||
// A case not handled:
|
||||
// what if the chunk is in a different collection?
|
||||
@@ -55,7 +54,7 @@ func (f *Filer) DeleteEntryMetaAndData(ctx context.Context, p util.FullPath, isR
|
||||
f.maybeDeleteHardLinks(hardLinkIds)
|
||||
}
|
||||
|
||||
if isCollection {
|
||||
if isDeleteCollection {
|
||||
collectionName := entry.Name()
|
||||
f.doDeleteCollection(collectionName)
|
||||
f.deleteBucket(collectionName)
|
||||
@@ -122,7 +121,7 @@ func (f *Filer) doDeleteEntryMetaAndData(ctx context.Context, entry *Entry, shou
|
||||
|
||||
glog.V(3).Infof("deleting entry %v, delete chunks: %v", entry.FullPath, shouldDeleteChunks)
|
||||
|
||||
if storeDeletionErr := f.Store.DeleteEntry(ctx, entry.FullPath); storeDeletionErr != nil {
|
||||
if storeDeletionErr := f.Store.DeleteOneEntry(ctx, entry); storeDeletionErr != nil {
|
||||
return fmt.Errorf("filer store delete: %v", storeDeletionErr)
|
||||
}
|
||||
if !entry.IsDirectory() {
|
||||
@@ -145,3 +144,11 @@ func (f *Filer) doDeleteCollection(collectionName string) (err error) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (f *Filer) maybeDeleteHardLinks(hardLinkIds []HardLinkId) {
|
||||
for _, hardLinkId := range hardLinkIds {
|
||||
if err := f.Store.DeleteHardLink(context.Background(), hardLinkId); err != nil {
|
||||
glog.Errorf("delete hard link id %d : %v", hardLinkId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,50 @@ func (f *Filer) loopProcessingDeletion() {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filer) doDeleteFileIds(fileIds []string) {
|
||||
|
||||
lookupFunc := LookupByMasterClientFn(f.MasterClient)
|
||||
DeletionBatchSize := 100000 // roughly 20 bytes cost per file id.
|
||||
|
||||
for len(fileIds) > 0 {
|
||||
var toDeleteFileIds []string
|
||||
if len(fileIds) > DeletionBatchSize {
|
||||
toDeleteFileIds = fileIds[:DeletionBatchSize]
|
||||
fileIds = fileIds[DeletionBatchSize:]
|
||||
} else {
|
||||
toDeleteFileIds = fileIds
|
||||
fileIds = fileIds[:0]
|
||||
}
|
||||
deletionCount := len(toDeleteFileIds)
|
||||
_, err := operation.DeleteFilesWithLookupVolumeId(f.GrpcDialOption, toDeleteFileIds, lookupFunc)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "already deleted") {
|
||||
glog.V(0).Infof("deleting fileIds len=%d error: %v", deletionCount, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filer) DirectDeleteChunks(chunks []*filer_pb.FileChunk) {
|
||||
var fildIdsToDelete []string
|
||||
for _, chunk := range chunks {
|
||||
if !chunk.IsChunkManifest {
|
||||
fildIdsToDelete = append(fildIdsToDelete, chunk.GetFileIdString())
|
||||
continue
|
||||
}
|
||||
dataChunks, manifestResolveErr := ResolveOneChunkManifest(f.MasterClient.LookupFileId, chunk)
|
||||
if manifestResolveErr != nil {
|
||||
glog.V(0).Infof("failed to resolve manifest %s: %v", chunk.FileId, manifestResolveErr)
|
||||
}
|
||||
for _, dChunk := range dataChunks {
|
||||
fildIdsToDelete = append(fildIdsToDelete, dChunk.GetFileIdString())
|
||||
}
|
||||
fildIdsToDelete = append(fildIdsToDelete, chunk.GetFileIdString())
|
||||
}
|
||||
|
||||
f.doDeleteFileIds(fildIdsToDelete)
|
||||
}
|
||||
|
||||
func (f *Filer) DeleteChunks(chunks []*filer_pb.FileChunk) {
|
||||
for _, chunk := range chunks {
|
||||
if !chunk.IsChunkManifest {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func (f *Filer) appendToFile(targetFile string, data []byte) error {
|
||||
|
||||
assignResult, uploadResult, err2 := f.assignAndUpload(data)
|
||||
assignResult, uploadResult, err2 := f.assignAndUpload(targetFile, data)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@@ -46,14 +46,16 @@ func (f *Filer) appendToFile(targetFile string, data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Filer) assignAndUpload(data []byte) (*operation.AssignResult, *operation.UploadResult, error) {
|
||||
func (f *Filer) assignAndUpload(targetFile string, data []byte) (*operation.AssignResult, *operation.UploadResult, error) {
|
||||
// assign a volume location
|
||||
rule := f.FilerConf.MatchStorageRule(targetFile)
|
||||
assignRequest := &operation.VolumeAssignRequest{
|
||||
Count: 1,
|
||||
Collection: f.metaLogCollection,
|
||||
Replication: f.metaLogReplication,
|
||||
WritableVolumeCount: 1,
|
||||
Collection: util.Nvl(f.metaLogCollection, rule.Collection),
|
||||
Replication: util.Nvl(f.metaLogReplication, rule.Replication),
|
||||
WritableVolumeCount: rule.VolumeGrowthCount,
|
||||
}
|
||||
|
||||
assignResult, err := operation.Assign(f.GetMaster(), f.GrpcDialOption, assignRequest)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("AssignVolume: %v", err)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
// onMetadataChangeEvent is triggered after filer processed change events from local or remote filers
|
||||
func (f *Filer) onMetadataChangeEvent(event *filer_pb.SubscribeMetadataResponse) {
|
||||
if DirectoryEtcSeaweedFS != event.Directory {
|
||||
if DirectoryEtcSeaweedFS != event.EventNotification.NewParentPath {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
entry := event.EventNotification.NewEntry
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(0).Infof("procesing %v", event)
|
||||
if entry.Name == FilerConfName {
|
||||
f.reloadFilerConfiguration(entry)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f *Filer) readEntry(chunks []*filer_pb.FileChunk) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := StreamContent(f.MasterClient, &buf, chunks, 0, math.MaxInt64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *Filer) reloadFilerConfiguration(entry *filer_pb.Entry) {
|
||||
fc := NewFilerConf()
|
||||
err := fc.loadFromChunks(f, entry.Chunks)
|
||||
if err != nil {
|
||||
glog.Errorf("read filer conf chunks: %v", err)
|
||||
return
|
||||
}
|
||||
f.FilerConf = fc
|
||||
}
|
||||
|
||||
func (f *Filer) LoadFilerConf() {
|
||||
fc := NewFilerConf()
|
||||
err := util.Retry("loadFilerConf", func() error {
|
||||
return fc.loadFromFiler(f)
|
||||
})
|
||||
if err != nil {
|
||||
glog.Errorf("read filer conf: %v", err)
|
||||
return
|
||||
}
|
||||
f.FilerConf = fc
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (f *Filer) CanRename(source, target util.FullPath) error {
|
||||
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
|
||||
}
|
||||
|
||||
func (f *Filer) DetectBucket(source util.FullPath) (bucket string) {
|
||||
if strings.HasPrefix(string(source), f.DirBucketsPath+"/") {
|
||||
bucketAndObjectKey := string(source)[len(f.DirBucketsPath)+1:]
|
||||
t := strings.Index(bucketAndObjectKey, "/")
|
||||
if t < 0 {
|
||||
bucket = bucketAndObjectKey
|
||||
}
|
||||
if t > 0 {
|
||||
bucket = bucketAndObjectKey[:t]
|
||||
}
|
||||
}
|
||||
return bucket
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func splitPattern(pattern string) (prefix string, restPattern string) {
|
||||
position := strings.Index(pattern, "*")
|
||||
if position >= 0 {
|
||||
return pattern[:position], pattern[position:]
|
||||
}
|
||||
position = strings.Index(pattern, "?")
|
||||
if position >= 0 {
|
||||
return pattern[:position], pattern[position:]
|
||||
}
|
||||
return "", restPattern
|
||||
}
|
||||
|
||||
func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int, namePattern string) (entries []*Entry, err error) {
|
||||
if strings.HasSuffix(string(p), "/") && len(p) > 1 {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
|
||||
prefix, restNamePattern := splitPattern(namePattern)
|
||||
var missedCount int
|
||||
var lastFileName string
|
||||
|
||||
entries, missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, startFileName, inclusive, limit, prefix, restNamePattern)
|
||||
|
||||
for missedCount > 0 && err == nil {
|
||||
var makeupEntries []*Entry
|
||||
makeupEntries, missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, lastFileName, false, missedCount, prefix, restNamePattern)
|
||||
for _, entry := range makeupEntries {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int, prefix, restNamePattern string) (matchedEntries []*Entry, missedCount int, lastFileName string, err error) {
|
||||
var foundEntries []*Entry
|
||||
|
||||
foundEntries, lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(restNamePattern) == 0 {
|
||||
return foundEntries, 0, lastFileName, nil
|
||||
}
|
||||
for _, entry := range foundEntries {
|
||||
nameToTest := strings.ToLower(entry.Name())
|
||||
if matched, matchErr := filepath.Match(restNamePattern, nameToTest[len(prefix):]); matchErr == nil && matched {
|
||||
matchedEntries = append(matchedEntries, entry)
|
||||
} else {
|
||||
missedCount++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Filer) doListValidEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*Entry, lastFileName string, err error) {
|
||||
var makeupEntries []*Entry
|
||||
var expiredCount int
|
||||
entries, expiredCount, lastFileName, err = f.doListDirectoryEntries(ctx, p, startFileName, inclusive, limit, prefix)
|
||||
for expiredCount > 0 && err == nil {
|
||||
makeupEntries, expiredCount, lastFileName, err = f.doListDirectoryEntries(ctx, p, lastFileName, false, expiredCount, prefix)
|
||||
if err == nil {
|
||||
entries = append(entries, makeupEntries...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+4
-220
@@ -3,18 +3,14 @@ package filer
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/stats"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedListDirectoryPrefixed = errors.New("unsupported directory prefix listing")
|
||||
ErrKvNotImplemented = errors.New("kv not implemented yet")
|
||||
ErrKvNotFound = errors.New("kv: not found")
|
||||
ErrUnsupportedListDirectoryPrefixed = errors.New("unsupported directory prefix listing")
|
||||
ErrUnsupportedSuperLargeDirectoryListing = errors.New("unsupported super large directory listing")
|
||||
ErrKvNotImplemented = errors.New("kv not implemented yet")
|
||||
ErrKvNotFound = errors.New("kv: not found")
|
||||
)
|
||||
|
||||
type FilerStore interface {
|
||||
@@ -41,215 +37,3 @@ type FilerStore interface {
|
||||
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
type VirtualFilerStore interface {
|
||||
FilerStore
|
||||
DeleteHardLink(ctx context.Context, hardLinkId HardLinkId) error
|
||||
}
|
||||
|
||||
type FilerStoreWrapper struct {
|
||||
ActualStore FilerStore
|
||||
}
|
||||
|
||||
func NewFilerStoreWrapper(store FilerStore) *FilerStoreWrapper {
|
||||
if innerStore, ok := store.(*FilerStoreWrapper); ok {
|
||||
return innerStore
|
||||
}
|
||||
return &FilerStoreWrapper{
|
||||
ActualStore: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) GetName() string {
|
||||
return fsw.ActualStore.GetName()
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) Initialize(configuration util.Configuration, prefix string) error {
|
||||
return fsw.ActualStore.Initialize(configuration, prefix)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) InsertEntry(ctx context.Context, entry *Entry) error {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "insert").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "insert").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
filer_pb.BeforeEntrySerialization(entry.Chunks)
|
||||
if entry.Mime == "application/octet-stream" {
|
||||
entry.Mime = ""
|
||||
}
|
||||
|
||||
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fsw.ActualStore.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) UpdateEntry(ctx context.Context, entry *Entry) error {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "update").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "update").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
filer_pb.BeforeEntrySerialization(entry.Chunks)
|
||||
if entry.Mime == "application/octet-stream" {
|
||||
entry.Mime = ""
|
||||
}
|
||||
|
||||
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fsw.ActualStore.UpdateEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) FindEntry(ctx context.Context, fp util.FullPath) (entry *Entry, err error) {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "find").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "find").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
entry, err = fsw.ActualStore.FindEntry(ctx, fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
return
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) DeleteEntry(ctx context.Context, fp util.FullPath) (err error) {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "delete").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "delete").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
existingEntry, findErr := fsw.FindEntry(ctx, fp)
|
||||
if findErr == filer_pb.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if len(existingEntry.HardLinkId) != 0 {
|
||||
// remove hard link
|
||||
if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fsw.ActualStore.DeleteEntry(ctx, fp)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) DeleteFolderChildren(ctx context.Context, fp util.FullPath) (err error) {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "deleteFolderChildren").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "deleteFolderChildren").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
return fsw.ActualStore.DeleteFolderChildren(ctx, fp)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int) ([]*Entry, error) {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "list").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "list").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
entries, err := fsw.ActualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
}
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) ([]*Entry, error) {
|
||||
stats.FilerStoreCounter.WithLabelValues(fsw.ActualStore.GetName(), "prefixList").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(fsw.ActualStore.GetName(), "prefixList").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
entries, err := fsw.ActualStore.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix)
|
||||
if err == ErrUnsupportedListDirectoryPrefixed {
|
||||
entries, err = fsw.prefixFilterEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) (entries []*Entry, err error) {
|
||||
entries, err = fsw.ActualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
var lastFileName string
|
||||
notPrefixed := entries
|
||||
entries = nil
|
||||
for count < limit && len(notPrefixed) > 0 {
|
||||
for _, entry := range notPrefixed {
|
||||
lastFileName = entry.Name()
|
||||
if strings.HasPrefix(entry.Name(), prefix) {
|
||||
count++
|
||||
entries = append(entries, entry)
|
||||
if count >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if count < limit {
|
||||
notPrefixed, err = fsw.ActualStore.ListDirectoryEntries(ctx, dirPath, lastFileName, false, limit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return fsw.ActualStore.BeginTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) CommitTransaction(ctx context.Context) error {
|
||||
return fsw.ActualStore.CommitTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) RollbackTransaction(ctx context.Context) error {
|
||||
return fsw.ActualStore.RollbackTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) Shutdown() {
|
||||
fsw.ActualStore.Shutdown()
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
return fsw.ActualStore.KvPut(ctx, key, value)
|
||||
}
|
||||
func (fsw *FilerStoreWrapper) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
return fsw.ActualStore.KvGet(ctx, key)
|
||||
}
|
||||
func (fsw *FilerStoreWrapper) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
return fsw.ActualStore.KvDelete(ctx, key)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,16 @@ func (fsw *FilerStoreWrapper) handleUpdateToHardLinks(ctx context.Context, entry
|
||||
}
|
||||
|
||||
// check what is existing entry
|
||||
existingEntry, err := fsw.ActualStore.FindEntry(ctx, entry.FullPath)
|
||||
glog.V(4).Infof("handleUpdateToHardLinks FindEntry %s", entry.FullPath)
|
||||
actualStore := fsw.getActualStore(entry.FullPath)
|
||||
existingEntry, err := actualStore.FindEntry(ctx, entry.FullPath)
|
||||
if err != nil && err != filer_pb.ErrNotFound {
|
||||
return fmt.Errorf("update existing entry %s: %v", entry.FullPath, err)
|
||||
}
|
||||
|
||||
// remove old hard link
|
||||
if err == nil && len(existingEntry.HardLinkId) != 0 && bytes.Compare(existingEntry.HardLinkId, entry.HardLinkId) != 0 {
|
||||
glog.V(4).Infof("handleUpdateToHardLinks DeleteHardLink %s", entry.FullPath)
|
||||
if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,6 +55,7 @@ func (fsw *FilerStoreWrapper) maybeReadHardLink(ctx context.Context, entry *Entr
|
||||
}
|
||||
key := entry.HardLinkId
|
||||
|
||||
glog.V(4).Infof("maybeReadHardLink KvGet %v", key)
|
||||
value, err := fsw.KvGet(ctx, key)
|
||||
if err != nil {
|
||||
glog.Errorf("read %s hardlink %d: %v", entry.FullPath, entry.HardLinkId, err)
|
||||
@@ -83,6 +87,7 @@ func (fsw *FilerStoreWrapper) DeleteHardLink(ctx context.Context, hardLinkId Har
|
||||
|
||||
entry.HardLinkCounter--
|
||||
if entry.HardLinkCounter <= 0 {
|
||||
glog.V(4).Infof("DeleteHardLink KvDelete %v", key)
|
||||
return fsw.KvDelete(ctx, key)
|
||||
}
|
||||
|
||||
@@ -91,6 +96,7 @@ func (fsw *FilerStoreWrapper) DeleteHardLink(ctx context.Context, hardLinkId Har
|
||||
return encodeErr
|
||||
}
|
||||
|
||||
glog.V(4).Infof("DeleteHardLink KvPut %v", key)
|
||||
return fsw.KvPut(ctx, key, newBlob)
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = FilerStore(&FilerStorePathTranlator{})
|
||||
)
|
||||
|
||||
type FilerStorePathTranlator struct {
|
||||
actualStore FilerStore
|
||||
storeRoot string
|
||||
}
|
||||
|
||||
func NewFilerStorePathTranlator(storeRoot string, store FilerStore) *FilerStorePathTranlator {
|
||||
if innerStore, ok := store.(*FilerStorePathTranlator); ok {
|
||||
return innerStore
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(storeRoot, "/") {
|
||||
storeRoot += "/"
|
||||
}
|
||||
|
||||
return &FilerStorePathTranlator{
|
||||
actualStore: store,
|
||||
storeRoot: storeRoot,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) translatePath(fp util.FullPath) (newPath util.FullPath) {
|
||||
newPath = fp
|
||||
if t.storeRoot == "/" {
|
||||
return
|
||||
}
|
||||
newPath = fp[len(t.storeRoot)-1:]
|
||||
if newPath == "" {
|
||||
newPath = "/"
|
||||
}
|
||||
return
|
||||
}
|
||||
func (t *FilerStorePathTranlator) changeEntryPath(entry *Entry) (previousPath util.FullPath) {
|
||||
previousPath = entry.FullPath
|
||||
if t.storeRoot == "/" {
|
||||
return
|
||||
}
|
||||
entry.FullPath = t.translatePath(previousPath)
|
||||
return
|
||||
}
|
||||
func (t *FilerStorePathTranlator) recoverEntryPath(entry *Entry, previousPath util.FullPath) {
|
||||
entry.FullPath = previousPath
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) GetName() string {
|
||||
return t.actualStore.GetName()
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) Initialize(configuration util.Configuration, prefix string) error {
|
||||
return t.actualStore.Initialize(configuration, prefix)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) InsertEntry(ctx context.Context, entry *Entry) error {
|
||||
previousPath := t.changeEntryPath(entry)
|
||||
defer t.recoverEntryPath(entry, previousPath)
|
||||
|
||||
return t.actualStore.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) UpdateEntry(ctx context.Context, entry *Entry) error {
|
||||
previousPath := t.changeEntryPath(entry)
|
||||
defer t.recoverEntryPath(entry, previousPath)
|
||||
|
||||
return t.actualStore.UpdateEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) FindEntry(ctx context.Context, fp util.FullPath) (entry *Entry, err error) {
|
||||
if t.storeRoot == "/" {
|
||||
return t.actualStore.FindEntry(ctx, fp)
|
||||
}
|
||||
newFullPath := t.translatePath(fp)
|
||||
entry, err = t.actualStore.FindEntry(ctx, newFullPath)
|
||||
if err == nil {
|
||||
entry.FullPath = fp[:len(t.storeRoot)-1] + entry.FullPath
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) DeleteEntry(ctx context.Context, fp util.FullPath) (err error) {
|
||||
newFullPath := t.translatePath(fp)
|
||||
return t.actualStore.DeleteEntry(ctx, newFullPath)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) DeleteOneEntry(ctx context.Context, existingEntry *Entry) (err error) {
|
||||
|
||||
previousPath := t.changeEntryPath(existingEntry)
|
||||
defer t.recoverEntryPath(existingEntry, previousPath)
|
||||
|
||||
return t.actualStore.DeleteEntry(ctx, existingEntry.FullPath)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) DeleteFolderChildren(ctx context.Context, fp util.FullPath) (err error) {
|
||||
newFullPath := t.translatePath(fp)
|
||||
|
||||
return t.actualStore.DeleteFolderChildren(ctx, newFullPath)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int) ([]*Entry, error) {
|
||||
|
||||
newFullPath := t.translatePath(dirPath)
|
||||
|
||||
entries, err := t.actualStore.ListDirectoryEntries(ctx, newFullPath, startFileName, includeStartFile, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
entry.FullPath = dirPath[:len(t.storeRoot)-1] + entry.FullPath
|
||||
}
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) ([]*Entry, error) {
|
||||
|
||||
newFullPath := t.translatePath(dirPath)
|
||||
|
||||
entries, err := t.actualStore.ListDirectoryPrefixedEntries(ctx, newFullPath, startFileName, includeStartFile, limit, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
entry.FullPath = dirPath[:len(t.storeRoot)-1] + entry.FullPath
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return t.actualStore.BeginTransaction(ctx)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) CommitTransaction(ctx context.Context) error {
|
||||
return t.actualStore.CommitTransaction(ctx)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) RollbackTransaction(ctx context.Context) error {
|
||||
return t.actualStore.RollbackTransaction(ctx)
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) Shutdown() {
|
||||
t.actualStore.Shutdown()
|
||||
}
|
||||
|
||||
func (t *FilerStorePathTranlator) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
return t.actualStore.KvPut(ctx, key, value)
|
||||
}
|
||||
func (t *FilerStorePathTranlator) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
return t.actualStore.KvGet(ctx, key)
|
||||
}
|
||||
func (t *FilerStorePathTranlator) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
return t.actualStore.KvDelete(ctx, key)
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/viant/ptrie"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/stats"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = VirtualFilerStore(&FilerStoreWrapper{})
|
||||
)
|
||||
|
||||
type VirtualFilerStore interface {
|
||||
FilerStore
|
||||
DeleteHardLink(ctx context.Context, hardLinkId HardLinkId) error
|
||||
DeleteOneEntry(ctx context.Context, entry *Entry) error
|
||||
AddPathSpecificStore(path string, storeId string, store FilerStore)
|
||||
}
|
||||
|
||||
type FilerStoreWrapper struct {
|
||||
defaultStore FilerStore
|
||||
pathToStore ptrie.Trie
|
||||
storeIdToStore map[string]FilerStore
|
||||
}
|
||||
|
||||
func NewFilerStoreWrapper(store FilerStore) *FilerStoreWrapper {
|
||||
if innerStore, ok := store.(*FilerStoreWrapper); ok {
|
||||
return innerStore
|
||||
}
|
||||
return &FilerStoreWrapper{
|
||||
defaultStore: store,
|
||||
pathToStore: ptrie.New(),
|
||||
storeIdToStore: make(map[string]FilerStore),
|
||||
}
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) AddPathSpecificStore(path string, storeId string, store FilerStore) {
|
||||
fsw.storeIdToStore[storeId] = NewFilerStorePathTranlator(path, store)
|
||||
err := fsw.pathToStore.Put([]byte(path), storeId)
|
||||
if err != nil {
|
||||
glog.Fatalf("put path specific store: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) getActualStore(path util.FullPath) (store FilerStore) {
|
||||
store = fsw.defaultStore
|
||||
if path == "/" {
|
||||
return
|
||||
}
|
||||
var storeId string
|
||||
fsw.pathToStore.MatchPrefix([]byte(path), func(key []byte, value interface{}) bool {
|
||||
storeId = value.(string)
|
||||
return false
|
||||
})
|
||||
if storeId != "" {
|
||||
store = fsw.storeIdToStore[storeId]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) getDefaultStore() (store FilerStore) {
|
||||
return fsw.defaultStore
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) GetName() string {
|
||||
return fsw.getDefaultStore().GetName()
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) Initialize(configuration util.Configuration, prefix string) error {
|
||||
return fsw.getDefaultStore().Initialize(configuration, prefix)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) InsertEntry(ctx context.Context, entry *Entry) error {
|
||||
actualStore := fsw.getActualStore(entry.FullPath)
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "insert").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "insert").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
filer_pb.BeforeEntrySerialization(entry.Chunks)
|
||||
if entry.Mime == "application/octet-stream" {
|
||||
entry.Mime = ""
|
||||
}
|
||||
|
||||
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("InsertEntry %s", entry.FullPath)
|
||||
return actualStore.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) UpdateEntry(ctx context.Context, entry *Entry) error {
|
||||
actualStore := fsw.getActualStore(entry.FullPath)
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "update").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "update").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
filer_pb.BeforeEntrySerialization(entry.Chunks)
|
||||
if entry.Mime == "application/octet-stream" {
|
||||
entry.Mime = ""
|
||||
}
|
||||
|
||||
if err := fsw.handleUpdateToHardLinks(ctx, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("UpdateEntry %s", entry.FullPath)
|
||||
return actualStore.UpdateEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) FindEntry(ctx context.Context, fp util.FullPath) (entry *Entry, err error) {
|
||||
actualStore := fsw.getActualStore(fp)
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "find").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "find").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
glog.V(4).Infof("FindEntry %s", fp)
|
||||
entry, err = actualStore.FindEntry(ctx, fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
return
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) DeleteEntry(ctx context.Context, fp util.FullPath) (err error) {
|
||||
actualStore := fsw.getActualStore(fp)
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "delete").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "delete").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
existingEntry, findErr := fsw.FindEntry(ctx, fp)
|
||||
if findErr == filer_pb.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
if len(existingEntry.HardLinkId) != 0 {
|
||||
// remove hard link
|
||||
glog.V(4).Infof("DeleteHardLink %s", existingEntry.FullPath)
|
||||
if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(4).Infof("DeleteEntry %s", fp)
|
||||
return actualStore.DeleteEntry(ctx, fp)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) DeleteOneEntry(ctx context.Context, existingEntry *Entry) (err error) {
|
||||
actualStore := fsw.getActualStore(existingEntry.FullPath)
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "delete").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "delete").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
if len(existingEntry.HardLinkId) != 0 {
|
||||
// remove hard link
|
||||
glog.V(4).Infof("DeleteHardLink %s", existingEntry.FullPath)
|
||||
if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(4).Infof("DeleteOneEntry %s", existingEntry.FullPath)
|
||||
return actualStore.DeleteEntry(ctx, existingEntry.FullPath)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) DeleteFolderChildren(ctx context.Context, fp util.FullPath) (err error) {
|
||||
actualStore := fsw.getActualStore(fp + "/")
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "deleteFolderChildren").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "deleteFolderChildren").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
glog.V(4).Infof("DeleteFolderChildren %s", fp)
|
||||
return actualStore.DeleteFolderChildren(ctx, fp)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int) ([]*Entry, error) {
|
||||
actualStore := fsw.getActualStore(dirPath + "/")
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "list").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "list").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
|
||||
glog.V(4).Infof("ListDirectoryEntries %s from %s limit %d", dirPath, startFileName, limit)
|
||||
entries, err := actualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
}
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) ([]*Entry, error) {
|
||||
actualStore := fsw.getActualStore(dirPath + "/")
|
||||
stats.FilerStoreCounter.WithLabelValues(actualStore.GetName(), "prefixList").Inc()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "prefixList").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
glog.V(4).Infof("ListDirectoryPrefixedEntries %s from %s prefix %s limit %d", dirPath, startFileName, prefix, limit)
|
||||
entries, err := actualStore.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix)
|
||||
if err == ErrUnsupportedListDirectoryPrefixed {
|
||||
entries, err = fsw.prefixFilterEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
fsw.maybeReadHardLink(ctx, entry)
|
||||
filer_pb.AfterEntryDeserialization(entry.Chunks)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) prefixFilterEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) (entries []*Entry, err error) {
|
||||
actualStore := fsw.getActualStore(dirPath + "/")
|
||||
entries, err = actualStore.ListDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
var lastFileName string
|
||||
notPrefixed := entries
|
||||
entries = nil
|
||||
for count < limit && len(notPrefixed) > 0 {
|
||||
for _, entry := range notPrefixed {
|
||||
lastFileName = entry.Name()
|
||||
if strings.HasPrefix(entry.Name(), prefix) {
|
||||
count++
|
||||
entries = append(entries, entry)
|
||||
if count >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if count < limit {
|
||||
notPrefixed, err = actualStore.ListDirectoryEntries(ctx, dirPath, lastFileName, false, limit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return fsw.getDefaultStore().BeginTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) CommitTransaction(ctx context.Context) error {
|
||||
return fsw.getDefaultStore().CommitTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) RollbackTransaction(ctx context.Context) error {
|
||||
return fsw.getDefaultStore().RollbackTransaction(ctx)
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) Shutdown() {
|
||||
fsw.getDefaultStore().Shutdown()
|
||||
}
|
||||
|
||||
func (fsw *FilerStoreWrapper) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
return fsw.getDefaultStore().KvPut(ctx, key, value)
|
||||
}
|
||||
func (fsw *FilerStoreWrapper) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
return fsw.getDefaultStore().KvGet(ctx, key)
|
||||
}
|
||||
func (fsw *FilerStoreWrapper) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
return fsw.getDefaultStore().KvDelete(ctx, key)
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package hbase
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/tsuna/gohbase"
|
||||
"github.com/tsuna/gohbase/hrpc"
|
||||
"io"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &HbaseStore{})
|
||||
}
|
||||
|
||||
type HbaseStore struct {
|
||||
Client gohbase.Client
|
||||
table []byte
|
||||
cfKv string
|
||||
cfMetaDir string
|
||||
column string
|
||||
}
|
||||
|
||||
func (store *HbaseStore) GetName() string {
|
||||
return "hbase"
|
||||
}
|
||||
|
||||
func (store *HbaseStore) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
return store.initialize(
|
||||
configuration.GetString(prefix+"zkquorum"),
|
||||
configuration.GetString(prefix+"table"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) initialize(zkquorum, table string) (err error) {
|
||||
store.Client = gohbase.NewClient(zkquorum)
|
||||
store.table = []byte(table)
|
||||
store.cfKv = "kv"
|
||||
store.cfMetaDir = "meta"
|
||||
store.column = "a"
|
||||
|
||||
// check table exists
|
||||
key := "whatever"
|
||||
headers := map[string][]string{store.cfMetaDir: nil}
|
||||
get, err := hrpc.NewGet(context.Background(), store.table, []byte(key), hrpc.Families(headers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("NewGet returned an error: %v", err)
|
||||
}
|
||||
_, err = store.Client.Get(get)
|
||||
if err != gohbase.TableNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create table
|
||||
adminClient := gohbase.NewAdminClient(zkquorum)
|
||||
cFamilies := []string{store.cfKv, store.cfMetaDir}
|
||||
cf := make(map[string]map[string]string, len(cFamilies))
|
||||
for _, f := range cFamilies {
|
||||
cf[f] = nil
|
||||
}
|
||||
ct := hrpc.NewCreateTable(context.Background(), []byte(table), cf)
|
||||
if err := adminClient.CreateTable(ct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) InsertEntry(ctx context.Context, entry *filer.Entry) error {
|
||||
value, err := entry.EncodeAttributesAndChunks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
|
||||
}
|
||||
if len(entry.Chunks) > 50 {
|
||||
value = util.MaybeGzipData(value)
|
||||
}
|
||||
|
||||
return store.doPut(ctx, store.cfMetaDir, []byte(entry.FullPath), value, entry.TtlSec)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
return store.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) FindEntry(ctx context.Context, path util.FullPath) (entry *filer.Entry, err error) {
|
||||
value, err := store.doGet(ctx, store.cfMetaDir, []byte(path))
|
||||
if err != nil {
|
||||
if err == filer.ErrKvNotFound {
|
||||
return nil, filer_pb.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry = &filer.Entry{
|
||||
FullPath: path,
|
||||
}
|
||||
err = entry.DecodeAttributesAndChunks(util.MaybeDecompressData(value))
|
||||
if err != nil {
|
||||
return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) DeleteEntry(ctx context.Context, path util.FullPath) (err error) {
|
||||
return store.doDelete(ctx, store.cfMetaDir, []byte(path))
|
||||
}
|
||||
|
||||
func (store *HbaseStore) DeleteFolderChildren(ctx context.Context, path util.FullPath) (err error) {
|
||||
|
||||
family := map[string][]string{store.cfMetaDir: {COLUMN_NAME}}
|
||||
expectedPrefix := []byte(path.Child(""))
|
||||
scan, err := hrpc.NewScanRange(ctx, store.table, expectedPrefix, nil, hrpc.Families(family))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner := store.Client.Scan(scan)
|
||||
defer scanner.Close()
|
||||
for {
|
||||
res, err := scanner.Next()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(res.Cells) == 0 {
|
||||
continue
|
||||
}
|
||||
cell := res.Cells[0]
|
||||
|
||||
if !bytes.HasPrefix(cell.Row, expectedPrefix) {
|
||||
break
|
||||
}
|
||||
fullpath := util.FullPath(cell.Row)
|
||||
dir, _ := fullpath.DirAndName()
|
||||
if dir != string(path) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = store.doDelete(ctx, store.cfMetaDir, cell.Row)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (store *HbaseStore) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int) ([]*filer.Entry, error) {
|
||||
return store.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, "")
|
||||
}
|
||||
|
||||
func (store *HbaseStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) ([]*filer.Entry, error) {
|
||||
family := map[string][]string{store.cfMetaDir: {COLUMN_NAME}}
|
||||
expectedPrefix := []byte(dirPath.Child(prefix))
|
||||
scan, err := hrpc.NewScanRange(ctx, store.table, expectedPrefix, nil, hrpc.Families(family))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var entries []*filer.Entry
|
||||
scanner := store.Client.Scan(scan)
|
||||
defer scanner.Close()
|
||||
for {
|
||||
res, err := scanner.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return entries, err
|
||||
}
|
||||
if len(res.Cells) == 0 {
|
||||
continue
|
||||
}
|
||||
cell := res.Cells[0]
|
||||
|
||||
if !bytes.HasPrefix(cell.Row, expectedPrefix) {
|
||||
break
|
||||
}
|
||||
|
||||
fullpath := util.FullPath(cell.Row)
|
||||
dir, fileName := fullpath.DirAndName()
|
||||
if dir != string(dirPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
value := cell.Value
|
||||
|
||||
if fileName == startFileName && !includeStartFile {
|
||||
continue
|
||||
}
|
||||
|
||||
limit--
|
||||
if limit < 0 {
|
||||
break
|
||||
}
|
||||
entry := &filer.Entry{
|
||||
FullPath: fullpath,
|
||||
}
|
||||
if decodeErr := entry.DecodeAttributesAndChunks(util.MaybeDecompressData(value)); decodeErr != nil {
|
||||
err = decodeErr
|
||||
glog.V(0).Infof("list %s : %v", entry.FullPath, err)
|
||||
break
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) CommitTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) RollbackTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) Shutdown() {
|
||||
store.Client.Close()
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package hbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/tsuna/gohbase/hrpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
const(
|
||||
COLUMN_NAME = "a"
|
||||
)
|
||||
func (store *HbaseStore) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
return store.doPut(ctx, store.cfKv, key, value, 0)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
return store.doGet(ctx, store.cfKv, key)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
return store.doDelete(ctx, store.cfKv, key)
|
||||
}
|
||||
|
||||
func (store *HbaseStore) doPut(ctx context.Context, cf string, key, value []byte, ttlSecond int32) (err error) {
|
||||
if ttlSecond > 0 {
|
||||
return store.doPutWithOptions(ctx, cf, key, value, hrpc.Durability(hrpc.AsyncWal), hrpc.TTL(time.Duration(ttlSecond)*time.Second))
|
||||
}
|
||||
return store.doPutWithOptions(ctx, cf, key, value, hrpc.Durability(hrpc.AsyncWal))
|
||||
}
|
||||
|
||||
func (store *HbaseStore) doPutWithOptions(ctx context.Context, cf string, key, value []byte, options ...func(hrpc.Call) error) (err error) {
|
||||
values := map[string]map[string][]byte{cf: map[string][]byte{}}
|
||||
values[cf][COLUMN_NAME] = value
|
||||
putRequest, err := hrpc.NewPut(ctx, store.table, key, values, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = store.Client.Put(putRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) doGet(ctx context.Context, cf string, key []byte) (value []byte, err error) {
|
||||
family := map[string][]string{cf: {COLUMN_NAME}}
|
||||
getRequest, err := hrpc.NewGet(context.Background(), store.table, key, hrpc.Families(family))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getResp, err := store.Client.Get(getRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(getResp.Cells) == 0 {
|
||||
return nil, filer.ErrKvNotFound
|
||||
}
|
||||
|
||||
return getResp.Cells[0].Value, nil
|
||||
}
|
||||
|
||||
func (store *HbaseStore) doDelete(ctx context.Context, cf string, key []byte) (err error) {
|
||||
values := map[string]map[string][]byte{cf: map[string][]byte{}}
|
||||
values[cf][COLUMN_NAME] = nil
|
||||
deleteRequest, err := hrpc.NewDel(ctx, store.table, key, values, hrpc.Durability(hrpc.AsyncWal))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = store.Client.Delete(deleteRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -162,14 +162,14 @@ func (store *LevelDBStore) DeleteFolderChildren(ctx context.Context, fullpath we
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *LevelDBStore) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer.Entry, err error) {
|
||||
return nil, filer.ErrUnsupportedListDirectoryPrefixed
|
||||
}
|
||||
|
||||
func (store *LevelDBStore) ListDirectoryEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool,
|
||||
limit int) (entries []*filer.Entry, err error) {
|
||||
return store.ListDirectoryPrefixedEntries(ctx, fullpath, startFileName, inclusive, limit, "")
|
||||
}
|
||||
|
||||
directoryPrefix := genDirectoryKeyPrefix(fullpath, "")
|
||||
func (store *LevelDBStore) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer.Entry, err error) {
|
||||
|
||||
directoryPrefix := genDirectoryKeyPrefix(fullpath, prefix)
|
||||
|
||||
iter := store.db.NewIterator(&leveldb_util.Range{Start: genDirectoryKeyPrefix(fullpath, startFileName)}, nil)
|
||||
for iter.Next() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", nil)
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir, _ := ioutil.TempDir("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDBStore{}
|
||||
@@ -65,7 +65,7 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", nil)
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir, _ := ioutil.TempDir("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDBStore{}
|
||||
|
||||
@@ -171,14 +171,14 @@ func (store *LevelDB2Store) DeleteFolderChildren(ctx context.Context, fullpath w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *LevelDB2Store) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer.Entry, err error) {
|
||||
return nil, filer.ErrUnsupportedListDirectoryPrefixed
|
||||
}
|
||||
|
||||
func (store *LevelDB2Store) ListDirectoryEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool,
|
||||
limit int) (entries []*filer.Entry, err error) {
|
||||
return store.ListDirectoryPrefixedEntries(ctx, fullpath, startFileName, inclusive, limit, "")
|
||||
}
|
||||
|
||||
directoryPrefix, partitionId := genDirectoryKeyPrefix(fullpath, "", store.dbCount)
|
||||
func (store *LevelDB2Store) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer.Entry, err error) {
|
||||
|
||||
directoryPrefix, partitionId := genDirectoryKeyPrefix(fullpath, prefix, store.dbCount)
|
||||
lastFileStart, _ := genDirectoryKeyPrefix(fullpath, startFileName, store.dbCount)
|
||||
|
||||
iter := store.dbs[partitionId].NewIterator(&leveldb_util.Range{Start: lastFileStart}, nil)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCreateAndFind(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", nil)
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir, _ := ioutil.TempDir("", "seaweedfs_filer_test")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB2Store{}
|
||||
@@ -65,7 +65,7 @@ func TestCreateAndFind(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmptyRoot(t *testing.T) {
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", nil)
|
||||
testFiler := filer.NewFiler(nil, nil, "", 0, "", "", "", nil)
|
||||
dir, _ := ioutil.TempDir("", "seaweedfs_filer_test2")
|
||||
defer os.RemoveAll(dir)
|
||||
store := &LevelDB2Store{}
|
||||
|
||||
@@ -141,6 +141,9 @@ func (ma *MetaAggregator) subscribeToOneFiler(f *Filer, self string, peer string
|
||||
return fmt.Errorf("process %v: %v", resp, err)
|
||||
}
|
||||
lastTsNs = resp.TsNs
|
||||
|
||||
f.onMetadataChangeEvent(resp)
|
||||
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ReadEntry(masterClient *wdclient.MasterClient, filerClient filer_pb.SeaweedFilerClient, dir, name string, byteBuffer *bytes.Buffer) error {
|
||||
|
||||
request := &filer_pb.LookupDirectoryEntryRequest{
|
||||
Directory: dir,
|
||||
Name: name,
|
||||
}
|
||||
respLookupEntry, err := filer_pb.LookupEntry(filerClient, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(respLookupEntry.Entry.Content) > 0 {
|
||||
_, err = byteBuffer.Write(respLookupEntry.Entry.Content)
|
||||
return err
|
||||
}
|
||||
|
||||
return StreamContent(masterClient, byteBuffer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64)
|
||||
|
||||
}
|
||||
|
||||
func ReadContent(filerAddress string, dir, name string) ([]byte, error) {
|
||||
|
||||
target := fmt.Sprintf("http://%s%s/%s", filerAddress, dir, name)
|
||||
|
||||
data, _, err := util.Get(target)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func SaveAs(host string, port int, dir, name string, contentType string, byteBuffer *bytes.Buffer) error {
|
||||
|
||||
target := fmt.Sprintf("http://%s:%d%s/%s", host, port, dir, name)
|
||||
|
||||
// set the HTTP method, url, and request body
|
||||
req, err := http.NewRequest(http.MethodPut, target, byteBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the request header Content-Type for json
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer util.CloseResponse(resp)
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("%s: %s %v", target, resp.Status, string(b))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func SaveInsideFiler(client filer_pb.SeaweedFilerClient, dir, name string, content []byte) error {
|
||||
|
||||
resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
|
||||
Directory: dir,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
if err == filer_pb.ErrNotFound {
|
||||
err = filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{
|
||||
Directory: dir,
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: name,
|
||||
IsDirectory: false,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(0644),
|
||||
Collection: "",
|
||||
Replication: "",
|
||||
FileSize: uint64(len(content)),
|
||||
},
|
||||
Content: content,
|
||||
},
|
||||
})
|
||||
} else if err == nil {
|
||||
entry := resp.Entry
|
||||
entry.Content = content
|
||||
entry.Attributes.Mtime = time.Now().Unix()
|
||||
entry.Attributes.FileSize = uint64(len(content))
|
||||
err = filer_pb.UpdateEntry(client, &filer_pb.UpdateEntryRequest{
|
||||
Directory: dir,
|
||||
Entry: entry,
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
+10
-3
@@ -23,12 +23,13 @@ type ChunkReadAt struct {
|
||||
fileSize int64
|
||||
|
||||
fetchGroup singleflight.Group
|
||||
chunkCache chunk_cache.ChunkCache
|
||||
lastChunkFileId string
|
||||
lastChunkData []byte
|
||||
chunkCache chunk_cache.ChunkCache
|
||||
}
|
||||
|
||||
// var _ = io.ReaderAt(&ChunkReadAt{})
|
||||
var _ = io.ReaderAt(&ChunkReadAt{})
|
||||
var _ = io.Closer(&ChunkReadAt{})
|
||||
|
||||
type LookupFileIdFunctionType func(fileId string) (targetUrls []string, err error)
|
||||
|
||||
@@ -96,6 +97,12 @@ func NewChunkReaderAtFromClient(filerClient filer_pb.FilerClient, chunkViews []*
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) Close() error {
|
||||
c.lastChunkData = nil
|
||||
c.lastChunkFileId = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) ReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
c.readerLock.Lock()
|
||||
@@ -107,7 +114,6 @@ func (c *ChunkReadAt) ReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
var buffer []byte
|
||||
startOffset, remaining := offset, int64(len(p))
|
||||
var nextChunk *ChunkView
|
||||
for i, chunk := range c.chunkViews {
|
||||
@@ -134,6 +140,7 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
glog.Errorf("fetching chunk %+v: %v\n", chunk, err)
|
||||
|
||||
@@ -28,15 +28,17 @@ func (store *RedisCluster2Store) Initialize(configuration util.Configuration, pr
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetBool(prefix+"useReadOnly"),
|
||||
configuration.GetBool(prefix+"routeByLatency"),
|
||||
configuration.GetStringSlice(prefix+"superLargeDirectories"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *RedisCluster2Store) initialize(addresses []string, password string, readOnly, routeByLatency bool) (err error) {
|
||||
func (store *RedisCluster2Store) initialize(addresses []string, password string, readOnly, routeByLatency bool, superLargeDirectories []string) (err error) {
|
||||
store.Client = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: addresses,
|
||||
Password: password,
|
||||
ReadOnly: readOnly,
|
||||
RouteByLatency: routeByLatency,
|
||||
})
|
||||
store.loadSuperLargeDirectories(superLargeDirectories)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,14 +23,16 @@ func (store *Redis2Store) Initialize(configuration util.Configuration, prefix st
|
||||
configuration.GetString(prefix+"address"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetInt(prefix+"database"),
|
||||
configuration.GetStringSlice(prefix+"superLargeDirectories"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *Redis2Store) initialize(hostPort string, password string, database int) (err error) {
|
||||
func (store *Redis2Store) initialize(hostPort string, password string, database int, superLargeDirectories []string) (err error) {
|
||||
store.Client = redis.NewClient(&redis.Options{
|
||||
Addr: hostPort,
|
||||
Password: password,
|
||||
DB: database,
|
||||
})
|
||||
store.loadSuperLargeDirectories(superLargeDirectories)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -18,7 +18,21 @@ const (
|
||||
)
|
||||
|
||||
type UniversalRedis2Store struct {
|
||||
Client redis.UniversalClient
|
||||
Client redis.UniversalClient
|
||||
superLargeDirectoryHash map[string]bool
|
||||
}
|
||||
|
||||
func (store *UniversalRedis2Store) isSuperLargeDirectory(dir string) (isSuperLargeDirectory bool) {
|
||||
_, isSuperLargeDirectory = store.superLargeDirectoryHash[dir]
|
||||
return
|
||||
}
|
||||
|
||||
func (store *UniversalRedis2Store) loadSuperLargeDirectories(superLargeDirectories []string) {
|
||||
// set directory hash
|
||||
store.superLargeDirectoryHash = make(map[string]bool)
|
||||
for _, dir := range superLargeDirectories {
|
||||
store.superLargeDirectoryHash[dir] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (store *UniversalRedis2Store) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
@@ -47,6 +61,10 @@ func (store *UniversalRedis2Store) InsertEntry(ctx context.Context, entry *filer
|
||||
}
|
||||
|
||||
dir, name := entry.FullPath.DirAndName()
|
||||
if store.isSuperLargeDirectory(dir) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
if err = store.Client.ZAddNX(genDirectoryListKey(dir), redis.Z{Score: 0, Member: name}).Err(); err != nil {
|
||||
return fmt.Errorf("persisting %s in parent dir: %v", entry.FullPath, err)
|
||||
@@ -96,6 +114,9 @@ func (store *UniversalRedis2Store) DeleteEntry(ctx context.Context, fullpath uti
|
||||
}
|
||||
|
||||
dir, name := fullpath.DirAndName()
|
||||
if store.isSuperLargeDirectory(dir) {
|
||||
return nil
|
||||
}
|
||||
if name != "" {
|
||||
_, err = store.Client.ZRem(genDirectoryListKey(dir), name).Result()
|
||||
if err != nil {
|
||||
@@ -108,6 +129,10 @@ func (store *UniversalRedis2Store) DeleteEntry(ctx context.Context, fullpath uti
|
||||
|
||||
func (store *UniversalRedis2Store) DeleteFolderChildren(ctx context.Context, fullpath util.FullPath) (err error) {
|
||||
|
||||
if store.isSuperLargeDirectory(string(fullpath)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
members, err := store.Client.ZRange(genDirectoryListKey(string(fullpath)), 0, -1).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteFolderChildren %s : %v", fullpath, err)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ParseS3ConfigurationFromBytes(content []byte, config *iam_pb.S3ApiConfiguration) error {
|
||||
if err := jsonpb.Unmarshal(bytes.NewBuffer(content), config); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func S3ConfigurationToText(writer io.Writer, config *iam_pb.S3ApiConfiguration) error {
|
||||
|
||||
m := jsonpb.Marshaler{
|
||||
EmitDefaults: false,
|
||||
Indent: " ",
|
||||
}
|
||||
|
||||
return m.Marshal(writer, config)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||
"testing"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestS3Conf(t *testing.T) {
|
||||
s3Conf := &iam_pb.S3ApiConfiguration{
|
||||
Identities: []*iam_pb.Identity{
|
||||
{
|
||||
Name: "some_name",
|
||||
Credentials: []*iam_pb.Credential{
|
||||
{
|
||||
AccessKey: "some_access_key1",
|
||||
SecretKey: "some_secret_key1",
|
||||
},
|
||||
},
|
||||
Actions: []string{
|
||||
ACTION_ADMIN,
|
||||
ACTION_READ,
|
||||
ACTION_WRITE,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "some_read_only_user",
|
||||
Credentials: []*iam_pb.Credential{
|
||||
{
|
||||
AccessKey: "some_access_key2",
|
||||
SecretKey: "some_secret_key2",
|
||||
},
|
||||
},
|
||||
Actions: []string{
|
||||
ACTION_READ,
|
||||
ACTION_TAGGING,
|
||||
ACTION_LIST,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := S3ConfigurationToText(&buf, s3Conf)
|
||||
assert.Equal(t, err, nil)
|
||||
s3ConfSaved := &iam_pb.S3ApiConfiguration{}
|
||||
err = ParseS3ConfigurationFromBytes(buf.Bytes(), s3ConfSaved)
|
||||
assert.Equal(t, err, nil)
|
||||
|
||||
assert.Equal(t, "some_name", s3ConfSaved.Identities[0].Name)
|
||||
assert.Equal(t, "some_read_only_user", s3ConfSaved.Identities[1].Name)
|
||||
assert.Equal(t, "some_access_key1", s3ConfSaved.Identities[0].Credentials[0].AccessKey)
|
||||
assert.Equal(t, "some_secret_key2", s3ConfSaved.Identities[1].Credentials[0].SecretKey)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirector
|
||||
_, err := client.AtomicRenameEntry(ctx, request)
|
||||
if err != nil {
|
||||
glog.Errorf("dir AtomicRenameEntry %s => %s : %v", oldPath, newPath, err)
|
||||
return fuse.EIO
|
||||
return fuse.EXDEV
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -294,7 +294,7 @@ func (file *File) addChunks(chunks []*filer_pb.FileChunk) {
|
||||
// find the earliest incoming chunk
|
||||
newChunks := chunks
|
||||
earliestChunk := newChunks[0]
|
||||
for i:=1;i<len(newChunks);i++{
|
||||
for i := 1; i < len(newChunks); i++ {
|
||||
if lessThan(earliestChunk, newChunks[i]) {
|
||||
earliestChunk = newChunks[i]
|
||||
}
|
||||
|
||||
@@ -110,6 +110,12 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if offset+int64(len(buff)) <= int64(len(fh.f.entry.Content)) {
|
||||
totalRead := copy(buff, fh.f.entry.Content[offset:])
|
||||
glog.V(4).Infof("file handle read cached %s [%d,%d] %d", fh.f.fullpath(), offset, offset+int64(totalRead), totalRead)
|
||||
return int64(totalRead), nil
|
||||
}
|
||||
|
||||
var chunkResolveErr error
|
||||
if fh.f.entryViewCache == nil {
|
||||
fh.f.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(filer.LookupFn(fh.f.wfs), fh.f.entry.Chunks)
|
||||
@@ -149,6 +155,7 @@ func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *f
|
||||
copy(data, req.Data)
|
||||
}
|
||||
|
||||
fh.f.entry.Content = nil
|
||||
fh.f.entry.Attributes.FileSize = uint64(max(req.Offset+int64(len(data)), int64(fh.f.entry.Attributes.FileSize)))
|
||||
glog.V(4).Infof("%v write [%d,%d) %d", fh.f.fullpath(), req.Offset, req.Offset+int64(len(req.Data)), len(req.Data))
|
||||
|
||||
@@ -195,6 +202,10 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err
|
||||
}
|
||||
|
||||
fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle))
|
||||
if closer, ok := fh.f.reader.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
fh.f.reader = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -12,7 +12,7 @@ var _ = filer_pb.FilerClient(&WFS{})
|
||||
|
||||
func (wfs *WFS) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error) error {
|
||||
|
||||
err := util.Retry("filer grpc", func() error {
|
||||
err := util.Retry("filer grpc "+wfs.option.FilerGrpcAddress, func() error {
|
||||
return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error {
|
||||
client := filer_pb.NewSeaweedFilerClient(grpcConnection)
|
||||
return fn(client)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/master_pb"
|
||||
@@ -49,14 +50,14 @@ func Assign(server string, grpcDialOption grpc.DialOption, primaryRequest *Volum
|
||||
lastError = WithMasterServerClient(server, grpcDialOption, func(masterClient master_pb.SeaweedClient) error {
|
||||
|
||||
req := &master_pb.AssignRequest{
|
||||
Count: primaryRequest.Count,
|
||||
Replication: primaryRequest.Replication,
|
||||
Collection: primaryRequest.Collection,
|
||||
Ttl: primaryRequest.Ttl,
|
||||
DataCenter: primaryRequest.DataCenter,
|
||||
Rack: primaryRequest.Rack,
|
||||
DataNode: primaryRequest.DataNode,
|
||||
WritableVolumeCount: primaryRequest.WritableVolumeCount,
|
||||
Count: request.Count,
|
||||
Replication: request.Replication,
|
||||
Collection: request.Collection,
|
||||
Ttl: request.Ttl,
|
||||
DataCenter: request.DataCenter,
|
||||
Rack: request.Rack,
|
||||
DataNode: request.DataNode,
|
||||
WritableVolumeCount: request.WritableVolumeCount,
|
||||
}
|
||||
resp, grpcErr := masterClient.Assign(context.Background(), req)
|
||||
if grpcErr != nil {
|
||||
@@ -101,3 +102,41 @@ func LookupJwt(master string, fileId string) security.EncodedJwt {
|
||||
|
||||
return security.EncodedJwt(tokenStr)
|
||||
}
|
||||
|
||||
type StorageOption struct {
|
||||
Replication string
|
||||
Collection string
|
||||
DataCenter string
|
||||
Rack string
|
||||
TtlSeconds int32
|
||||
Fsync bool
|
||||
VolumeGrowthCount uint32
|
||||
}
|
||||
|
||||
func (so *StorageOption) TtlString() string {
|
||||
return needle.SecondsToTTL(so.TtlSeconds)
|
||||
}
|
||||
|
||||
func (so *StorageOption) ToAssignRequests(count int) (ar *VolumeAssignRequest, altRequest *VolumeAssignRequest) {
|
||||
ar = &VolumeAssignRequest{
|
||||
Count: uint64(count),
|
||||
Replication: so.Replication,
|
||||
Collection: so.Collection,
|
||||
Ttl: so.TtlString(),
|
||||
DataCenter: so.DataCenter,
|
||||
Rack: so.Rack,
|
||||
WritableVolumeCount: so.VolumeGrowthCount,
|
||||
}
|
||||
if so.DataCenter != "" || so.Rack != "" {
|
||||
altRequest = &VolumeAssignRequest{
|
||||
Count: uint64(count),
|
||||
Replication: so.Replication,
|
||||
Collection: so.Collection,
|
||||
Ttl: so.TtlString(),
|
||||
DataCenter: "",
|
||||
Rack: "",
|
||||
WritableVolumeCount: so.VolumeGrowthCount,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,29 +75,31 @@ func TestCreateNeedleFromRequest(t *testing.T) {
|
||||
Upload("http://localhost:8080/389,0f084d17353afda0", "t.txt", false, bytes.NewReader(gzippedData), true, "text/plain", nil, "")
|
||||
}
|
||||
|
||||
{
|
||||
mc.needleHandling = func(n *needle.Needle, originalSize int, err error) {
|
||||
assert.Equal(t, nil, err, "upload: %v", err)
|
||||
assert.Equal(t, "text/plain", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, true, n.IsCompressed(), "this should be compressed")
|
||||
assert.Equal(t, true, util.IsZstdContent(n.Data), "this should be zstd")
|
||||
fmt.Printf("needle: %v, dataSize:%d originalSize:%d\n", n, len(n.Data), originalSize)
|
||||
/*
|
||||
{
|
||||
mc.needleHandling = func(n *needle.Needle, originalSize int, err error) {
|
||||
assert.Equal(t, nil, err, "upload: %v", err)
|
||||
assert.Equal(t, "text/plain", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, true, n.IsCompressed(), "this should be compressed")
|
||||
assert.Equal(t, true, util.IsZstdContent(n.Data), "this should be zstd")
|
||||
fmt.Printf("needle: %v, dataSize:%d originalSize:%d\n", n, len(n.Data), originalSize)
|
||||
}
|
||||
zstdData, _ := util.ZstdData([]byte(textContent))
|
||||
Upload("http://localhost:8080/389,0f084d17353afda0", "t.txt", false, bytes.NewReader(zstdData), true, "text/plain", nil, "")
|
||||
}
|
||||
zstdData, _ := util.ZstdData([]byte(textContent))
|
||||
Upload("http://localhost:8080/389,0f084d17353afda0", "t.txt", false, bytes.NewReader(zstdData), true, "text/plain", nil, "")
|
||||
}
|
||||
|
||||
{
|
||||
mc.needleHandling = func(n *needle.Needle, originalSize int, err error) {
|
||||
assert.Equal(t, nil, err, "upload: %v", err)
|
||||
assert.Equal(t, "application/zstd", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, false, n.IsCompressed(), "this should not be compressed")
|
||||
assert.Equal(t, true, util.IsZstdContent(n.Data), "this should still be zstd")
|
||||
fmt.Printf("needle: %v, dataSize:%d originalSize:%d\n", n, len(n.Data), originalSize)
|
||||
{
|
||||
mc.needleHandling = func(n *needle.Needle, originalSize int, err error) {
|
||||
assert.Equal(t, nil, err, "upload: %v", err)
|
||||
assert.Equal(t, "application/zstd", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, false, n.IsCompressed(), "this should not be compressed")
|
||||
assert.Equal(t, true, util.IsZstdContent(n.Data), "this should still be zstd")
|
||||
fmt.Printf("needle: %v, dataSize:%d originalSize:%d\n", n, len(n.Data), originalSize)
|
||||
}
|
||||
zstdData, _ := util.ZstdData([]byte(textContent))
|
||||
Upload("http://localhost:8080/389,0f084d17353afda0", "t.txt", false, bytes.NewReader(zstdData), false, "application/zstd", nil, "")
|
||||
}
|
||||
zstdData, _ := util.ZstdData([]byte(textContent))
|
||||
Upload("http://localhost:8080/389,0f084d17353afda0", "t.txt", false, bytes.NewReader(zstdData), false, "application/zstd", nil, "")
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ func doUploadData(uploadUrl string, filename string, cipher bool, data []byte, i
|
||||
uploadResult, err = upload_content(uploadUrl, func(w io.Writer) (err error) {
|
||||
_, err = w.Write(data)
|
||||
return
|
||||
}, filename, contentIsGzipped, 0, mtype, pairMap, jwt)
|
||||
}, filename, contentIsGzipped, len(data), mtype, pairMap, jwt)
|
||||
}
|
||||
|
||||
if uploadResult == nil {
|
||||
@@ -190,6 +190,7 @@ func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error
|
||||
body_writer := multipart.NewWriter(buf)
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fileNameEscaper.Replace(filename)))
|
||||
h.Set("Idempotency-Key", uploadUrl)
|
||||
if mtype == "" {
|
||||
mtype = mime.TypeByExtension(strings.ToLower(filepath.Ext(filename)))
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ message Entry {
|
||||
map<string, bytes> extended = 5;
|
||||
bytes hard_link_id = 7;
|
||||
int32 hard_link_counter = 8; // only exists in hard link meta data
|
||||
bytes content = 9; // if not empty, the file content
|
||||
}
|
||||
|
||||
message FullEntry {
|
||||
@@ -348,3 +349,23 @@ message KvPutRequest {
|
||||
message KvPutResponse {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
// path-based configurations
|
||||
message FilerConf {
|
||||
int32 version = 1;
|
||||
message PathConf {
|
||||
string location_prefix = 1;
|
||||
string collection = 2;
|
||||
string replication = 3;
|
||||
string ttl = 4;
|
||||
enum DiskType {
|
||||
NONE = 0;
|
||||
HDD = 1;
|
||||
SSD = 2;
|
||||
}
|
||||
DiskType disk_type = 5;
|
||||
bool fsync = 6;
|
||||
uint32 volume_growth_count = 7;
|
||||
}
|
||||
repeated PathConf locations = 2;
|
||||
}
|
||||
|
||||
+815
-549
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -61,64 +60,88 @@ type EachEntryFunciton func(entry *Entry, isLast bool) error
|
||||
|
||||
func ReadDirAllEntries(filerClient FilerClient, fullDirPath util.FullPath, prefix string, fn EachEntryFunciton) (err error) {
|
||||
|
||||
return doList(filerClient, fullDirPath, prefix, fn, "", false, math.MaxUint32)
|
||||
var counter uint32
|
||||
var startFrom string
|
||||
var counterFunc = func(entry *Entry, isLast bool) error {
|
||||
counter++
|
||||
startFrom = entry.Name
|
||||
return fn(entry, isLast)
|
||||
}
|
||||
|
||||
var paginationLimit uint32 = 10000
|
||||
|
||||
if err = doList(filerClient, fullDirPath, prefix, counterFunc, "", false, paginationLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for counter == paginationLimit {
|
||||
counter = 0
|
||||
if err = doList(filerClient, fullDirPath, prefix, counterFunc, startFrom, false, paginationLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func List(filerClient FilerClient, parentDirectoryPath, prefix string, fn EachEntryFunciton, startFrom string, inclusive bool, limit uint32) (err error) {
|
||||
|
||||
return doList(filerClient, util.FullPath(parentDirectoryPath), prefix, fn, startFrom, inclusive, limit)
|
||||
|
||||
return filerClient.WithFilerClient(func(client SeaweedFilerClient) error {
|
||||
return doSeaweedList(client, util.FullPath(parentDirectoryPath), prefix, fn, startFrom, inclusive, limit)
|
||||
})
|
||||
}
|
||||
|
||||
func doList(filerClient FilerClient, fullDirPath util.FullPath, prefix string, fn EachEntryFunciton, startFrom string, inclusive bool, limit uint32) (err error) {
|
||||
|
||||
err = filerClient.WithFilerClient(func(client SeaweedFilerClient) error {
|
||||
|
||||
request := &ListEntriesRequest{
|
||||
Directory: string(fullDirPath),
|
||||
Prefix: prefix,
|
||||
StartFromFileName: startFrom,
|
||||
Limit: limit,
|
||||
InclusiveStartFrom: inclusive,
|
||||
}
|
||||
|
||||
glog.V(4).Infof("read directory: %v", request)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
stream, err := client.ListEntries(ctx, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list %s: %v", fullDirPath, err)
|
||||
}
|
||||
|
||||
var prevEntry *Entry
|
||||
for {
|
||||
resp, recvErr := stream.Recv()
|
||||
if recvErr != nil {
|
||||
if recvErr == io.EOF {
|
||||
if prevEntry != nil {
|
||||
if err := fn(prevEntry, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
} else {
|
||||
return recvErr
|
||||
}
|
||||
}
|
||||
if prevEntry != nil {
|
||||
if err := fn(prevEntry, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
prevEntry = resp.Entry
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
return filerClient.WithFilerClient(func(client SeaweedFilerClient) error {
|
||||
return doSeaweedList(client, fullDirPath, prefix, fn, startFrom, inclusive, limit)
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
func SeaweedList(client SeaweedFilerClient, parentDirectoryPath, prefix string, fn EachEntryFunciton, startFrom string, inclusive bool, limit uint32) (err error) {
|
||||
return doSeaweedList(client, util.FullPath(parentDirectoryPath), prefix, fn, startFrom, inclusive, limit)
|
||||
}
|
||||
|
||||
func doSeaweedList(client SeaweedFilerClient, fullDirPath util.FullPath, prefix string, fn EachEntryFunciton, startFrom string, inclusive bool, limit uint32) (err error) {
|
||||
|
||||
request := &ListEntriesRequest{
|
||||
Directory: string(fullDirPath),
|
||||
Prefix: prefix,
|
||||
StartFromFileName: startFrom,
|
||||
Limit: limit,
|
||||
InclusiveStartFrom: inclusive,
|
||||
}
|
||||
|
||||
glog.V(4).Infof("read directory: %v", request)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
stream, err := client.ListEntries(ctx, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list %s: %v", fullDirPath, err)
|
||||
}
|
||||
|
||||
var prevEntry *Entry
|
||||
for {
|
||||
resp, recvErr := stream.Recv()
|
||||
if recvErr != nil {
|
||||
if recvErr == io.EOF {
|
||||
if prevEntry != nil {
|
||||
if err := fn(prevEntry, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
} else {
|
||||
return recvErr
|
||||
}
|
||||
}
|
||||
if prevEntry != nil {
|
||||
if err := fn(prevEntry, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
prevEntry = resp.Entry
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Exists(filerClient FilerClient, parentDirectoryPath string, entryName string, isDirectory bool) (exists bool, err error) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/viant/ptrie"
|
||||
)
|
||||
|
||||
func ToFileIdObject(fileIdStr string) (*FileId, error) {
|
||||
@@ -121,3 +123,27 @@ func LookupEntry(client SeaweedFilerClient, request *LookupDirectoryEntryRequest
|
||||
}
|
||||
|
||||
var ErrNotFound = errors.New("filer: no entry is found in filer store")
|
||||
|
||||
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
|
||||
}
|
||||
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 &&
|
||||
event.Directory != event.EventNotification.NewParentPath
|
||||
}
|
||||
|
||||
var _ = ptrie.KeyProvider(&FilerConf_PathConf{})
|
||||
|
||||
func (fp *FilerConf_PathConf) Key() interface{} {
|
||||
key, _ := proto.Marshal(fp)
|
||||
return string(key)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ service Seaweed {
|
||||
}
|
||||
rpc LookupEcVolume (LookupEcVolumeRequest) returns (LookupEcVolumeResponse) {
|
||||
}
|
||||
rpc VacuumVolume (VacuumVolumeRequest) returns (VacuumVolumeResponse) {
|
||||
}
|
||||
rpc GetMasterConfiguration (GetMasterConfigurationRequest) returns (GetMasterConfigurationResponse) {
|
||||
}
|
||||
rpc ListMasterClients (ListMasterClientsRequest) returns (ListMasterClientsResponse) {
|
||||
@@ -130,6 +132,7 @@ message VolumeLocation {
|
||||
repeated uint32 new_vids = 3;
|
||||
repeated uint32 deleted_vids = 4;
|
||||
string leader = 5; // optional when leader is not itself
|
||||
string data_center = 6; // optional when DataCenter is in use
|
||||
}
|
||||
|
||||
message LookupVolumeRequest {
|
||||
@@ -187,11 +190,6 @@ message StatisticsResponse {
|
||||
//
|
||||
// collection related
|
||||
//
|
||||
|
||||
message StorageType {
|
||||
string replication = 1;
|
||||
string ttl = 2;
|
||||
}
|
||||
message Collection {
|
||||
string name = 1;
|
||||
}
|
||||
@@ -268,6 +266,12 @@ message LookupEcVolumeResponse {
|
||||
repeated EcShardIdLocation shard_id_locations = 2;
|
||||
}
|
||||
|
||||
message VacuumVolumeRequest {
|
||||
float garbage_threshold = 1;
|
||||
}
|
||||
message VacuumVolumeResponse {
|
||||
}
|
||||
|
||||
message GetMasterConfigurationRequest {
|
||||
}
|
||||
message GetMasterConfigurationResponse {
|
||||
|
||||
+645
-544
File diff suppressed because it is too large
Load Diff
@@ -112,6 +112,7 @@ func (fs *FilerSink) CreateEntry(key string, entry *filer_pb.Entry, signatures [
|
||||
IsDirectory: entry.IsDirectory,
|
||||
Attributes: entry.Attributes,
|
||||
Chunks: replicatedChunks,
|
||||
Content: entry.Content,
|
||||
},
|
||||
IsFromOtherCluster: true,
|
||||
Signatures: signatures,
|
||||
|
||||
+110
-44
@@ -1,28 +1,20 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
|
||||
"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/s3err"
|
||||
)
|
||||
|
||||
type Action string
|
||||
|
||||
const (
|
||||
ACTION_READ = "Read"
|
||||
ACTION_WRITE = "Write"
|
||||
ACTION_ADMIN = "Admin"
|
||||
ACTION_TAGGING = "Tagging"
|
||||
ACTION_LIST = "List"
|
||||
)
|
||||
|
||||
type Iam interface {
|
||||
Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
|
||||
}
|
||||
@@ -43,36 +35,54 @@ type Credential struct {
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
func NewIdentityAccessManagement(fileName string, domain string) *IdentityAccessManagement {
|
||||
func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement {
|
||||
iam := &IdentityAccessManagement{
|
||||
domain: domain,
|
||||
domain: option.DomainName,
|
||||
}
|
||||
if fileName == "" {
|
||||
return iam
|
||||
}
|
||||
if err := iam.loadS3ApiConfiguration(fileName); err != nil {
|
||||
glog.Fatalf("fail to load config file %s: %v", fileName, err)
|
||||
if option.Config != "" {
|
||||
if err := iam.loadS3ApiConfigurationFromFile(option.Config); err != nil {
|
||||
glog.Fatalf("fail to load config file %s: %v", option.Config, err)
|
||||
}
|
||||
} else {
|
||||
if err := iam.loadS3ApiConfigurationFromFiler(option); err != nil {
|
||||
glog.Warningf("fail to load config: %v", err)
|
||||
}
|
||||
}
|
||||
return iam
|
||||
}
|
||||
|
||||
func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) error {
|
||||
func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3ApiServerOption) error {
|
||||
content, err := filer.ReadContent(option.Filer, filer.IamConfigDirecotry, filer.IamIdentityFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read S3 config: %v", err)
|
||||
}
|
||||
return iam.loadS3ApiConfigurationFromBytes(content)
|
||||
}
|
||||
|
||||
s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}
|
||||
|
||||
rawData, readErr := ioutil.ReadFile(fileName)
|
||||
func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error {
|
||||
content, readErr := ioutil.ReadFile(fileName)
|
||||
if readErr != nil {
|
||||
glog.Warningf("fail to read %s : %v", fileName, readErr)
|
||||
return fmt.Errorf("fail to read %s : %v", fileName, readErr)
|
||||
}
|
||||
return iam.loadS3ApiConfigurationFromBytes(content)
|
||||
}
|
||||
|
||||
glog.V(1).Infof("load s3 config: %v", fileName)
|
||||
if err := jsonpb.Unmarshal(bytes.NewReader(rawData), s3ApiConfiguration); err != nil {
|
||||
func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromBytes(content []byte) error {
|
||||
s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}
|
||||
if err := filer.ParseS3ConfigurationFromBytes(content, s3ApiConfiguration); err != nil {
|
||||
glog.Warningf("unmarshal error: %v", err)
|
||||
return fmt.Errorf("unmarshal %s error: %v", fileName, err)
|
||||
return fmt.Errorf("unmarshal error: %v", err)
|
||||
}
|
||||
if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ident := range s3ApiConfiguration.Identities {
|
||||
func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
|
||||
var identities []*Identity
|
||||
for _, ident := range config.Identities {
|
||||
t := &Identity{
|
||||
Name: ident.Name,
|
||||
Credentials: nil,
|
||||
@@ -87,9 +97,11 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(fileName string) err
|
||||
SecretKey: cred.SecretKey,
|
||||
})
|
||||
}
|
||||
iam.identities = append(iam.identities, t)
|
||||
identities = append(identities, t)
|
||||
}
|
||||
|
||||
// atomically switch
|
||||
iam.identities = identities
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,8 +139,14 @@ func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) htt
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
errCode := iam.authRequest(r, action)
|
||||
identity, errCode := iam.authRequest(r, action)
|
||||
if errCode == s3err.ErrNone {
|
||||
if identity != nil && identity.Name != "" {
|
||||
r.Header.Set(xhttp.AmzIdentityId, identity.Name)
|
||||
if identity.isAdmin() {
|
||||
r.Header.Set(xhttp.AmzIsAdmin, "true")
|
||||
}
|
||||
}
|
||||
f(w, r)
|
||||
return
|
||||
}
|
||||
@@ -137,16 +155,16 @@ func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) htt
|
||||
}
|
||||
|
||||
// check whether the request has valid access keys
|
||||
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) s3err.ErrorCode {
|
||||
func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) (*Identity, s3err.ErrorCode) {
|
||||
var identity *Identity
|
||||
var s3Err s3err.ErrorCode
|
||||
var found bool
|
||||
switch getRequestAuthType(r) {
|
||||
case authTypeStreamingSigned:
|
||||
return s3err.ErrNone
|
||||
return identity, s3err.ErrNone
|
||||
case authTypeUnknown:
|
||||
glog.V(3).Infof("unknown auth type")
|
||||
return s3err.ErrAccessDenied
|
||||
return identity, s3err.ErrAccessDenied
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
glog.V(3).Infof("v2 auth type")
|
||||
identity, s3Err = iam.isReqAuthenticatedV2(r)
|
||||
@@ -155,22 +173,21 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
|
||||
identity, s3Err = iam.reqSignatureV4Verify(r)
|
||||
case authTypePostPolicy:
|
||||
glog.V(3).Infof("post policy auth type")
|
||||
return s3err.ErrNone
|
||||
return identity, s3err.ErrNone
|
||||
case authTypeJWT:
|
||||
glog.V(3).Infof("jwt auth type")
|
||||
return s3err.ErrNotImplemented
|
||||
return identity, s3err.ErrNotImplemented
|
||||
case authTypeAnonymous:
|
||||
identity, found = iam.lookupAnonymous()
|
||||
if !found {
|
||||
return s3err.ErrAccessDenied
|
||||
return identity, s3err.ErrAccessDenied
|
||||
}
|
||||
default:
|
||||
return s3err.ErrNotImplemented
|
||||
return identity, s3err.ErrNotImplemented
|
||||
}
|
||||
|
||||
glog.V(3).Infof("auth error: %v", s3Err)
|
||||
if s3Err != s3err.ErrNone {
|
||||
return s3Err
|
||||
return identity, s3Err
|
||||
}
|
||||
|
||||
glog.V(3).Infof("user name: %v actions: %v", identity.Name, identity.Actions)
|
||||
@@ -178,18 +195,54 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
|
||||
if !identity.canDo(action, bucket) {
|
||||
return s3err.ErrAccessDenied
|
||||
return identity, s3err.ErrAccessDenied
|
||||
}
|
||||
|
||||
return s3err.ErrNone
|
||||
return identity, s3err.ErrNone
|
||||
|
||||
}
|
||||
|
||||
func (identity *Identity) canDo(action Action, bucket string) bool {
|
||||
for _, a := range identity.Actions {
|
||||
if a == "Admin" {
|
||||
return true
|
||||
func (iam *IdentityAccessManagement) authUser(r *http.Request) (*Identity, s3err.ErrorCode) {
|
||||
var identity *Identity
|
||||
var s3Err s3err.ErrorCode
|
||||
var found bool
|
||||
switch getRequestAuthType(r) {
|
||||
case authTypeStreamingSigned:
|
||||
return identity, s3err.ErrNone
|
||||
case authTypeUnknown:
|
||||
glog.V(3).Infof("unknown auth type")
|
||||
return identity, s3err.ErrAccessDenied
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
glog.V(3).Infof("v2 auth type")
|
||||
identity, s3Err = iam.isReqAuthenticatedV2(r)
|
||||
case authTypeSigned, authTypePresigned:
|
||||
glog.V(3).Infof("v4 auth type")
|
||||
identity, s3Err = iam.reqSignatureV4Verify(r)
|
||||
case authTypePostPolicy:
|
||||
glog.V(3).Infof("post policy auth type")
|
||||
return identity, s3err.ErrNone
|
||||
case authTypeJWT:
|
||||
glog.V(3).Infof("jwt auth type")
|
||||
return identity, s3err.ErrNotImplemented
|
||||
case authTypeAnonymous:
|
||||
identity, found = iam.lookupAnonymous()
|
||||
if !found {
|
||||
return identity, s3err.ErrAccessDenied
|
||||
}
|
||||
default:
|
||||
return identity, s3err.ErrNotImplemented
|
||||
}
|
||||
|
||||
glog.V(3).Infof("auth error: %v", s3Err)
|
||||
if s3Err != s3err.ErrNone {
|
||||
return identity, s3Err
|
||||
}
|
||||
return identity, s3err.ErrNone
|
||||
}
|
||||
|
||||
func (identity *Identity) canDo(action Action, bucket string) bool {
|
||||
if identity.isAdmin() {
|
||||
return true
|
||||
}
|
||||
for _, a := range identity.Actions {
|
||||
if a == action {
|
||||
@@ -200,10 +253,23 @@ func (identity *Identity) canDo(action Action, bucket string) bool {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (identity *Identity) isAdmin() bool {
|
||||
for _, a := range identity.Actions {
|
||||
if a == "Admin" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s3a *S3ApiServer) subscribeMetaEvents(clientName string, prefix string, lastTsNs int64) error {
|
||||
|
||||
processEventFn := func(resp *filer_pb.SubscribeMetadataResponse) error {
|
||||
|
||||
message := resp.EventNotification
|
||||
if message.NewEntry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir := resp.Directory
|
||||
|
||||
if message.NewParentPath != "" {
|
||||
dir = message.NewParentPath
|
||||
}
|
||||
if dir == filer.IamConfigDirecotry && message.NewEntry.Name == filer.IamIdentityFile {
|
||||
if err := s3a.iam.loadS3ApiConfigurationFromBytes(message.NewEntry.Content); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(0).Infof("updated %s/%s", filer.IamConfigDirecotry, filer.IamIdentityFile)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
err := s3a.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
stream, err := client.SubscribeMetadata(ctx, &filer_pb.SubscribeMetadataRequest{
|
||||
ClientName: clientName,
|
||||
PathPrefix: prefix,
|
||||
SinceNs: lastTsNs,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("subscribe: %v", err)
|
||||
}
|
||||
|
||||
for {
|
||||
resp, listenErr := stream.Recv()
|
||||
if listenErr == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if listenErr != nil {
|
||||
return listenErr
|
||||
}
|
||||
|
||||
if err := processEventFn(resp); err != nil {
|
||||
glog.Fatalf("process %v: %v", resp, err)
|
||||
}
|
||||
lastTsNs = resp.TsNs
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
glog.Errorf("subscribing filer meta change: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user