mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-01 20:26:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84d2e1bdd0 | ||
|
|
e6ef7b2387 | ||
|
|
4800d0ce26 | ||
|
|
bde2fc65b0 | ||
|
|
3d586be552 | ||
|
|
f4676824a7 | ||
|
|
8205166009 | ||
|
|
3e2acf677c | ||
|
|
bf218cd59d | ||
|
|
5ca0a551ac | ||
|
|
17e08a16f4 | ||
|
|
ce1efeb0eb | ||
|
|
e4830bd93d | ||
|
|
0a856241fe | ||
|
|
d688e10ed1 | ||
|
|
332f5ad3a8 | ||
|
|
f3d8232e14 | ||
|
|
03bb82043c | ||
|
|
2336a397dc | ||
|
|
7584f758b8 | ||
|
|
ed456253d2 | ||
|
|
f14be41439 | ||
|
|
b08fac0014 | ||
|
|
371fead8a5 | ||
|
|
8668d49c9d | ||
|
|
6b31f3c97a | ||
|
|
893f0587b1 | ||
|
|
f0d1e7bd05 | ||
|
|
332d49432d | ||
|
|
96119eab00 | ||
|
|
8a66306064 | ||
|
|
4ed2994555 | ||
|
|
6a030547a2 | ||
|
|
62c2732fd1 | ||
|
|
d4bb16e20e | ||
|
|
947add39e6 | ||
|
|
513fed323a | ||
|
|
280ab7f95c | ||
|
|
04662126bb | ||
|
|
2b9aab3442 | ||
|
|
366f522a2d | ||
|
|
ba7fbac07f | ||
|
|
e6196cdc50 | ||
|
|
a481c4a45e | ||
|
|
3799929830 | ||
|
|
ca04c59ac9 | ||
|
|
1d2dfe593c | ||
|
|
22d8684e88 | ||
|
|
d343b0db57 | ||
|
|
4f50f8c2ca | ||
|
|
69b84bb771 | ||
|
|
57e2fd3f9b | ||
|
|
4c1741fdbb | ||
|
|
b297849147 | ||
|
|
af207bbaf0 | ||
|
|
b6694279d7 | ||
|
|
ec3351a4ec | ||
|
|
1e3fdf366f | ||
|
|
e862b2529a | ||
|
|
88e006bc6d | ||
|
|
5e64b22b45 | ||
|
|
f58ea6a2ee | ||
|
|
0c12a7d12a | ||
|
|
10d6392101 | ||
|
|
41efc6e629 | ||
|
|
7b776be285 | ||
|
|
3b159db143 | ||
|
|
92d65c917c | ||
|
|
3ffbaaa071 | ||
|
|
2cecde89c3 | ||
|
|
fc51ffce2b | ||
|
|
dad88a452f | ||
|
|
4c7a3cf2fd | ||
|
|
adfd79e243 | ||
|
|
a067deaabc | ||
|
|
040443e2d1 | ||
|
|
2e9372dcf7 | ||
|
|
225b019fe0 | ||
|
|
1904448d4e | ||
|
|
5956a8b05a | ||
|
|
aa64f2ac4c | ||
|
|
c4d7ee6c5c | ||
|
|
4a1d4d7462 | ||
|
|
cee4d20bc1 | ||
|
|
ecaab0e048 | ||
|
|
603ea2db73 | ||
|
|
9887610b54 | ||
|
|
2baed2e1e9 | ||
|
|
a814f3f0a8 | ||
|
|
52fe86df45 | ||
|
|
b3d88180ca | ||
|
|
fa7c65bd4b | ||
|
|
84fdda85e7 | ||
|
|
ad5099e570 | ||
|
|
5abdc0be77 | ||
|
|
ede7a65a50 | ||
|
|
c1255d30ad | ||
|
|
eab7cf4bd3 | ||
|
|
59dd271734 | ||
|
|
4cbba2b1c6 | ||
|
|
71175461ef | ||
|
|
e066e2642c | ||
|
|
198fa58e3c | ||
|
|
49d971e602 | ||
|
|
8f2e4be074 | ||
|
|
b751debd31 | ||
|
|
2226c3c8b6 | ||
|
|
94a01fcfcb | ||
|
|
b5f4910412 | ||
|
|
f0907eb83c | ||
|
|
63da4bbb54 | ||
|
|
0c099c1bea | ||
|
|
7a135b7735 | ||
|
|
2789d10342 | ||
|
|
119d5908dd | ||
|
|
7504be58f9 | ||
|
|
df1d6133a8 | ||
|
|
51c8f2518f | ||
|
|
b3e49d2758 | ||
|
|
38c8470d1d | ||
|
|
849f185a20 | ||
|
|
5f6cc9a814 | ||
|
|
172da83449 | ||
|
|
01661ec6a7 | ||
|
|
0c360eb6b2 | ||
|
|
88d68cad87 | ||
|
|
2d237da74a | ||
|
|
ec72547c8d |
@@ -6,57 +6,66 @@ on:
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
build-latest-docker-image:
|
||||
runs-on: [ubuntu-latest]
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
|
||||
- name: Delete old release assets
|
||||
uses: mknejp/delete-release-assets@v1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
tag: dev
|
||||
fail-if-no-assets: false
|
||||
assets: |
|
||||
weed-*
|
||||
|
||||
build_dev:
|
||||
needs: cleanup
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin, freebsd]
|
||||
goarch: [amd64, arm64]
|
||||
exclude:
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
|
||||
- name: Set BUILD_TIME env
|
||||
run: echo BUILD_TIME=$(date -u +%Y%m%d-%H%M) >> ${GITHUB_ENV}
|
||||
|
||||
- name: Go Release Binaries Large Disk
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
ghcr.io/chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# 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 Normal Volume Size
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USERNAME }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64, linux/arm, linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed-normal-disk
|
||||
asset_name: "weed-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: "go: build versioned binaries for windows"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
|
||||
build-release-binaries_windows:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [windows]
|
||||
goarch: [amd64]
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- name: Go Release Binaries Normal Volume Size
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
# build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
- name: Go Release Large Disk Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}_large_disk"
|
||||
@@ -1,6 +1,6 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: "go: build versioned binaries"
|
||||
name: "go: build versioned binaries for linux"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,21 +13,12 @@ on:
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
|
||||
build-release-binaries:
|
||||
build-release-binaries_linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin, freebsd]
|
||||
goos: [linux]
|
||||
goarch: [amd64, arm, arm64]
|
||||
exclude:
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
@@ -0,0 +1,54 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: "go: build versioned binaries for darwin"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
|
||||
build-release-binaries_darwin:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [darwin]
|
||||
goarch: [amd64, arm64]
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- name: Go Release Binaries Normal Volume Size
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
# build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
- name: Go Release Large Disk Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}_large_disk"
|
||||
@@ -0,0 +1,54 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: "go: build versioned binaries for freebsd"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
|
||||
build-release-binaries_freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [freebsd]
|
||||
goarch: [amd64, arm, arm64]
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- name: Go Release Binaries Normal Volume Size
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
# build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
- name: Go Release Large Disk Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.20
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
overwrite: true
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed
|
||||
asset_name: "${{ matrix.goos }}_${{ matrix.goarch }}_large_disk"
|
||||
@@ -58,6 +58,6 @@ jobs:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.go_build
|
||||
platforms: linux/amd64, linux/arm, linux/arm64, linux/386
|
||||
platforms: linux/amd64, linux/arm64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
name: "docker: build release containers"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch: []
|
||||
|
||||
jobs:
|
||||
build-default-release-container:
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
ghcr.io/chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
flavor: |
|
||||
latest=false
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast!
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USERNAME }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.go_build
|
||||
platforms: linux/amd64, linux/arm, linux/arm64, linux/386
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
build-large-release-container:
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
ghcr.io/chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=ref,event=tag,suffix=_large_disk
|
||||
flavor: |
|
||||
latest=false
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast!
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USERNAME }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.go_build_large
|
||||
platforms: linux/amd64, linux/arm, linux/arm64, linux/386
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
@@ -0,0 +1,54 @@
|
||||
name: "docker: build release containers for normal volume"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch: []
|
||||
|
||||
jobs:
|
||||
build-default-release-container:
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
flavor: |
|
||||
latest=false
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast!
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.go_build
|
||||
platforms: linux/amd64, linux/arm, linux/arm64, linux/386
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: "docker: build release containers for large volume"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch: []
|
||||
|
||||
jobs:
|
||||
|
||||
build-large-release-container:
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=ref,event=tag,suffix=_large_disk
|
||||
flavor: |
|
||||
latest=false
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast!
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.go_build_large
|
||||
platforms: linux/amd64, linux/arm, linux/arm64, linux/386
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: "docker: build release containers for rocksdb"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch: []
|
||||
|
||||
jobs:
|
||||
|
||||
build-large-release-container_rocksdb:
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
chrislusf/seaweedfs
|
||||
tags: |
|
||||
type=ref,event=tag,suffix=_large_disk_rocksdb
|
||||
flavor: |
|
||||
latest=false
|
||||
labels: |
|
||||
org.opencontainers.image.title=seaweedfs
|
||||
org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast!
|
||||
org.opencontainers.image.vendor=Chris Lu
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./docker
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
file: ./docker/Dockerfile.rocksdb_large
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
@@ -147,6 +147,7 @@ Faster and Cheaper than direct cloud storage!
|
||||
* [AES256-GCM Encrypted Storage][FilerDataEncryption] safely stores the encrypted data.
|
||||
* [Super Large Files][SuperLargeFiles] stores large or super large files in tens of TB.
|
||||
* [Cloud Drive][CloudDrive] mounts cloud storage to local cluster, cached for fast read and write with asynchronous write back.
|
||||
* [Gateway to Remote Object Store][GatewayToRemoteObjectStore] mirrors bucket operations to remote object storage, in addition to [Cloud Drive][CloudDrive]
|
||||
|
||||
## Kubernetes ##
|
||||
* [Kubernetes CSI Driver][SeaweedFsCsiDriver] A Container Storage Interface (CSI) Driver. [](https://hub.docker.com/r/chrislusf/seaweedfs-csi-driver/)
|
||||
@@ -170,6 +171,7 @@ Faster and Cheaper than direct cloud storage!
|
||||
[FilerStoreReplication]: https://github.com/chrislusf/seaweedfs/wiki/Filer-Store-Replication
|
||||
[KeyLargeValueStore]: https://github.com/chrislusf/seaweedfs/wiki/Filer-as-a-Key-Large-Value-Store
|
||||
[CloudDrive]: https://github.com/chrislusf/seaweedfs/wiki/Cloud-Drive-Architecture
|
||||
[GatewayToRemoteObjectStore]: https://github.com/chrislusf/seaweedfs/wiki/Gateway-to-Remote-Object-Storage
|
||||
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
FROM golang:1.17-buster as builder
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev libzstd-dev
|
||||
|
||||
ENV ROCKSDB_VERSION v6.22.1
|
||||
|
||||
# build RocksDB
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/facebook/rocksdb.git /tmp/rocksdb --depth 1 --single-branch --branch $ROCKSDB_VERSION && \
|
||||
cd rocksdb && \
|
||||
make static_lib && \
|
||||
make install-static
|
||||
|
||||
ENV CGO_CFLAGS "-I/tmp/rocksdb/include"
|
||||
ENV CGO_LDFLAGS "-L/tmp/rocksdb -lrocksdb -lstdc++ -lm -lz -lbz2 -lsnappy -llz4 -lzstd"
|
||||
|
||||
# build SeaweedFS
|
||||
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 rocksdb" -ldflags "-extldflags -static ${LDFLAGS}"
|
||||
|
||||
|
||||
FROM alpine AS final
|
||||
LABEL author="Chris Lu"
|
||||
COPY --from=builder /go/bin/weed /usr/bin/
|
||||
RUN mkdir -p /etc/seaweedfs
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/filer.toml /etc/seaweedfs/filer.toml
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/entrypoint.sh /entrypoint.sh
|
||||
RUN apk add fuse snappy gflags
|
||||
|
||||
# volume server gprc port
|
||||
EXPOSE 18080
|
||||
# volume server http port
|
||||
EXPOSE 8080
|
||||
# filer server gprc port
|
||||
EXPOSE 18888
|
||||
# filer server http port
|
||||
EXPOSE 8888
|
||||
# master server shared gprc port
|
||||
EXPOSE 19333
|
||||
# master server shared http port
|
||||
EXPOSE 9333
|
||||
# s3 server http port
|
||||
EXPOSE 8333
|
||||
# webdav server http port
|
||||
EXPOSE 7333
|
||||
|
||||
RUN mkdir -p /data/filerldb2
|
||||
|
||||
VOLUME /data
|
||||
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -13,6 +13,12 @@ build: binary
|
||||
docker build --no-cache -t chrislusf/seaweedfs:local -f Dockerfile.local .
|
||||
rm ./weed
|
||||
|
||||
build_gorocksdb:
|
||||
docker build --no-cache -t chrislusf/gorocksdb -f Dockerfile.go_rocksdb .
|
||||
|
||||
build_rocksdb:
|
||||
docker build --no-cache -t chrislusf/seaweedfs:rocksdb -f Dockerfile.rocksdb_large .
|
||||
|
||||
s3tests_build:
|
||||
docker build --no-cache -t chrislusf/ceph-s3-tests:local -f Dockerfile.s3tests .
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ require (
|
||||
cloud.google.com/go/storage v1.16.1
|
||||
github.com/Azure/azure-pipeline-go v0.2.3
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.2
|
||||
github.com/Shopify/sarama v1.23.1
|
||||
@@ -39,6 +38,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.4.4
|
||||
github.com/go-redsync/redsync/v4 v4.4.1
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/go-zookeeper/zk v1.0.2 // indirect
|
||||
@@ -54,11 +54,10 @@ require (
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/gorilla/websocket v1.4.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
@@ -74,6 +73,7 @@ require (
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/linxGnu/grocksdb v1.6.38
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.1 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
@@ -110,12 +110,11 @@ require (
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c
|
||||
github.com/tidwall/gjson v1.8.1
|
||||
github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/pretty v1.1.0 // indirect
|
||||
github.com/tikv/client-go/v2 v2.0.0-alpha.0.20210824090536-16d902a3c7e5
|
||||
github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365
|
||||
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
@@ -126,24 +125,24 @@ require (
|
||||
github.com/xdg-go/scram v1.0.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.2 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.etcd.io/etcd v3.3.25+incompatible
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
go.mongodb.org/mongo-driver v1.7.0
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/otel v0.15.0 // indirect
|
||||
gocloud.dev v0.20.0
|
||||
gocloud.dev/pubsub/natspubsub v0.20.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.20.0
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/tools v0.1.5
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/api v0.56.0
|
||||
google.golang.org/api v0.57.0
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6 // indirect
|
||||
google.golang.org/grpc v1.40.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
@@ -165,35 +164,23 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/etcd v3.3.10+incompatible // indirect
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
|
||||
cloud.google.com/go/kms v1.0.0 // indirect
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.2 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.7 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 // indirect
|
||||
github.com/pingcap/failpoint v0.0.0-20210316064728-7acb0f0a3dfd // indirect
|
||||
github.com/pingcap/kvproto v0.0.0-20210806074406-317f69fb54b4 // indirect
|
||||
github.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4 // indirect
|
||||
github.com/pingcap/parser v0.0.0-20210525032559-c37778aff307 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tikv/pd v1.1.0-beta.0.20210323121136-78679e5e209d // indirect
|
||||
github.com/twmb/murmur3 v1.1.3 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
appVersion: "2.68"
|
||||
version: "2.68"
|
||||
appVersion: "2.71"
|
||||
version: "2.71"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
{{- if .Values.global.monitoring.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
@@ -15,4 +16,5 @@ spec:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: filer
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -2,6 +2,7 @@ apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-{{ template "seaweedfs.name" . }}-filer
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
nginx.ingress.kubernetes.io/auth-type: "basic"
|
||||
@@ -32,6 +33,7 @@ apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-{{ template "seaweedfs.name" . }}-master
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
nginx.ingress.kubernetes.io/auth-type: "basic"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- if .Values.s3.metricsPort }}
|
||||
{{- if .Values.global.monitoring.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
@@ -15,4 +16,5 @@ spec:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: s3
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,4 +1,5 @@
|
||||
{{- if .Values.volume.metricsPort }}
|
||||
{{- if .Values.global.monitoring.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
@@ -15,4 +16,5 @@ spec:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: volume
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -161,7 +161,7 @@ volume:
|
||||
|
||||
# Directories to store data files. dir[,dir]... (default "/tmp")
|
||||
dir: "/data"
|
||||
# Directories to store index files. dir[,dir]... (default "/tmp")
|
||||
# Directories to store index files. dir[,dir]... (default is the same as "dir")
|
||||
dir_idx: null
|
||||
|
||||
# Maximum numbers of volumes, count[,count]...
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -15,7 +15,7 @@ public class ChunkCache {
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public class VolumeIdCache {
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
const (
|
||||
maxPartSize = int64(5 * 1024 * 1024)
|
||||
maxRetries = 3
|
||||
awsAccessKeyID = "Your access key"
|
||||
awsSecretAccessKey = "Your secret key"
|
||||
awsBucketRegion = "S3 bucket region"
|
||||
awsBucketName = "newBucket"
|
||||
awsAccessKeyID = "any"
|
||||
awsSecretAccessKey = "any"
|
||||
awsBucketRegion = "us‑west‑1"
|
||||
awsBucketName = "bucket1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,7 +37,7 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Printf("bad credentials: %s", err)
|
||||
}
|
||||
cfg := aws.NewConfig().WithRegion(awsBucketRegion).WithCredentials(creds).WithDisableSSL(true).WithEndpoint("localhost:8333")
|
||||
cfg := aws.NewConfig().WithRegion(awsBucketRegion).WithCredentials(creds).WithDisableSSL(true).WithEndpoint("localhost:8333").WithS3ForcePathStyle(true)
|
||||
svc := s3.New(session.New(), cfg)
|
||||
|
||||
file, err := os.Open(*filename)
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/operation"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/security"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
volumeServer = flag.String("volumeServer", "localhost:8080", "a volume server")
|
||||
volumeId = flag.Int("volumeId", -1, "a volume id to stream read")
|
||||
grpcDialOption grpc.DialOption
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
util.LoadConfiguration("security", false)
|
||||
grpcDialOption = security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||
|
||||
vid := uint32(*volumeId)
|
||||
|
||||
eachNeedleFunc := func(resp *volume_server_pb.ReadAllNeedlesResponse) error {
|
||||
fmt.Printf("%d,%x%08x %d\n", resp.VolumeId, resp.NeedleId, resp.Cookie, len(resp.NeedleBlob))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := operation.WithVolumeServerClient(pb.ServerAddress(*volumeServer), grpcDialOption, func(vs volume_server_pb.VolumeServerClient) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
copyFileClient, err := vs.ReadAllNeedles(ctx, &volume_server_pb.ReadAllNeedlesRequest{
|
||||
VolumeIds: []uint32{vid},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
resp, err := copyFileClient.Recv()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = eachNeedleFunc(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("read %s: %v\n", *volumeServer, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ var Commands = []*Command{
|
||||
cmdFilerCopy,
|
||||
cmdFilerMetaBackup,
|
||||
cmdFilerMetaTail,
|
||||
cmdFilerRemoteGateway,
|
||||
cmdFilerRemoteSynchronize,
|
||||
cmdFilerReplicate,
|
||||
cmdFilerSynchronize,
|
||||
|
||||
@@ -62,7 +62,7 @@ func init() {
|
||||
f.ip = cmdFiler.Flag.String("ip", util.DetectedHostAddress(), "filer server http listen ip address")
|
||||
f.bindIp = cmdFiler.Flag.String("ip.bind", "", "ip address to bind to")
|
||||
f.port = cmdFiler.Flag.Int("port", 8888, "filer server http listen port")
|
||||
f.portGrpc = cmdFiler.Flag.Int("port.grpc", 18888, "filer server grpc listen port")
|
||||
f.portGrpc = cmdFiler.Flag.Int("port.grpc", 0, "filer server grpc listen port")
|
||||
f.publicPort = cmdFiler.Flag.Int("port.readonly", 0, "readonly port opened to public")
|
||||
f.defaultReplicaPlacement = cmdFiler.Flag.String("defaultReplicaPlacement", "", "default replication type. If not specified, use master setting.")
|
||||
f.disableDirListing = cmdFiler.Flag.Bool("disableDirListing", false, "turn off directory listing")
|
||||
@@ -87,7 +87,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")
|
||||
filerS3Options.allowEmptyFolder = cmdFiler.Flag.Bool("s3.allowEmptyFolder", true, "allow empty folders")
|
||||
|
||||
// start webdav on filer
|
||||
filerStartWebDav = cmdFiler.Flag.Bool("webdav", false, "whether to start webdav gateway")
|
||||
@@ -180,6 +180,9 @@ func (fo *FilerOptions) startFiler() {
|
||||
if *fo.publicPort != 0 {
|
||||
publicVolumeMux = http.NewServeMux()
|
||||
}
|
||||
if *fo.portGrpc == 0 {
|
||||
*fo.portGrpc = 10000 + *fo.port
|
||||
}
|
||||
|
||||
defaultLevelDbDirectory := util.ResolvePath(*fo.defaultLevelDbDirectory + "/filerldb2")
|
||||
|
||||
|
||||
+34
-28
@@ -344,9 +344,9 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err
|
||||
return err
|
||||
}
|
||||
|
||||
// assign a volume
|
||||
err = util.Retry("assignVolume", func() error {
|
||||
return pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
err = util.Retry("upload", func() error {
|
||||
// assign a volume
|
||||
assignErr := pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.AssignVolumeRequest{
|
||||
Count: 1,
|
||||
@@ -369,35 +369,41 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if assignErr != nil {
|
||||
return assignErr
|
||||
}
|
||||
|
||||
// upload data
|
||||
targetUrl := "http://" + assignResult.Location.Url + "/" + assignResult.FileId
|
||||
uploadOption := &operation.UploadOption{
|
||||
UploadUrl: targetUrl,
|
||||
Filename: fileName,
|
||||
Cipher: worker.options.cipher,
|
||||
IsInputCompressed: false,
|
||||
MimeType: mimeType,
|
||||
PairMap: nil,
|
||||
Jwt: security.EncodedJwt(assignResult.Auth),
|
||||
}
|
||||
uploadResult, err := operation.UploadData(data, uploadOption)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
|
||||
}
|
||||
if uploadResult.Error != "" {
|
||||
return fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
|
||||
}
|
||||
if *worker.options.verbose {
|
||||
fmt.Printf("uploaded %s to %s\n", fileName, targetUrl)
|
||||
}
|
||||
|
||||
fmt.Printf("copied %s => http://%s%s%s\n", f.Name(), worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName)
|
||||
chunks = append(chunks, uploadResult.ToPbFileChunk(assignResult.FileId, 0))
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to assign from %v: %v\n", worker.options.masters, err)
|
||||
return fmt.Errorf("upload %v: %v\n", fileName, err)
|
||||
}
|
||||
|
||||
targetUrl := "http://" + assignResult.Location.Url + "/" + assignResult.FileId
|
||||
uploadOption := &operation.UploadOption{
|
||||
UploadUrl: targetUrl,
|
||||
Filename: fileName,
|
||||
Cipher: worker.options.cipher,
|
||||
IsInputCompressed: false,
|
||||
MimeType: mimeType,
|
||||
PairMap: nil,
|
||||
Jwt: security.EncodedJwt(assignResult.Auth),
|
||||
}
|
||||
uploadResult, err := operation.UploadData(data, uploadOption)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
|
||||
}
|
||||
if uploadResult.Error != "" {
|
||||
return fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
|
||||
}
|
||||
if *worker.options.verbose {
|
||||
fmt.Printf("uploaded %s to %s\n", fileName, targetUrl)
|
||||
}
|
||||
|
||||
chunks = append(chunks, uploadResult.ToPbFileChunk(assignResult.FileId, 0))
|
||||
|
||||
fmt.Printf("copied %s => http://%s%s%s\n", f.Name(), worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName)
|
||||
}
|
||||
|
||||
if err := pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
@@ -71,12 +71,12 @@ func runFilerMetaTail(cmd *Command, args []string) bool {
|
||||
}
|
||||
|
||||
shouldPrint := func(resp *filer_pb.SubscribeMetadataResponse) bool {
|
||||
if filterFunc == nil {
|
||||
return true
|
||||
}
|
||||
if resp.EventNotification.OldEntry == nil && resp.EventNotification.NewEntry == nil {
|
||||
return false
|
||||
}
|
||||
if filterFunc == nil {
|
||||
return true
|
||||
}
|
||||
if resp.EventNotification.OldEntry != nil && filterFunc(resp.Directory, resp.EventNotification.OldEntry.Name) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/remote_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
||||
"github.com/chrislusf/seaweedfs/weed/security"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RemoteGatewayOptions struct {
|
||||
filerAddress *string
|
||||
grpcDialOption grpc.DialOption
|
||||
readChunkFromFiler *bool
|
||||
timeAgo *time.Duration
|
||||
createBucketAt *string
|
||||
createBucketRandomSuffix *bool
|
||||
include *string
|
||||
exclude *string
|
||||
|
||||
mappings *remote_pb.RemoteStorageMapping
|
||||
remoteConfs map[string]*remote_pb.RemoteConf
|
||||
bucketsDir string
|
||||
}
|
||||
|
||||
var _ = filer_pb.FilerClient(&RemoteGatewayOptions{})
|
||||
|
||||
func (option *RemoteGatewayOptions) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error) error {
|
||||
return pb.WithFilerClient(pb.ServerAddress(*option.filerAddress), option.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
return fn(client)
|
||||
})
|
||||
}
|
||||
func (option *RemoteGatewayOptions) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
var (
|
||||
remoteGatewayOptions RemoteGatewayOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdFilerRemoteGateway.Run = runFilerRemoteGateway // break init cycle
|
||||
remoteGatewayOptions.filerAddress = cmdFilerRemoteGateway.Flag.String("filer", "localhost:8888", "filer of the SeaweedFS cluster")
|
||||
remoteGatewayOptions.createBucketAt = cmdFilerRemoteGateway.Flag.String("createBucketAt", "", "one remote storage name to create new buckets in")
|
||||
remoteGatewayOptions.createBucketRandomSuffix = cmdFilerRemoteGateway.Flag.Bool("createBucketWithRandomSuffix", true, "add randomized suffix to bucket name to avoid conflicts")
|
||||
remoteGatewayOptions.readChunkFromFiler = cmdFilerRemoteGateway.Flag.Bool("filerProxy", false, "read file chunks from filer instead of volume servers")
|
||||
remoteGatewayOptions.timeAgo = cmdFilerRemoteGateway.Flag.Duration("timeAgo", 0, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
remoteGatewayOptions.include = cmdFilerRemoteGateway.Flag.String("include", "", "pattens of new bucket names, e.g., s3*")
|
||||
remoteGatewayOptions.exclude = cmdFilerRemoteGateway.Flag.String("exclude", "", "pattens of new bucket names, e.g., local*")
|
||||
}
|
||||
|
||||
var cmdFilerRemoteGateway = &Command{
|
||||
UsageLine: "filer.remote.gateway",
|
||||
Short: "resumable continuously write back bucket creation, deletion, and other local updates to remote object store",
|
||||
Long: `resumable continuously write back bucket creation, deletion, and other local updates to remote object store
|
||||
|
||||
filer.remote.gateway listens on filer local buckets update events.
|
||||
If any bucket is created, deleted, or updated, it will mirror the changes to remote object store.
|
||||
|
||||
weed filer.remote.sync -createBucketAt=cloud1
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
func runFilerRemoteGateway(cmd *Command, args []string) bool {
|
||||
|
||||
util.LoadConfiguration("security", false)
|
||||
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||
remoteGatewayOptions.grpcDialOption = grpcDialOption
|
||||
|
||||
filerAddress := pb.ServerAddress(*remoteGatewayOptions.filerAddress)
|
||||
|
||||
filerSource := &source.FilerSource{}
|
||||
filerSource.DoInitialize(
|
||||
filerAddress.ToHttpAddress(),
|
||||
filerAddress.ToGrpcAddress(),
|
||||
"/", // does not matter
|
||||
*remoteGatewayOptions.readChunkFromFiler,
|
||||
)
|
||||
|
||||
remoteGatewayOptions.bucketsDir = "/buckets"
|
||||
// check buckets again
|
||||
remoteGatewayOptions.WithFilerClient(func(filerClient filer_pb.SeaweedFilerClient) error {
|
||||
resp, err := filerClient.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteGatewayOptions.bucketsDir = resp.DirBuckets
|
||||
return nil
|
||||
})
|
||||
|
||||
// read filer remote storage mount mappings
|
||||
if detectErr := remoteGatewayOptions.collectRemoteStorageConf(); detectErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "read mount info: %v\n", detectErr)
|
||||
return true
|
||||
}
|
||||
|
||||
// synchronize /buckets folder
|
||||
fmt.Printf("synchronize buckets in %s ...\n", remoteGatewayOptions.bucketsDir)
|
||||
util.RetryForever("filer.remote.sync buckets", func() error {
|
||||
return remoteGatewayOptions.followBucketUpdatesAndUploadToRemote(filerSource)
|
||||
}, func(err error) bool {
|
||||
if err != nil {
|
||||
glog.Errorf("synchronize %s: %v", remoteGatewayOptions.bucketsDir, err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
|
||||
}
|
||||
+17
-6
@@ -13,11 +13,12 @@ import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"math"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (option *RemoteSyncOptions) followBucketUpdatesAndUploadToRemote(filerSource *source.FilerSource) error {
|
||||
func (option *RemoteGatewayOptions) followBucketUpdatesAndUploadToRemote(filerSource *source.FilerSource) error {
|
||||
|
||||
// read filer remote storage mount mappings
|
||||
if detectErr := option.collectRemoteStorageConf(); detectErr != nil {
|
||||
@@ -35,13 +36,13 @@ func (option *RemoteSyncOptions) followBucketUpdatesAndUploadToRemote(filerSourc
|
||||
return remote_storage.SetSyncOffset(option.grpcDialOption, pb.ServerAddress(*option.filerAddress), option.bucketsDir, lastTsNs)
|
||||
})
|
||||
|
||||
lastOffsetTs := collectLastSyncOffset(option, option.bucketsDir)
|
||||
lastOffsetTs := collectLastSyncOffset(option, option.grpcDialOption, pb.ServerAddress(*option.filerAddress), option.bucketsDir, *option.timeAgo)
|
||||
|
||||
return pb.FollowMetadata(pb.ServerAddress(*option.filerAddress), option.grpcDialOption, "filer.remote.sync",
|
||||
option.bucketsDir, []string{filer.DirectoryEtcRemote}, lastOffsetTs.UnixNano(), 0, processEventFnWithOffset, false)
|
||||
}
|
||||
|
||||
func (option *RemoteSyncOptions) makeBucketedEventProcessor(filerSource *source.FilerSource) (pb.ProcessMetadataFunc, error) {
|
||||
func (option *RemoteGatewayOptions) makeBucketedEventProcessor(filerSource *source.FilerSource) (pb.ProcessMetadataFunc, error) {
|
||||
|
||||
handleCreateBucket := func(entry *filer_pb.Entry) error {
|
||||
if !entry.IsDirectory {
|
||||
@@ -75,6 +76,16 @@ func (option *RemoteSyncOptions) makeBucketedEventProcessor(filerSource *source.
|
||||
}
|
||||
|
||||
bucketName := strings.ToLower(entry.Name)
|
||||
if *option.include != "" {
|
||||
if ok, _ := filepath.Match(*option.include, entry.Name); !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if *option.exclude != "" {
|
||||
if ok, _ := filepath.Match(*option.exclude, entry.Name); ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if *option.createBucketRandomSuffix {
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
|
||||
if len(bucketName)+5 > 63 {
|
||||
@@ -307,7 +318,7 @@ func (option *RemoteSyncOptions) makeBucketedEventProcessor(filerSource *source.
|
||||
return eachEntryFunc, nil
|
||||
}
|
||||
|
||||
func (option *RemoteSyncOptions) findRemoteStorageClient(bucketName string) (client remote_storage.RemoteStorageClient, remoteStorageMountLocation *remote_pb.RemoteStorageLocation, err error) {
|
||||
func (option *RemoteGatewayOptions) findRemoteStorageClient(bucketName string) (client remote_storage.RemoteStorageClient, remoteStorageMountLocation *remote_pb.RemoteStorageLocation, err error) {
|
||||
bucket := util.FullPath(option.bucketsDir).Child(bucketName)
|
||||
|
||||
var isMounted bool
|
||||
@@ -327,7 +338,7 @@ func (option *RemoteSyncOptions) findRemoteStorageClient(bucketName string) (cli
|
||||
return client, remoteStorageMountLocation, nil
|
||||
}
|
||||
|
||||
func (option *RemoteSyncOptions) detectBucketInfo(actualDir string) (bucket util.FullPath, remoteStorageMountLocation *remote_pb.RemoteStorageLocation, remoteConf *remote_pb.RemoteConf, ok bool) {
|
||||
func (option *RemoteGatewayOptions) detectBucketInfo(actualDir string) (bucket util.FullPath, remoteStorageMountLocation *remote_pb.RemoteStorageLocation, remoteConf *remote_pb.RemoteConf, ok bool) {
|
||||
bucket, ok = extractBucketPath(option.bucketsDir, actualDir)
|
||||
if !ok {
|
||||
return "", nil, nil, false
|
||||
@@ -355,7 +366,7 @@ func extractBucketPath(bucketsDir, dir string) (util.FullPath, bool) {
|
||||
return util.FullPath(bucketsDir).Child(parts[0]), true
|
||||
}
|
||||
|
||||
func (option *RemoteSyncOptions) collectRemoteStorageConf() (err error) {
|
||||
func (option *RemoteGatewayOptions) collectRemoteStorageConf() (err error) {
|
||||
|
||||
if mappings, err := filer.ReadMountMappings(option.grpcDialOption, pb.ServerAddress(*option.filerAddress)); err != nil {
|
||||
return err
|
||||
@@ -1,33 +1,23 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/remote_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
||||
"github.com/chrislusf/seaweedfs/weed/security"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RemoteSyncOptions struct {
|
||||
filerAddress *string
|
||||
grpcDialOption grpc.DialOption
|
||||
readChunkFromFiler *bool
|
||||
debug *bool
|
||||
timeAgo *time.Duration
|
||||
dir *string
|
||||
createBucketAt *string
|
||||
createBucketRandomSuffix *bool
|
||||
|
||||
mappings *remote_pb.RemoteStorageMapping
|
||||
remoteConfs map[string]*remote_pb.RemoteConf
|
||||
bucketsDir string
|
||||
filerAddress *string
|
||||
grpcDialOption grpc.DialOption
|
||||
readChunkFromFiler *bool
|
||||
timeAgo *time.Duration
|
||||
dir *string
|
||||
}
|
||||
|
||||
var _ = filer_pb.FilerClient(&RemoteSyncOptions{})
|
||||
@@ -49,10 +39,7 @@ func init() {
|
||||
cmdFilerRemoteSynchronize.Run = runFilerRemoteSynchronize // break init cycle
|
||||
remoteSyncOptions.filerAddress = cmdFilerRemoteSynchronize.Flag.String("filer", "localhost:8888", "filer of the SeaweedFS cluster")
|
||||
remoteSyncOptions.dir = cmdFilerRemoteSynchronize.Flag.String("dir", "", "a mounted directory on filer")
|
||||
remoteSyncOptions.createBucketAt = cmdFilerRemoteSynchronize.Flag.String("createBucketAt", "", "one remote storage name to create new buckets in")
|
||||
remoteSyncOptions.createBucketRandomSuffix = cmdFilerRemoteSynchronize.Flag.Bool("createBucketWithRandomSuffix", true, "add randomized suffix to bucket name to avoid conflicts")
|
||||
remoteSyncOptions.readChunkFromFiler = cmdFilerRemoteSynchronize.Flag.Bool("filerProxy", false, "read file chunks from filer instead of volume servers")
|
||||
remoteSyncOptions.debug = cmdFilerRemoteSynchronize.Flag.Bool("debug", false, "debug mode to print out filer updated remote files")
|
||||
remoteSyncOptions.timeAgo = cmdFilerRemoteSynchronize.Flag.Duration("timeAgo", 0, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
}
|
||||
|
||||
@@ -65,21 +52,8 @@ var cmdFilerRemoteSynchronize = &Command{
|
||||
If any mounted remote file is updated, it will fetch the updated content,
|
||||
and write to the remote storage.
|
||||
|
||||
There are two modes:
|
||||
|
||||
1)By default, watch /buckets folder and write back all changes.
|
||||
|
||||
# if there is only one remote storage configured
|
||||
weed filer.remote.sync
|
||||
# if there are multiple remote storages configured
|
||||
# specify a remote storage to create new buckets.
|
||||
weed filer.remote.sync -createBucketAt=cloud1
|
||||
|
||||
2)Write back one mounted folder to remote storage
|
||||
|
||||
weed filer.remote.sync -dir=/mount/s3_on_cloud
|
||||
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -100,18 +74,7 @@ func runFilerRemoteSynchronize(cmd *Command, args []string) bool {
|
||||
*remoteSyncOptions.readChunkFromFiler,
|
||||
)
|
||||
|
||||
remoteSyncOptions.bucketsDir = "/buckets"
|
||||
// check buckets again
|
||||
remoteSyncOptions.WithFilerClient(func(filerClient filer_pb.SeaweedFilerClient) error {
|
||||
resp, err := filerClient.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteSyncOptions.bucketsDir = resp.DirBuckets
|
||||
return nil
|
||||
})
|
||||
|
||||
if dir != "" && dir != remoteSyncOptions.bucketsDir {
|
||||
if dir != "" {
|
||||
fmt.Printf("synchronize %s to remote storage...\n", dir)
|
||||
util.RetryForever("filer.remote.sync "+dir, func() error {
|
||||
return followUpdatesAndUploadToRemote(&remoteSyncOptions, filerSource, dir)
|
||||
@@ -124,22 +87,6 @@ func runFilerRemoteSynchronize(cmd *Command, args []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// read filer remote storage mount mappings
|
||||
if detectErr := remoteSyncOptions.collectRemoteStorageConf(); detectErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "read mount info: %v\n", detectErr)
|
||||
return true
|
||||
}
|
||||
|
||||
// synchronize /buckets folder
|
||||
fmt.Printf("synchronize buckets in %s ...\n", remoteSyncOptions.bucketsDir)
|
||||
util.RetryForever("filer.remote.sync buckets", func() error {
|
||||
return remoteSyncOptions.followBucketUpdatesAndUploadToRemote(filerSource)
|
||||
}, func(err error) bool {
|
||||
if err != nil {
|
||||
glog.Errorf("synchronize %s: %v", remoteSyncOptions.bucketsDir, err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/grpc"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -36,7 +37,7 @@ func followUpdatesAndUploadToRemote(option *RemoteSyncOptions, filerSource *sour
|
||||
return remote_storage.SetSyncOffset(option.grpcDialOption, pb.ServerAddress(*option.filerAddress), mountedDir, lastTsNs)
|
||||
})
|
||||
|
||||
lastOffsetTs := collectLastSyncOffset(option, mountedDir)
|
||||
lastOffsetTs := collectLastSyncOffset(option, option.grpcDialOption, pb.ServerAddress(*option.filerAddress), mountedDir, *option.timeAgo)
|
||||
|
||||
return pb.FollowMetadata(pb.ServerAddress(*option.filerAddress), option.grpcDialOption, "filer.remote.sync",
|
||||
mountedDir, []string{filer.DirectoryEtcRemote}, lastOffsetTs.UnixNano(), 0, processEventFnWithOffset, false)
|
||||
@@ -159,19 +160,19 @@ func makeEventProcessor(remoteStorage *remote_pb.RemoteConf, mountedDir string,
|
||||
return eachEntryFunc, nil
|
||||
}
|
||||
|
||||
func collectLastSyncOffset(option *RemoteSyncOptions, mountedDir string) time.Time {
|
||||
func collectLastSyncOffset(filerClient filer_pb.FilerClient, grpcDialOption grpc.DialOption, filerAddress pb.ServerAddress, mountedDir string, timeAgo time.Duration) time.Time {
|
||||
// 1. specified by timeAgo
|
||||
// 2. last offset timestamp for this directory
|
||||
// 3. directory creation time
|
||||
var lastOffsetTs time.Time
|
||||
if *option.timeAgo == 0 {
|
||||
mountedDirEntry, err := filer_pb.GetEntry(option, util.FullPath(mountedDir))
|
||||
if timeAgo == 0 {
|
||||
mountedDirEntry, err := filer_pb.GetEntry(filerClient, util.FullPath(mountedDir))
|
||||
if err != nil {
|
||||
glog.V(0).Infof("get mounted directory %s: %v", mountedDir, err)
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
lastOffsetTsNs, err := remote_storage.GetSyncOffset(option.grpcDialOption, pb.ServerAddress(*option.filerAddress), mountedDir)
|
||||
lastOffsetTsNs, err := remote_storage.GetSyncOffset(grpcDialOption, filerAddress, mountedDir)
|
||||
if mountedDirEntry != nil {
|
||||
if err == nil && mountedDirEntry.Attributes.Crtime < lastOffsetTsNs/1000000 {
|
||||
lastOffsetTs = time.Unix(0, lastOffsetTsNs)
|
||||
@@ -183,7 +184,7 @@ func collectLastSyncOffset(option *RemoteSyncOptions, mountedDir string) time.Ti
|
||||
lastOffsetTs = time.Now()
|
||||
}
|
||||
} else {
|
||||
lastOffsetTs = time.Now().Add(-*option.timeAgo)
|
||||
lastOffsetTs = time.Now().Add(-timeAgo)
|
||||
}
|
||||
return lastOffsetTs
|
||||
}
|
||||
|
||||
@@ -29,6 +29,6 @@ import (
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/postgres2"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis2"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis3"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/sqlite"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/tikv"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ type MasterOptions struct {
|
||||
func init() {
|
||||
cmdMaster.Run = runMaster // break init cycle
|
||||
m.port = cmdMaster.Flag.Int("port", 9333, "http listen port")
|
||||
m.portGrpc = cmdMaster.Flag.Int("port.grpc", 19333, "grpc listen port")
|
||||
m.portGrpc = cmdMaster.Flag.Int("port.grpc", 0, "grpc listen port")
|
||||
m.ip = cmdMaster.Flag.String("ip", util.DetectedHostAddress(), "master <ip>|<server> address, also used as identifier")
|
||||
m.ipBind = cmdMaster.Flag.String("ip.bind", "", "ip address to bind to")
|
||||
m.metaFolder = cmdMaster.Flag.String("mdir", os.TempDir(), "data directory to store meta data")
|
||||
@@ -113,6 +113,10 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) {
|
||||
|
||||
backend.LoadConfiguration(util.GetViper())
|
||||
|
||||
if *masterOption.portGrpc == 0 {
|
||||
*masterOption.portGrpc = 10000 + *masterOption.port
|
||||
}
|
||||
|
||||
myMasterAddress, peers := checkPeers(*masterOption.ip, *masterOption.port, *masterOption.portGrpc, *masterOption.peers)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
func init() {
|
||||
cmdMasterFollower.Run = runMasterFollower // break init cycle
|
||||
mf.port = cmdMasterFollower.Flag.Int("port", 9334, "http listen port")
|
||||
mf.portGrpc = cmdMasterFollower.Flag.Int("port.grpc", 19334, "grpc listen port")
|
||||
mf.portGrpc = cmdMasterFollower.Flag.Int("port.grpc", 0, "grpc listen port")
|
||||
mf.ipBind = cmdMasterFollower.Flag.String("ip.bind", "", "ip address to bind to")
|
||||
mf.peers = cmdMasterFollower.Flag.String("masters", "localhost:9333", "all master nodes in comma separated ip:port list, example: 127.0.0.1:9093,127.0.0.1:9094,127.0.0.1:9095")
|
||||
|
||||
@@ -70,6 +70,10 @@ func runMasterFollower(cmd *Command, args []string) bool {
|
||||
util.LoadConfiguration("security", false)
|
||||
util.LoadConfiguration("master", false)
|
||||
|
||||
if *mf.portGrpc == 0 {
|
||||
*mf.portGrpc = 10000 + *mf.port
|
||||
}
|
||||
|
||||
startMasterFollower(mf)
|
||||
|
||||
return true
|
||||
|
||||
+1
-1
@@ -42,7 +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")
|
||||
s3StandaloneOptions.allowEmptyFolder = cmdS3.Flag.Bool("allowEmptyFolder", true, "allow empty folders")
|
||||
}
|
||||
|
||||
var cmdS3 = &Command{
|
||||
|
||||
@@ -185,6 +185,28 @@ routeByLatency = false
|
||||
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
|
||||
superLargeDirectories = []
|
||||
|
||||
[redis3] # beta
|
||||
enabled = false
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 0
|
||||
|
||||
[redis_cluster3] # beta
|
||||
enabled = false
|
||||
addresses = [
|
||||
"localhost:30001",
|
||||
"localhost:30002",
|
||||
"localhost:30003",
|
||||
"localhost:30004",
|
||||
"localhost:30005",
|
||||
"localhost:30006",
|
||||
]
|
||||
password = ""
|
||||
# allows reads from slave servers or the master, but all writes still go to the master
|
||||
readOnly = false
|
||||
# automatically use the closest Redis server for reads
|
||||
routeByLatency = false
|
||||
|
||||
[etcd]
|
||||
enabled = false
|
||||
servers = "localhost:2379"
|
||||
@@ -230,11 +252,3 @@ location = "/tmp/"
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 1
|
||||
|
||||
[tikv]
|
||||
enabled = false
|
||||
# If you have many pd address, use ',' split then:
|
||||
# pdaddrs = "pdhost1:2379, pdhost2:2379, pdhost3:2379"
|
||||
pdaddrs = "localhost:2379"
|
||||
# Concurrency for TiKV delete range
|
||||
deleterange_concurrency = 1
|
||||
|
||||
@@ -23,10 +23,7 @@ default = "localhost:8888" # used by maintenance scripts if the scripts needs
|
||||
|
||||
|
||||
[master.sequencer]
|
||||
type = "raft" # Choose [raft|etcd|snowflake] type for storing the file id sequence
|
||||
# when sequencer.type = etcd, set listen client urls of etcd cluster that store file id sequence
|
||||
# example : http://127.0.0.1:2379,http://127.0.0.1:2389
|
||||
sequencer_etcd_urls = "http://127.0.0.1:2379"
|
||||
type = "raft" # Choose [raft|snowflake] type for storing the file id sequence
|
||||
# when sequencer.type = snowflake, the snowflake id must be different from other masters
|
||||
sequencer_snowflake_id = 0 # any number between 1~1023
|
||||
|
||||
|
||||
+12
-7
@@ -86,7 +86,7 @@ func init() {
|
||||
serverOptions.debugPort = cmdServer.Flag.Int("debug.port", 6060, "http port for debugging")
|
||||
|
||||
masterOptions.port = cmdServer.Flag.Int("master.port", 9333, "master server http listen port")
|
||||
masterOptions.portGrpc = cmdServer.Flag.Int("master.port.grpc", 19333, "master server grpc listen port")
|
||||
masterOptions.portGrpc = cmdServer.Flag.Int("master.port.grpc", 0, "master server grpc listen port")
|
||||
masterOptions.metaFolder = cmdServer.Flag.String("master.dir", "", "data directory to store meta data, default to same as -dir specified")
|
||||
masterOptions.peers = cmdServer.Flag.String("master.peers", "", "all master nodes in comma separated ip:masterPort list")
|
||||
masterOptions.volumeSizeLimitMB = cmdServer.Flag.Uint("master.volumeSizeLimitMB", 30*1000, "Master stops directing writes to oversized volumes.")
|
||||
@@ -99,7 +99,7 @@ func init() {
|
||||
|
||||
filerOptions.collection = cmdServer.Flag.String("filer.collection", "", "all data will be stored in this collection")
|
||||
filerOptions.port = cmdServer.Flag.Int("filer.port", 8888, "filer server http listen port")
|
||||
filerOptions.portGrpc = cmdServer.Flag.Int("filer.port.grpc", 18888, "filer server grpc listen port")
|
||||
filerOptions.portGrpc = cmdServer.Flag.Int("filer.port.grpc", 0, "filer server grpc listen port")
|
||||
filerOptions.publicPort = cmdServer.Flag.Int("filer.port.public", 0, "filer server public http listen port")
|
||||
filerOptions.defaultReplicaPlacement = cmdServer.Flag.String("filer.defaultReplicaPlacement", "", "default replication type. If not specified, use master setting.")
|
||||
filerOptions.disableDirListing = cmdServer.Flag.Bool("filer.disableDirListing", false, "turn off directory listing")
|
||||
@@ -111,7 +111,7 @@ func init() {
|
||||
filerOptions.concurrentUploadLimitMB = cmdServer.Flag.Int("filer.concurrentUploadLimitMB", 64, "limit total concurrent upload size")
|
||||
|
||||
serverOptions.v.port = cmdServer.Flag.Int("volume.port", 8080, "volume server http listen port")
|
||||
serverOptions.v.portGrpc = cmdServer.Flag.Int("volume.port.grpc", 18080, "volume server grpc listen port")
|
||||
serverOptions.v.portGrpc = cmdServer.Flag.Int("volume.port.grpc", 0, "volume server grpc listen port")
|
||||
serverOptions.v.publicPort = cmdServer.Flag.Int("volume.port.public", 0, "volume server public port")
|
||||
serverOptions.v.indexType = cmdServer.Flag.String("volume.index", "memory", "Choose [memory|leveldb|leveldbMedium|leveldbLarge] mode for memory~performance balance.")
|
||||
serverOptions.v.diskType = cmdServer.Flag.String("volume.disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
|
||||
@@ -132,7 +132,7 @@ func init() {
|
||||
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")
|
||||
s3Options.allowEmptyFolder = cmdServer.Flag.Bool("s3.allowEmptyFolder", true, "allow empty folders")
|
||||
|
||||
webdavOptions.port = cmdServer.Flag.Int("webdav.port", 7333, "webdav server http listen port")
|
||||
webdavOptions.collection = cmdServer.Flag.String("webdav.collection", "", "collection to create the files")
|
||||
@@ -168,16 +168,21 @@ func runServer(cmd *Command, args []string) bool {
|
||||
*isStartingFiler = true
|
||||
}
|
||||
|
||||
if *isStartingMasterServer {
|
||||
_, peerList := checkPeers(*serverIp, *masterOptions.port, *masterOptions.portGrpc, *masterOptions.peers)
|
||||
peers := strings.Join(pb.ToAddressStrings(peerList), ",")
|
||||
masterOptions.peers = &peers
|
||||
}
|
||||
|
||||
// ip address
|
||||
masterOptions.ip = serverIp
|
||||
masterOptions.ipBind = serverBindIp
|
||||
_, masters := checkPeers(*masterOptions.ip, *masterOptions.port, *masterOptions.portGrpc, *masterOptions.peers)
|
||||
filerOptions.masters = masters
|
||||
filerOptions.masters = pb.ServerAddresses(*masterOptions.peers).ToAddresses()
|
||||
filerOptions.ip = serverIp
|
||||
filerOptions.bindIp = serverBindIp
|
||||
serverOptions.v.ip = serverIp
|
||||
serverOptions.v.bindIp = serverBindIp
|
||||
serverOptions.v.masters = masters
|
||||
serverOptions.v.masters = pb.ServerAddresses(*masterOptions.peers).ToAddresses()
|
||||
serverOptions.v.idleConnectionTimeout = serverTimeout
|
||||
serverOptions.v.dataCenter = serverDataCenter
|
||||
serverOptions.v.rack = serverRack
|
||||
|
||||
@@ -70,7 +70,7 @@ type VolumeServerOptions struct {
|
||||
func init() {
|
||||
cmdVolume.Run = runVolume // break init cycle
|
||||
v.port = cmdVolume.Flag.Int("port", 8080, "http listen port")
|
||||
v.portGrpc = cmdVolume.Flag.Int("port.grpc", 18080, "grpc listen port")
|
||||
v.portGrpc = cmdVolume.Flag.Int("port.grpc", 0, "grpc listen port")
|
||||
v.publicPort = cmdVolume.Flag.Int("port.public", 0, "port opened to public")
|
||||
v.ip = cmdVolume.Flag.String("ip", util.DetectedHostAddress(), "ip or server name, also used as identifier")
|
||||
v.publicUrl = cmdVolume.Flag.String("publicUrl", "", "Publicly accessible address")
|
||||
@@ -197,6 +197,9 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
|
||||
if *v.publicPort == 0 {
|
||||
*v.publicPort = *v.port
|
||||
}
|
||||
if *v.portGrpc == 0 {
|
||||
*v.portGrpc = 10000 + *v.port
|
||||
}
|
||||
if *v.publicUrl == "" {
|
||||
*v.publicUrl = util.JoinHostPort(*v.ip, *v.publicPort)
|
||||
}
|
||||
|
||||
+10
-10
@@ -103,16 +103,16 @@ func (wo *WebDavOption) startWebDav() bool {
|
||||
}
|
||||
|
||||
ws, webdavServer_err := weed_server.NewWebDavServer(&weed_server.WebDavOption{
|
||||
Filer: filerAddress,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
Collection: *wo.collection,
|
||||
Replication: *wo.replication,
|
||||
DiskType: *wo.disk,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Cipher: cipher,
|
||||
CacheDir: util.ResolvePath(*wo.cacheDir),
|
||||
CacheSizeMB: *wo.cacheSizeMB,
|
||||
Filer: filerAddress,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
Collection: *wo.collection,
|
||||
Replication: *wo.replication,
|
||||
DiskType: *wo.disk,
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Cipher: cipher,
|
||||
CacheDir: util.ResolvePath(*wo.cacheDir),
|
||||
CacheSizeMB: *wo.cacheSizeMB,
|
||||
})
|
||||
if webdavServer_err != nil {
|
||||
glog.Fatalf("WebDav Server startup error: %v", webdavServer_err)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[redis3]
|
||||
enabled = true
|
||||
address = "localhost:6379"
|
||||
password = ""
|
||||
database = 0
|
||||
@@ -44,6 +44,7 @@ type Filer struct {
|
||||
Signature int32
|
||||
FilerConf *FilerConf
|
||||
RemoteStorage *FilerRemoteStorage
|
||||
UniqueFileId uint32
|
||||
}
|
||||
|
||||
func NewFiler(masters []pb.ServerAddress, grpcDialOption grpc.DialOption,
|
||||
@@ -54,6 +55,7 @@ func NewFiler(masters []pb.ServerAddress, grpcDialOption grpc.DialOption,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
FilerConf: NewFilerConf(),
|
||||
RemoteStorage: NewFilerRemoteStorage(),
|
||||
UniqueFileId: uint32(util.RandomInt32()),
|
||||
}
|
||||
f.LocalMetaLogBuffer = log_buffer.NewLogBuffer("local", LogFlushInterval, f.logFlushFunc, notifyFn)
|
||||
f.metaLogCollection = collection
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -92,8 +93,8 @@ func (f *Filer) logFlushFunc(startTime, stopTime time.Time, buf []byte) {
|
||||
|
||||
startTime, stopTime = startTime.UTC(), stopTime.UTC()
|
||||
|
||||
targetFile := fmt.Sprintf("%s/%04d-%02d-%02d/%02d-%02d.segment", SystemLogDir,
|
||||
startTime.Year(), startTime.Month(), startTime.Day(), startTime.Hour(), startTime.Minute(),
|
||||
targetFile := fmt.Sprintf("%s/%04d-%02d-%02d/%02d-%02d.%08x", SystemLogDir,
|
||||
startTime.Year(), startTime.Month(), startTime.Day(), startTime.Hour(), startTime.Minute(), f.UniqueFileId,
|
||||
// startTime.Second(), startTime.Nanosecond(),
|
||||
)
|
||||
|
||||
@@ -111,7 +112,7 @@ func (f *Filer) ReadPersistedLogBuffer(startTime time.Time, eachLogEntryFn func(
|
||||
|
||||
startTime = startTime.UTC()
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", startTime.Year(), startTime.Month(), startTime.Day())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d.segment", startTime.Hour(), startTime.Minute())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d", startTime.Hour(), startTime.Minute())
|
||||
|
||||
sizeBuf := make([]byte, 4)
|
||||
startTsNs := startTime.UnixNano()
|
||||
@@ -122,14 +123,15 @@ func (f *Filer) ReadPersistedLogBuffer(startTime time.Time, eachLogEntryFn func(
|
||||
}
|
||||
for _, dayEntry := range dayEntries {
|
||||
// println("checking day", dayEntry.FullPath)
|
||||
hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, 24*60, "", "", "")
|
||||
hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, math.MaxInt32, "", "", "")
|
||||
if listHourMinuteErr != nil {
|
||||
return lastTsNs, fmt.Errorf("fail to list log %s by day: %v", dayEntry.Name(), listHourMinuteErr)
|
||||
}
|
||||
for _, hourMinuteEntry := range hourMinuteEntries {
|
||||
// println("checking hh-mm", hourMinuteEntry.FullPath)
|
||||
if dayEntry.Name() == startDate {
|
||||
if strings.Compare(hourMinuteEntry.Name(), startHourMinute) < 0 {
|
||||
hourMinute := util.FileNameBase(hourMinuteEntry.Name())
|
||||
if strings.Compare(hourMinute, startHourMinute) < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ func (f *Filer) appendToFile(targetFile string, data []byte) error {
|
||||
Gid: OS_GID,
|
||||
},
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("find %s: %v", fullpath, err)
|
||||
} else {
|
||||
offset = int64(TotalSize(entry.Chunks))
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ func splitPattern(pattern string) (prefix string, restPattern string) {
|
||||
// For now, prefix and namePattern are mutually exclusive
|
||||
func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, namePatternExclude string) (entries []*Entry, hasMore bool, err error) {
|
||||
|
||||
if limit > math.MaxInt32-1 {
|
||||
limit = math.MaxInt32 - 1
|
||||
}
|
||||
|
||||
_, err = f.StreamListDirectoryEntries(ctx, p, startFileName, inclusive, limit+1, prefix, namePattern, namePatternExclude, func(entry *Entry) bool {
|
||||
entries = append(entries, entry)
|
||||
return true
|
||||
})
|
||||
|
||||
if limit == math.MaxInt64 {
|
||||
limit = math.MaxInt64 - 1
|
||||
}
|
||||
|
||||
hasMore = int64(len(entries)) >= limit+1
|
||||
if hasMore {
|
||||
entries = entries[:limit]
|
||||
|
||||
@@ -3,6 +3,7 @@ package filer
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -120,6 +121,10 @@ func (t *FilerStorePathTranlator) ListDirectoryPrefixedEntries(ctx context.Conte
|
||||
|
||||
newFullPath := t.translatePath(dirPath)
|
||||
|
||||
if limit > math.MaxInt32-1 {
|
||||
limit = math.MaxInt32 - 1
|
||||
}
|
||||
|
||||
return t.actualStore.ListDirectoryPrefixedEntries(ctx, newFullPath, startFileName, includeStartFile, limit, prefix, func(entry *Entry) bool {
|
||||
entry.FullPath = dirPath[:len(t.storeRoot)-1] + entry.FullPath
|
||||
return eachEntryFunc(entry)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/viant/ptrie"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -248,6 +249,9 @@ func (fsw *FilerStoreWrapper) ListDirectoryPrefixedEntries(ctx context.Context,
|
||||
defer func() {
|
||||
stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "prefixList").Observe(time.Since(start).Seconds())
|
||||
}()
|
||||
if limit > math.MaxInt32-1 {
|
||||
limit = math.MaxInt32 - 1
|
||||
}
|
||||
glog.V(4).Infof("ListDirectoryPrefixedEntries %s from %s prefix %s limit %d", dirPath, startFileName, prefix, limit)
|
||||
lastFileName, err = actualStore.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, prefix, eachEntryFunc)
|
||||
if err == ErrUnsupportedListDirectoryPrefixed {
|
||||
|
||||
@@ -110,7 +110,7 @@ func (ma *MetaAggregator) subscribeToOneFiler(f *Filer, self pb.ServerAddress, p
|
||||
}
|
||||
dir := event.Directory
|
||||
// println("received meta change", dir, "size", len(data))
|
||||
ma.MetaLogBuffer.AddToBuffer([]byte(dir), data, 0)
|
||||
ma.MetaLogBuffer.AddToBuffer([]byte(dir), data, event.TsNs)
|
||||
if maybeReplicateMetadataChange != nil {
|
||||
maybeReplicateMetadataChange(event)
|
||||
}
|
||||
|
||||
@@ -120,6 +120,8 @@ func (store *UniversalRedisStore) DeleteFolderChildren(ctx context.Context, full
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s in parent dir: %v", fullpath, err)
|
||||
}
|
||||
// not efficient, but need to remove if it is a directory
|
||||
store.Client.Del(ctx, genDirectoryListKey(string(path)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -144,6 +144,8 @@ func (store *UniversalRedis2Store) DeleteFolderChildren(ctx context.Context, ful
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteFolderChildren %s in parent dir: %v", fullpath, err)
|
||||
}
|
||||
// not efficient, but need to remove if it is a directory
|
||||
store.Client.Del(ctx, genDirectoryListKey(string(path)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/skiplist"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
type ItemList struct {
|
||||
skipList *skiplist.SkipList
|
||||
batchSize int
|
||||
client redis.UniversalClient
|
||||
prefix string
|
||||
}
|
||||
|
||||
func newItemList(client redis.UniversalClient, prefix string, store skiplist.ListStore, batchSize int) *ItemList {
|
||||
return &ItemList{
|
||||
skipList: skiplist.New(store),
|
||||
batchSize: batchSize,
|
||||
client: client,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Be reluctant to create new nodes. Try to fit into either previous node or next node.
|
||||
Prefer to add to previous node.
|
||||
|
||||
There are multiple cases after finding the name for greater or equal node
|
||||
1. found and node.Key == name
|
||||
The node contains a batch with leading key the same as the name
|
||||
nothing to do
|
||||
2. no such node found or node.Key > name
|
||||
|
||||
if no such node found
|
||||
prevNode = list.LargestNode
|
||||
|
||||
// case 2.1
|
||||
if previousNode contains name
|
||||
nothing to do
|
||||
|
||||
// prefer to add to previous node
|
||||
if prevNode != nil {
|
||||
// case 2.2
|
||||
if prevNode has capacity
|
||||
prevNode.add name, and save
|
||||
return
|
||||
// case 2.3
|
||||
split prevNode by name
|
||||
}
|
||||
|
||||
// case 2.4
|
||||
// merge into next node. Avoid too many nodes if adding data in reverse order.
|
||||
if nextNode is not nil and nextNode has capacity
|
||||
delete nextNode.Key
|
||||
nextNode.Key = name
|
||||
nextNode.batch.add name
|
||||
insert nodeNode.Key
|
||||
return
|
||||
|
||||
// case 2.5
|
||||
if prevNode is nil
|
||||
insert new node with key = name, value = batch{name}
|
||||
return
|
||||
|
||||
*/
|
||||
|
||||
func (nl *ItemList) canAddMember(node *skiplist.SkipListElementReference, name string) (alreadyContains bool, nodeSize int, err error) {
|
||||
ctx := context.Background()
|
||||
pipe := nl.client.TxPipeline()
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
countOperation := pipe.ZLexCount(ctx, key, "-", "+")
|
||||
scoreOperationt := pipe.ZScore(ctx, key, name)
|
||||
if _, err = pipe.Exec(ctx); err != nil && err != redis.Nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if err == redis.Nil {
|
||||
err = nil
|
||||
}
|
||||
alreadyContains = scoreOperationt.Err() == nil
|
||||
nodeSize = int(countOperation.Val())
|
||||
return
|
||||
}
|
||||
|
||||
func (nl *ItemList) WriteName(name string) error {
|
||||
|
||||
lookupKey := []byte(name)
|
||||
prevNode, nextNode, found, err := nl.skipList.FindGreaterOrEqual(lookupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// case 1: the name already exists as one leading key in the batch
|
||||
if found && bytes.Compare(nextNode.Key, lookupKey) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var prevNodeReference *skiplist.SkipListElementReference
|
||||
if !found {
|
||||
prevNodeReference = nl.skipList.GetLargestNodeReference()
|
||||
}
|
||||
|
||||
if nextNode != nil && prevNode == nil {
|
||||
prevNodeReference = nextNode.Prev
|
||||
}
|
||||
|
||||
if prevNodeReference != nil {
|
||||
alreadyContains, nodeSize, err := nl.canAddMember(prevNodeReference, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if alreadyContains {
|
||||
// case 2.1
|
||||
return nil
|
||||
}
|
||||
|
||||
// case 2.2
|
||||
if nodeSize < nl.batchSize {
|
||||
return nl.NodeAddMember(prevNodeReference, name)
|
||||
}
|
||||
|
||||
// case 2.3
|
||||
x := nl.NodeInnerPosition(prevNodeReference, name)
|
||||
y := nodeSize - x
|
||||
addToX := x <= y
|
||||
// add to a new node
|
||||
if x == 0 || y == 0 {
|
||||
if err := nl.ItemAdd(lookupKey, 0, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if addToX {
|
||||
// collect names before name, add them to X
|
||||
namesToX, err := nl.NodeRangeBeforeExclusive(prevNodeReference, name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// delete skiplist reference to old node
|
||||
if _, err := nl.skipList.DeleteByKey(prevNodeReference.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
// add namesToY and name to a new X
|
||||
namesToX = append(namesToX, name)
|
||||
if err := nl.ItemAdd([]byte(namesToX[0]), 0, namesToX...); err != nil {
|
||||
return nil
|
||||
}
|
||||
// remove names less than name from current Y
|
||||
if err := nl.NodeDeleteBeforeExclusive(prevNodeReference, name); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// point skip list to current Y
|
||||
if err := nl.ItemAdd(lookupKey, prevNodeReference.ElementPointer); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
// collect names after name, add them to Y
|
||||
namesToY, err := nl.NodeRangeAfterExclusive(prevNodeReference, name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// add namesToY and name to a new Y
|
||||
namesToY = append(namesToY, name)
|
||||
if err := nl.ItemAdd(lookupKey, 0, namesToY...); err != nil {
|
||||
return nil
|
||||
}
|
||||
// remove names after name from current X
|
||||
if err := nl.NodeDeleteAfterExclusive(prevNodeReference, name); err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// case 2.4
|
||||
if nextNode != nil {
|
||||
nodeSize := nl.NodeSize(nextNode.Reference())
|
||||
if nodeSize < nl.batchSize {
|
||||
if id, err := nl.skipList.DeleteByKey(nextNode.Key); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := nl.ItemAdd(lookupKey, id, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// case 2.5
|
||||
// now prevNode is nil
|
||||
return nl.ItemAdd(lookupKey, 0, name)
|
||||
}
|
||||
|
||||
/*
|
||||
// case 1: exists in nextNode
|
||||
if nextNode != nil && nextNode.Key == name {
|
||||
remove from nextNode, update nextNode
|
||||
// TODO: merge with prevNode if possible?
|
||||
return
|
||||
}
|
||||
if nextNode is nil
|
||||
prevNode = list.Largestnode
|
||||
if prevNode == nil and nextNode.Prev != nil
|
||||
prevNode = load(nextNode.Prev)
|
||||
|
||||
// case 2: does not exist
|
||||
// case 2.1
|
||||
if prevNode == nil {
|
||||
return
|
||||
}
|
||||
// case 2.2
|
||||
if prevNameBatch does not contain name {
|
||||
return
|
||||
}
|
||||
|
||||
// case 3
|
||||
delete from prevNameBatch
|
||||
if prevNameBatch + nextNode < capacityList
|
||||
// case 3.1
|
||||
merge
|
||||
else
|
||||
// case 3.2
|
||||
update prevNode
|
||||
|
||||
|
||||
*/
|
||||
func (nl *ItemList) DeleteName(name string) error {
|
||||
lookupKey := []byte(name)
|
||||
prevNode, nextNode, found, err := nl.skipList.FindGreaterOrEqual(lookupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// case 1
|
||||
if found && bytes.Compare(nextNode.Key, lookupKey) == 0 {
|
||||
if _, err := nl.skipList.DeleteByKey(nextNode.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := nl.NodeDeleteMember(nextNode.Reference(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
minName := nl.NodeMin(nextNode.Reference())
|
||||
if minName == "" {
|
||||
return nl.NodeDelete(nextNode.Reference())
|
||||
}
|
||||
return nl.ItemAdd([]byte(minName), nextNode.Id)
|
||||
}
|
||||
|
||||
if !found {
|
||||
prevNode, err = nl.skipList.GetLargestNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if nextNode != nil && prevNode == nil {
|
||||
prevNode, err = nl.skipList.LoadElement(nextNode.Prev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// case 2
|
||||
if prevNode == nil {
|
||||
// case 2.1
|
||||
return nil
|
||||
}
|
||||
if !nl.NodeContainsItem(prevNode.Reference(), name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// case 3
|
||||
if err := nl.NodeDeleteMember(prevNode.Reference(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
prevSize := nl.NodeSize(prevNode.Reference())
|
||||
if prevSize == 0 {
|
||||
if _, err := nl.skipList.DeleteByKey(prevNode.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
nextSize := nl.NodeSize(nextNode.Reference())
|
||||
if nextSize > 0 && prevSize+nextSize < nl.batchSize {
|
||||
// case 3.1 merge nextNode and prevNode
|
||||
if _, err := nl.skipList.DeleteByKey(nextNode.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
nextNames, err := nl.NodeRangeBeforeExclusive(nextNode.Reference(), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := nl.NodeAddMember(prevNode.Reference(), nextNames...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nl.NodeDelete(nextNode.Reference())
|
||||
} else {
|
||||
// case 3.2 update prevNode
|
||||
// no action to take
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl *ItemList) ListNames(startFrom string, visitNamesFn func(name string) bool) error {
|
||||
lookupKey := []byte(startFrom)
|
||||
prevNode, nextNode, found, err := nl.skipList.FindGreaterOrEqual(lookupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found && bytes.Compare(nextNode.Key, lookupKey) == 0 {
|
||||
prevNode = nil
|
||||
}
|
||||
if !found {
|
||||
prevNode, err = nl.skipList.GetLargestNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if prevNode != nil {
|
||||
if !nl.NodeScanIncluseiveAfter(prevNode.Reference(), startFrom, visitNamesFn) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for nextNode != nil {
|
||||
if !nl.NodeScanIncluseiveAfter(nextNode.Reference(), startFrom, visitNamesFn) {
|
||||
return nil
|
||||
}
|
||||
nextNode, err = nl.skipList.LoadElement(nextNode.Next[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nl *ItemList) RemoteAllListElement() error {
|
||||
|
||||
t := nl.skipList
|
||||
|
||||
nodeRef := t.StartLevels[0]
|
||||
for nodeRef != nil {
|
||||
node, err := t.LoadElement(nodeRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if err := t.DeleteElement(node); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := nl.NodeDelete(node.Reference()); err != nil {
|
||||
return err
|
||||
}
|
||||
nodeRef = node.Next[0]
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeContainsItem(node *skiplist.SkipListElementReference, item string) bool {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
_, err := nl.client.ZScore(context.Background(), key, item).Result()
|
||||
if err == redis.Nil {
|
||||
return false
|
||||
}
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeSize(node *skiplist.SkipListElementReference) int {
|
||||
if node == nil {
|
||||
return 0
|
||||
}
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
return int(nl.client.ZLexCount(context.Background(), key, "-", "+").Val())
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeAddMember(node *skiplist.SkipListElementReference, names ...string) error {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
var members []*redis.Z
|
||||
for _, name := range names {
|
||||
members = append(members, &redis.Z{
|
||||
Score: 0,
|
||||
Member: name,
|
||||
})
|
||||
}
|
||||
return nl.client.ZAddNX(context.Background(), key, members...).Err()
|
||||
}
|
||||
func (nl *ItemList) NodeDeleteMember(node *skiplist.SkipListElementReference, name string) error {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
return nl.client.ZRem(context.Background(), key, name).Err()
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeDelete(node *skiplist.SkipListElementReference) error {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
return nl.client.Del(context.Background(), key).Err()
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeInnerPosition(node *skiplist.SkipListElementReference, name string) int {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
return int(nl.client.ZLexCount(context.Background(), key, "-", "("+name).Val())
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeMin(node *skiplist.SkipListElementReference) string {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
slice := nl.client.ZRangeByLex(context.Background(), key, &redis.ZRangeBy{
|
||||
Min: "-",
|
||||
Max: "+",
|
||||
Offset: 0,
|
||||
Count: 1,
|
||||
}).Val()
|
||||
if len(slice) > 0 {
|
||||
s := slice[0]
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeScanIncluseiveAfter(node *skiplist.SkipListElementReference, startFrom string, visitNamesFn func(name string) bool) bool {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
if startFrom == "" {
|
||||
startFrom = "-"
|
||||
} else {
|
||||
startFrom = "[" + startFrom
|
||||
}
|
||||
names := nl.client.ZRangeByLex(context.Background(), key, &redis.ZRangeBy{
|
||||
Min: startFrom,
|
||||
Max: "+",
|
||||
}).Val()
|
||||
for _, n := range names {
|
||||
if !visitNamesFn(n) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeRangeBeforeExclusive(node *skiplist.SkipListElementReference, stopAt string) ([]string, error) {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
if stopAt == "" {
|
||||
stopAt = "+"
|
||||
} else {
|
||||
stopAt = "(" + stopAt
|
||||
}
|
||||
return nl.client.ZRangeByLex(context.Background(), key, &redis.ZRangeBy{
|
||||
Min: "-",
|
||||
Max: stopAt,
|
||||
}).Result()
|
||||
}
|
||||
func (nl *ItemList) NodeRangeAfterExclusive(node *skiplist.SkipListElementReference, startFrom string) ([]string, error) {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
if startFrom == "" {
|
||||
startFrom = "-"
|
||||
} else {
|
||||
startFrom = "(" + startFrom
|
||||
}
|
||||
return nl.client.ZRangeByLex(context.Background(), key, &redis.ZRangeBy{
|
||||
Min: startFrom,
|
||||
Max: "+",
|
||||
}).Result()
|
||||
}
|
||||
|
||||
func (nl *ItemList) NodeDeleteBeforeExclusive(node *skiplist.SkipListElementReference, stopAt string) error {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
if stopAt == "" {
|
||||
stopAt = "+"
|
||||
} else {
|
||||
stopAt = "(" + stopAt
|
||||
}
|
||||
return nl.client.ZRemRangeByLex(context.Background(), key, "-", stopAt).Err()
|
||||
}
|
||||
func (nl *ItemList) NodeDeleteAfterExclusive(node *skiplist.SkipListElementReference, startFrom string) error {
|
||||
key := fmt.Sprintf("%s%dm", nl.prefix, node.ElementPointer)
|
||||
if startFrom == "" {
|
||||
startFrom = "-"
|
||||
} else {
|
||||
startFrom = "(" + startFrom
|
||||
}
|
||||
return nl.client.ZRemRangeByLex(context.Background(), key, startFrom, "+").Err()
|
||||
}
|
||||
|
||||
func (nl *ItemList) ItemAdd(lookupKey []byte, idIfKnown int64, names ...string) error {
|
||||
if id, err := nl.skipList.InsertByKey(lookupKey, idIfKnown, nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if len(names) > 0 {
|
||||
return nl.NodeAddMember(&skiplist.SkipListElementReference{
|
||||
ElementPointer: id,
|
||||
Key: lookupKey,
|
||||
}, names...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/skiplist"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
func LoadItemList(data []byte, prefix string, client redis.UniversalClient, store skiplist.ListStore, batchSize int) *ItemList {
|
||||
|
||||
nl := &ItemList{
|
||||
skipList: skiplist.New(store),
|
||||
batchSize: batchSize,
|
||||
client: client,
|
||||
prefix: prefix,
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nl
|
||||
}
|
||||
|
||||
message := &skiplist.SkipListProto{}
|
||||
if err := proto.Unmarshal(data, message); err != nil {
|
||||
glog.Errorf("loading skiplist: %v", err)
|
||||
}
|
||||
nl.skipList.MaxNewLevel = int(message.MaxNewLevel)
|
||||
nl.skipList.MaxLevel = int(message.MaxLevel)
|
||||
for i, ref := range message.StartLevels {
|
||||
nl.skipList.StartLevels[i] = &skiplist.SkipListElementReference{
|
||||
ElementPointer: ref.ElementPointer,
|
||||
Key: ref.Key,
|
||||
}
|
||||
}
|
||||
for i, ref := range message.EndLevels {
|
||||
nl.skipList.EndLevels[i] = &skiplist.SkipListElementReference{
|
||||
ElementPointer: ref.ElementPointer,
|
||||
Key: ref.Key,
|
||||
}
|
||||
}
|
||||
return nl
|
||||
}
|
||||
|
||||
func (nl *ItemList) HasChanges() bool {
|
||||
return nl.skipList.HasChanges
|
||||
}
|
||||
|
||||
func (nl *ItemList) ToBytes() []byte {
|
||||
message := &skiplist.SkipListProto{}
|
||||
message.MaxNewLevel = int32(nl.skipList.MaxNewLevel)
|
||||
message.MaxLevel = int32(nl.skipList.MaxLevel)
|
||||
for _, ref := range nl.skipList.StartLevels {
|
||||
if ref == nil {
|
||||
break
|
||||
}
|
||||
message.StartLevels = append(message.StartLevels, &skiplist.SkipListElementReference{
|
||||
ElementPointer: ref.ElementPointer,
|
||||
Key: ref.Key,
|
||||
})
|
||||
}
|
||||
for _, ref := range nl.skipList.EndLevels {
|
||||
if ref == nil {
|
||||
break
|
||||
}
|
||||
message.EndLevels = append(message.EndLevels, &skiplist.SkipListElementReference{
|
||||
ElementPointer: ref.ElementPointer,
|
||||
Key: ref.Key,
|
||||
})
|
||||
}
|
||||
data, err := proto.Marshal(message)
|
||||
if err != nil {
|
||||
glog.Errorf("marshal skiplist: %v", err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
const maxNameBatchSizeLimit = 1000000
|
||||
|
||||
func insertChild(ctx context.Context, redisStore *UniversalRedis3Store, key string, name string) error {
|
||||
|
||||
// lock and unlock
|
||||
mutex := redisStore.redsync.NewMutex(key + "lock")
|
||||
if err := mutex.Lock(); err != nil {
|
||||
return fmt.Errorf("lock %s: %v", key, err)
|
||||
}
|
||||
defer func() {
|
||||
mutex.Unlock()
|
||||
}()
|
||||
|
||||
client := redisStore.Client
|
||||
data, err := client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
return fmt.Errorf("read %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
store := newSkipListElementStore(key, client)
|
||||
nameList := LoadItemList([]byte(data), key, client, store, maxNameBatchSizeLimit)
|
||||
|
||||
if err := nameList.WriteName(name); err != nil {
|
||||
glog.Errorf("add %s %s: %v", key, name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !nameList.HasChanges() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := client.Set(ctx, key, nameList.ToBytes(), 0).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeChild(ctx context.Context, redisStore *UniversalRedis3Store, key string, name string) error {
|
||||
|
||||
// lock and unlock
|
||||
mutex := redisStore.redsync.NewMutex(key + "lock")
|
||||
if err := mutex.Lock(); err != nil {
|
||||
return fmt.Errorf("lock %s: %v", key, err)
|
||||
}
|
||||
defer mutex.Unlock()
|
||||
|
||||
client := redisStore.Client
|
||||
data, err := client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
return fmt.Errorf("read %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
store := newSkipListElementStore(key, client)
|
||||
nameList := LoadItemList([]byte(data), key, client, store, maxNameBatchSizeLimit)
|
||||
|
||||
if err := nameList.DeleteName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if !nameList.HasChanges() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := client.Set(ctx, key, nameList.ToBytes(), 0).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeChildren(ctx context.Context, redisStore *UniversalRedis3Store, key string, onDeleteFn func(name string) error) error {
|
||||
|
||||
// lock and unlock
|
||||
mutex := redisStore.redsync.NewMutex(key + "lock")
|
||||
if err := mutex.Lock(); err != nil {
|
||||
return fmt.Errorf("lock %s: %v", key, err)
|
||||
}
|
||||
defer mutex.Unlock()
|
||||
|
||||
client := redisStore.Client
|
||||
data, err := client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
return fmt.Errorf("read %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
store := newSkipListElementStore(key, client)
|
||||
nameList := LoadItemList([]byte(data), key, client, store, maxNameBatchSizeLimit)
|
||||
|
||||
if err = nameList.ListNames("", func(name string) bool {
|
||||
if err := onDeleteFn(name); err != nil {
|
||||
glog.Errorf("delete %s child %s: %v", key, name, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = nameList.RemoteAllListElement(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func listChildren(ctx context.Context, redisStore *UniversalRedis3Store, key string, startFileName string, eachFn func(name string) bool) error {
|
||||
client := redisStore.Client
|
||||
data, err := client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
return fmt.Errorf("read %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
store := newSkipListElementStore(key, client)
|
||||
nameList := LoadItemList([]byte(data), key, client, store, maxNameBatchSizeLimit)
|
||||
|
||||
if err = nameList.ListNames(startFileName, func(name string) bool {
|
||||
return eachFn(name)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/stvp/tempredis"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var names = []string{
|
||||
"cassandra.in.sh",
|
||||
"cassandra",
|
||||
"debug-cql.bat",
|
||||
"nodetool",
|
||||
"nodetool.bat",
|
||||
"source-conf.ps1",
|
||||
"sstableloader",
|
||||
"sstableloader.bat",
|
||||
"sstablescrub",
|
||||
"sstablescrub.bat",
|
||||
"sstableupgrade",
|
||||
"sstableupgrade.bat",
|
||||
"sstableutil",
|
||||
"sstableutil.bat",
|
||||
"sstableverify",
|
||||
"sstableverify.bat",
|
||||
"stop-server",
|
||||
"stop-server.bat",
|
||||
"stop-server.ps1",
|
||||
"cassandra.in.bat",
|
||||
"cqlsh.py",
|
||||
"cqlsh",
|
||||
"cassandra.ps1",
|
||||
"cqlsh.bat",
|
||||
"debug-cql",
|
||||
"cassandra.bat",
|
||||
}
|
||||
|
||||
func yTestNameList(t *testing.T) {
|
||||
server, err := tempredis.Start(tempredis.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Term()
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Network: "unix",
|
||||
Addr: server.Socket(),
|
||||
})
|
||||
|
||||
store := newSkipListElementStore("/yyy/bin", client)
|
||||
var data []byte
|
||||
for _, name := range names {
|
||||
nameList := LoadItemList(data, "/yyy/bin", client, store, maxNameBatchSizeLimit)
|
||||
nameList.WriteName(name)
|
||||
|
||||
nameList.ListNames("", func(name string) bool {
|
||||
println(name)
|
||||
return true
|
||||
})
|
||||
|
||||
if nameList.HasChanges() {
|
||||
data = nameList.ToBytes()
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
nameList := LoadItemList(data, "/yyy/bin", client, store, maxNameBatchSizeLimit)
|
||||
nameList.ListNames("", func(name string) bool {
|
||||
println(name)
|
||||
return true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func yBenchmarkNameList(b *testing.B) {
|
||||
|
||||
server, err := tempredis.Start(tempredis.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Term()
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Network: "unix",
|
||||
Addr: server.Socket(),
|
||||
})
|
||||
|
||||
store := newSkipListElementStore("/yyy/bin", client)
|
||||
var data []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
nameList := LoadItemList(data, "/yyy/bin", client, store, maxNameBatchSizeLimit)
|
||||
|
||||
nameList.WriteName(strconv.Itoa(i) + "namexxxxxxxxxxxxxxxxxxx")
|
||||
|
||||
if nameList.HasChanges() {
|
||||
data = nameList.ToBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRedis(b *testing.B) {
|
||||
|
||||
server, err := tempredis.Start(tempredis.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Term()
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Network: "unix",
|
||||
Addr: server.Socket(),
|
||||
})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
client.ZAddNX(context.Background(), "/yyy/bin", &redis.Z{Score: 0, Member: strconv.Itoa(i) + "namexxxxxxxxxxxxxxxxxxx"})
|
||||
}
|
||||
}
|
||||
|
||||
func xTestNameListAdd(t *testing.T) {
|
||||
|
||||
server, err := tempredis.Start(tempredis.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Term()
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
client.FlushAll(context.Background())
|
||||
|
||||
N := 364800
|
||||
|
||||
ts0 := time.Now()
|
||||
store := newSkipListElementStore("/y", client)
|
||||
var data []byte
|
||||
nameList := LoadItemList(data, "/y", client, store, 100000)
|
||||
for i := 0; i < N; i++ {
|
||||
nameList.WriteName(fmt.Sprintf("%8d", i))
|
||||
}
|
||||
|
||||
ts1 := time.Now()
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
client.ZAddNX(context.Background(), "/x", &redis.Z{Score: 0, Member: fmt.Sprintf("name %8d", i)})
|
||||
}
|
||||
ts2 := time.Now()
|
||||
|
||||
fmt.Printf("%v %v", ts1.Sub(ts0), ts2.Sub(ts1))
|
||||
|
||||
/*
|
||||
keys := client.Keys(context.Background(), "/*m").Val()
|
||||
for _, k := range keys {
|
||||
println("key", k)
|
||||
for i, v := range client.ZRangeByLex(context.Background(), k, &redis.ZRangeBy{
|
||||
Min: "-",
|
||||
Max: "+",
|
||||
}).Val() {
|
||||
println(" ", i, v)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func xBenchmarkNameList(b *testing.B) {
|
||||
|
||||
server, err := tempredis.Start(tempredis.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer server.Term()
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
store := newSkipListElementStore("/yyy/bin", client)
|
||||
var data []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
nameList := LoadItemList(data, "/yyy/bin", client, store, maxNameBatchSizeLimit)
|
||||
|
||||
nameList.WriteName(fmt.Sprintf("name %8d", i))
|
||||
|
||||
if nameList.HasChanges() {
|
||||
data = nameList.ToBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func xBenchmarkRedis(b *testing.B) {
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
client.ZAddNX(context.Background(), "/xxx/bin", &redis.Z{Score: 0, Member: fmt.Sprintf("name %8d", i)})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &RedisCluster3Store{})
|
||||
}
|
||||
|
||||
type RedisCluster3Store struct {
|
||||
UniversalRedis3Store
|
||||
}
|
||||
|
||||
func (store *RedisCluster3Store) GetName() string {
|
||||
return "redis_cluster3"
|
||||
}
|
||||
|
||||
func (store *RedisCluster3Store) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
|
||||
configuration.SetDefault(prefix+"useReadOnly", false)
|
||||
configuration.SetDefault(prefix+"routeByLatency", false)
|
||||
|
||||
return store.initialize(
|
||||
configuration.GetStringSlice(prefix+"addresses"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetBool(prefix+"useReadOnly"),
|
||||
configuration.GetBool(prefix+"routeByLatency"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *RedisCluster3Store) initialize(addresses []string, password string, readOnly, routeByLatency bool) (err error) {
|
||||
store.Client = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: addresses,
|
||||
Password: password,
|
||||
ReadOnly: readOnly,
|
||||
RouteByLatency: routeByLatency,
|
||||
})
|
||||
store.redsync = redsync.New(goredis.NewPool(store.Client))
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &Redis3Store{})
|
||||
}
|
||||
|
||||
type Redis3Store struct {
|
||||
UniversalRedis3Store
|
||||
}
|
||||
|
||||
func (store *Redis3Store) GetName() string {
|
||||
return "redis3"
|
||||
}
|
||||
|
||||
func (store *Redis3Store) Initialize(configuration util.Configuration, prefix string) (err error) {
|
||||
return store.initialize(
|
||||
configuration.GetString(prefix+"address"),
|
||||
configuration.GetString(prefix+"password"),
|
||||
configuration.GetInt(prefix+"database"),
|
||||
)
|
||||
}
|
||||
|
||||
func (store *Redis3Store) initialize(hostPort string, password string, database int) (err error) {
|
||||
store.Client = redis.NewClient(&redis.Options{
|
||||
Addr: hostPort,
|
||||
Password: password,
|
||||
DB: database,
|
||||
})
|
||||
store.redsync = redsync.New(goredis.NewPool(store.Client))
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/skiplist"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
type SkipListElementStore struct {
|
||||
Prefix string
|
||||
client redis.UniversalClient
|
||||
}
|
||||
|
||||
var _ = skiplist.ListStore(&SkipListElementStore{})
|
||||
|
||||
func newSkipListElementStore(prefix string, client redis.UniversalClient) *SkipListElementStore {
|
||||
return &SkipListElementStore{
|
||||
Prefix: prefix,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SkipListElementStore) SaveElement(id int64, element *skiplist.SkipListElement) error {
|
||||
key := fmt.Sprintf("%s%d", m.Prefix, id)
|
||||
data, err := proto.Marshal(element)
|
||||
if err != nil {
|
||||
glog.Errorf("marshal %s: %v", key, err)
|
||||
}
|
||||
return m.client.Set(context.Background(), key, data, 0).Err()
|
||||
}
|
||||
|
||||
func (m *SkipListElementStore) DeleteElement(id int64) error {
|
||||
key := fmt.Sprintf("%s%d", m.Prefix, id)
|
||||
return m.client.Del(context.Background(), key).Err()
|
||||
}
|
||||
|
||||
func (m *SkipListElementStore) LoadElement(id int64) (*skiplist.SkipListElement, error) {
|
||||
key := fmt.Sprintf("%s%d", m.Prefix, id)
|
||||
data, err := m.client.Get(context.Background(), key).Result()
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t := &skiplist.SkipListElement{}
|
||||
err = proto.Unmarshal([]byte(data), t)
|
||||
if err == nil {
|
||||
for i := 0; i < len(t.Next); i++ {
|
||||
if t.Next[i].IsNil() {
|
||||
t.Next[i] = nil
|
||||
}
|
||||
}
|
||||
if t.Prev.IsNil() {
|
||||
t.Prev = nil
|
||||
}
|
||||
}
|
||||
return t, err
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
DIR_LIST_MARKER = "\x00"
|
||||
)
|
||||
|
||||
type UniversalRedis3Store struct {
|
||||
Client redis.UniversalClient
|
||||
redsync *redsync.Redsync
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
func (store *UniversalRedis3Store) CommitTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (store *UniversalRedis3Store) RollbackTransaction(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
|
||||
value, err := entry.EncodeAttributesAndChunks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
|
||||
}
|
||||
|
||||
if len(entry.Chunks) > 50 {
|
||||
value = util.MaybeGzipData(value)
|
||||
}
|
||||
|
||||
if err = store.Client.Set(ctx, string(entry.FullPath), value, time.Duration(entry.TtlSec)*time.Second).Err(); err != nil {
|
||||
return fmt.Errorf("persisting %s : %v", entry.FullPath, err)
|
||||
}
|
||||
|
||||
dir, name := entry.FullPath.DirAndName()
|
||||
|
||||
if name != "" {
|
||||
if err = insertChild(ctx, store, genDirectoryListKey(dir), name); err != nil {
|
||||
return fmt.Errorf("persisting %s in parent dir: %v", entry.FullPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
|
||||
|
||||
return store.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) FindEntry(ctx context.Context, fullpath util.FullPath) (entry *filer.Entry, err error) {
|
||||
|
||||
data, err := store.Client.Get(ctx, string(fullpath)).Result()
|
||||
if err == redis.Nil {
|
||||
return nil, filer_pb.ErrNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
entry = &filer.Entry{
|
||||
FullPath: fullpath,
|
||||
}
|
||||
err = entry.DecodeAttributesAndChunks(util.MaybeDecompressData([]byte(data)))
|
||||
if err != nil {
|
||||
return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) DeleteEntry(ctx context.Context, fullpath util.FullPath) (err error) {
|
||||
|
||||
_, err = store.Client.Del(ctx, genDirectoryListKey(string(fullpath))).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete dir list %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
_, err = store.Client.Del(ctx, string(fullpath)).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s : %v", fullpath, err)
|
||||
}
|
||||
|
||||
dir, name := fullpath.DirAndName()
|
||||
|
||||
if name != "" {
|
||||
if err = removeChild(ctx, store, genDirectoryListKey(dir), name); err != nil {
|
||||
return fmt.Errorf("DeleteEntry %s in parent dir: %v", fullpath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) DeleteFolderChildren(ctx context.Context, fullpath util.FullPath) (err error) {
|
||||
|
||||
return removeChildren(ctx, store, genDirectoryListKey(string(fullpath)), func(name string) error {
|
||||
path := util.NewFullPath(string(fullpath), name)
|
||||
_, err = store.Client.Del(ctx, string(path)).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteFolderChildren %s in parent dir: %v", fullpath, err)
|
||||
}
|
||||
// not efficient, but need to remove if it is a directory
|
||||
store.Client.Del(ctx, genDirectoryListKey(string(path)))
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
|
||||
return lastFileName, filer.ErrUnsupportedListDirectoryPrefixed
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
|
||||
|
||||
dirListKey := genDirectoryListKey(string(dirPath))
|
||||
counter := int64(0)
|
||||
|
||||
err = listChildren(ctx, store, dirListKey, startFileName, func(fileName string) bool {
|
||||
if startFileName != "" {
|
||||
if !includeStartFile && startFileName == fileName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
path := util.NewFullPath(string(dirPath), fileName)
|
||||
entry, err := store.FindEntry(ctx, path)
|
||||
lastFileName = fileName
|
||||
if err != nil {
|
||||
glog.V(0).Infof("list %s : %v", path, err)
|
||||
if err == filer_pb.ErrNotFound {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if entry.TtlSec > 0 {
|
||||
if entry.Attr.Crtime.Add(time.Duration(entry.TtlSec) * time.Second).Before(time.Now()) {
|
||||
store.Client.Del(ctx, string(path)).Result()
|
||||
store.Client.ZRem(ctx, dirListKey, fileName).Result()
|
||||
return true
|
||||
}
|
||||
}
|
||||
counter++
|
||||
if !eachEntryFunc(entry) {
|
||||
return false
|
||||
}
|
||||
if counter >= limit {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return lastFileName, err
|
||||
}
|
||||
|
||||
func genDirectoryListKey(dir string) (dirList string) {
|
||||
return dir + DIR_LIST_MARKER
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) Shutdown() {
|
||||
store.Client.Close()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package redis3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func (store *UniversalRedis3Store) KvPut(ctx context.Context, key []byte, value []byte) (err error) {
|
||||
|
||||
_, err = store.Client.Set(ctx, string(key), value, 0).Result()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kv put: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) KvGet(ctx context.Context, key []byte) (value []byte, err error) {
|
||||
|
||||
data, err := store.Client.Get(ctx, string(key)).Result()
|
||||
|
||||
if err == redis.Nil {
|
||||
return nil, filer.ErrKvNotFound
|
||||
}
|
||||
|
||||
return []byte(data), err
|
||||
}
|
||||
|
||||
func (store *UniversalRedis3Store) KvDelete(ctx context.Context, key []byte) (err error) {
|
||||
|
||||
_, err = store.Client.Del(ctx, string(key)).Result()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kv delete: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/tecbot/gorocksdb"
|
||||
gorocksdb "github.com/linxGnu/grocksdb"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
|
||||
@@ -5,7 +5,7 @@ package rocksdb
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tecbot/gorocksdb"
|
||||
gorocksdb "github.com/linxGnu/grocksdb"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
)
|
||||
@@ -38,3 +38,8 @@ func (t *TTLFilter) Filter(level int, key, val []byte) (remove bool, newVal []by
|
||||
func (t *TTLFilter) Name() string {
|
||||
return "TTLFilter"
|
||||
}
|
||||
func (t *TTLFilter) SetIgnoreSnapshots(value bool) {
|
||||
}
|
||||
|
||||
func (t *TTLFilter) Destroy() {
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package tikv
|
||||
|
||||
/*
|
||||
* This empty file is let go build can work without tikv tag
|
||||
*/
|
||||
@@ -1,389 +0,0 @@
|
||||
//go:build tikv
|
||||
// +build tikv
|
||||
|
||||
package tikv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"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/tikv/client-go/v2/tikv"
|
||||
"github.com/tikv/client-go/v2/txnkv"
|
||||
)
|
||||
|
||||
var (
|
||||
_ filer.FilerStore = ((*TikvStore)(nil))
|
||||
)
|
||||
|
||||
func init() {
|
||||
filer.Stores = append(filer.Stores, &TikvStore{})
|
||||
}
|
||||
|
||||
type TikvStore struct {
|
||||
client *tikv.KVStore
|
||||
deleteRangeConcurrency int
|
||||
}
|
||||
|
||||
// Basic APIs
|
||||
func (store *TikvStore) GetName() string {
|
||||
return "tikv"
|
||||
}
|
||||
|
||||
func (store *TikvStore) Initialize(config util.Configuration, prefix string) error {
|
||||
pdAddrs := []string{}
|
||||
pdAddrsStr := config.GetString(prefix + "pdaddrs")
|
||||
for _, item := range strings.Split(pdAddrsStr, ",") {
|
||||
pdAddrs = append(pdAddrs, strings.TrimSpace(item))
|
||||
}
|
||||
drc := config.GetInt(prefix + "deleterange_concurrency")
|
||||
if drc <= 0 {
|
||||
drc = 1
|
||||
}
|
||||
store.deleteRangeConcurrency = drc
|
||||
return store.initialize(pdAddrs)
|
||||
}
|
||||
|
||||
func (store *TikvStore) initialize(pdAddrs []string) error {
|
||||
client, err := tikv.NewTxnClient(pdAddrs)
|
||||
store.client = client
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *TikvStore) Shutdown() {
|
||||
err := store.client.Close()
|
||||
if err != nil {
|
||||
glog.V(0).Infof("Shutdown TiKV client got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ~ Basic APIs
|
||||
|
||||
// Entry APIs
|
||||
func (store *TikvStore) InsertEntry(ctx context.Context, entry *filer.Entry) error {
|
||||
dir, name := entry.DirAndName()
|
||||
key := generateKey(dir, name)
|
||||
|
||||
value, err := entry.EncodeAttributesAndChunks()
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
|
||||
}
|
||||
txn, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = txn.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
return txn.Set(key, value)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("persisting %s : %v", entry.FullPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *TikvStore) UpdateEntry(ctx context.Context, entry *filer.Entry) error {
|
||||
return store.InsertEntry(ctx, entry)
|
||||
}
|
||||
|
||||
func (store *TikvStore) FindEntry(ctx context.Context, path util.FullPath) (*filer.Entry, error) {
|
||||
dir, name := path.DirAndName()
|
||||
key := generateKey(dir, name)
|
||||
|
||||
txn, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var value []byte = nil
|
||||
err = txn.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
val, err := txn.Get(context.TODO(), key)
|
||||
if err == nil {
|
||||
value = val
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if isNotExists(err) || value == nil {
|
||||
return nil, filer_pb.ErrNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get %s : %v", path, err)
|
||||
}
|
||||
|
||||
entry := &filer.Entry{
|
||||
FullPath: path,
|
||||
}
|
||||
err = entry.DecodeAttributesAndChunks(value)
|
||||
if err != nil {
|
||||
return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (store *TikvStore) DeleteEntry(ctx context.Context, path util.FullPath) error {
|
||||
dir, name := path.DirAndName()
|
||||
key := generateKey(dir, name)
|
||||
|
||||
txn, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = txn.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
return txn.Delete(key)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s : %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ~ Entry APIs
|
||||
|
||||
// Directory APIs
|
||||
func (store *TikvStore) DeleteFolderChildren(ctx context.Context, path util.FullPath) error {
|
||||
directoryPrefix := genDirectoryKeyPrefix(path, "")
|
||||
|
||||
txn, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
startKey []byte = nil
|
||||
endKey []byte = nil
|
||||
)
|
||||
err = txn.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
iter, err := txn.Iter(directoryPrefix, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer iter.Close()
|
||||
for iter.Valid() {
|
||||
key := iter.Key()
|
||||
endKey = key
|
||||
if !bytes.HasPrefix(key, directoryPrefix) {
|
||||
break
|
||||
}
|
||||
if startKey == nil {
|
||||
startKey = key
|
||||
}
|
||||
|
||||
err = iter.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Only one Key matched just delete it.
|
||||
if startKey != nil && bytes.Equal(startKey, endKey) {
|
||||
return txn.Delete(startKey)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s : %v", path, err)
|
||||
}
|
||||
|
||||
if startKey != nil && endKey != nil && !bytes.Equal(startKey, endKey) {
|
||||
// has startKey and endKey and they are not equals, so use delete range
|
||||
_, err = store.client.DeleteRange(context.Background(), startKey, endKey, store.deleteRangeConcurrency)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete %s : %v", path, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *TikvStore) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (string, error) {
|
||||
return store.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, "", eachEntryFunc)
|
||||
}
|
||||
|
||||
func (store *TikvStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (string, error) {
|
||||
lastFileName := ""
|
||||
directoryPrefix := genDirectoryKeyPrefix(dirPath, prefix)
|
||||
lastFileStart := directoryPrefix
|
||||
if startFileName != "" {
|
||||
lastFileStart = genDirectoryKeyPrefix(dirPath, startFileName)
|
||||
}
|
||||
|
||||
txn, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return lastFileName, err
|
||||
}
|
||||
err = txn.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
iter, err := txn.Iter(lastFileStart, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer iter.Close()
|
||||
i := int64(0)
|
||||
first := true
|
||||
for iter.Valid() {
|
||||
if first {
|
||||
first = false
|
||||
if !includeStartFile {
|
||||
if iter.Valid() {
|
||||
// Check first item is lastFileStart
|
||||
if bytes.Equal(iter.Key(), lastFileStart) {
|
||||
// Is lastFileStart and not include start file, just
|
||||
// ignore it.
|
||||
err = iter.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for limitation
|
||||
if limit > 0 {
|
||||
i++
|
||||
if i > limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Validate key prefix
|
||||
key := iter.Key()
|
||||
if !bytes.HasPrefix(key, directoryPrefix) {
|
||||
break
|
||||
}
|
||||
value := iter.Value()
|
||||
|
||||
// Start process
|
||||
fileName := getNameFromKey(key)
|
||||
if fileName != "" {
|
||||
// Got file name, then generate the Entry
|
||||
entry := &filer.Entry{
|
||||
FullPath: util.NewFullPath(string(dirPath), fileName),
|
||||
}
|
||||
// Update lastFileName
|
||||
lastFileName = fileName
|
||||
// Check for decode value.
|
||||
if decodeErr := entry.DecodeAttributesAndChunks(value); decodeErr != nil {
|
||||
// Got error just return the error
|
||||
glog.V(0).Infof("list %s : %v", entry.FullPath, err)
|
||||
return err
|
||||
}
|
||||
// Run for each callback if return false just break the iteration
|
||||
if !eachEntryFunc(entry) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// End process
|
||||
|
||||
err = iter.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return lastFileName, fmt.Errorf("prefix list %s : %v", dirPath, err)
|
||||
}
|
||||
return lastFileName, nil
|
||||
}
|
||||
|
||||
// ~ Directory APIs
|
||||
|
||||
// Transaction Related APIs
|
||||
func (store *TikvStore) BeginTransaction(ctx context.Context) (context.Context, error) {
|
||||
tx, err := store.client.Begin()
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
return context.WithValue(ctx, "tx", tx), nil
|
||||
}
|
||||
|
||||
func (store *TikvStore) CommitTransaction(ctx context.Context) error {
|
||||
if tx, ok := ctx.Value("tx").(*txnkv.KVTxn); ok {
|
||||
return tx.Commit(context.Background())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *TikvStore) RollbackTransaction(ctx context.Context) error {
|
||||
if tx, ok := ctx.Value("tx").(*txnkv.KVTxn); ok {
|
||||
return tx.Rollback()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ~ Transaction Related APIs
|
||||
|
||||
// Transaction Wrapper
|
||||
type TxnWrapper struct {
|
||||
*txnkv.KVTxn
|
||||
inContext bool
|
||||
}
|
||||
|
||||
func (w *TxnWrapper) RunInTxn(f func(txn *txnkv.KVTxn) error) error {
|
||||
err := f(w.KVTxn)
|
||||
if !w.inContext {
|
||||
if err != nil {
|
||||
w.KVTxn.Rollback()
|
||||
return err
|
||||
}
|
||||
w.KVTxn.Commit(context.Background())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *TikvStore) getTxn(ctx context.Context) (*TxnWrapper, error) {
|
||||
if tx, ok := ctx.Value("tx").(*txnkv.KVTxn); ok {
|
||||
return &TxnWrapper{tx, true}, nil
|
||||
}
|
||||
txn, err := store.client.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TxnWrapper{txn, false}, nil
|
||||
}
|
||||
|
||||
// ~ Transaction Wrapper
|
||||
|
||||
// Encoding Functions
|
||||
func hashToBytes(dir string) []byte {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, dir)
|
||||
b := h.Sum(nil)
|
||||
return b
|
||||
}
|
||||
|
||||
func generateKey(dirPath, fileName string) []byte {
|
||||
key := hashToBytes(dirPath)
|
||||
key = append(key, []byte(fileName)...)
|
||||
return key
|
||||
}
|
||||
|
||||
func getNameFromKey(key []byte) string {
|
||||
return string(key[sha1.Size:])
|
||||
}
|
||||
|
||||
func genDirectoryKeyPrefix(fullpath util.FullPath, startFileName string) (keyPrefix []byte) {
|
||||
keyPrefix = hashToBytes(string(fullpath))
|
||||
if len(startFileName) > 0 {
|
||||
keyPrefix = append(keyPrefix, []byte(startFileName)...)
|
||||
}
|
||||
return keyPrefix
|
||||
}
|
||||
|
||||
func isNotExists(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if err.Error() == "not exist" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ~ Encoding Functions
|
||||
@@ -1,50 +0,0 @@
|
||||
//go:build tikv
|
||||
// +build tikv
|
||||
|
||||
package tikv
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/tikv/client-go/v2/txnkv"
|
||||
)
|
||||
|
||||
func (store *TikvStore) KvPut(ctx context.Context, key []byte, value []byte) error {
|
||||
tw, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tw.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
return txn.Set(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (store *TikvStore) KvGet(ctx context.Context, key []byte) ([]byte, error) {
|
||||
tw, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []byte = nil
|
||||
err = tw.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
val, err := txn.Get(context.TODO(), key)
|
||||
if err == nil {
|
||||
data = val
|
||||
}
|
||||
return err
|
||||
})
|
||||
if isNotExists(err) {
|
||||
return data, filer.ErrKvNotFound
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (store *TikvStore) KvDelete(ctx context.Context, key []byte) error {
|
||||
tw, err := store.getTxn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tw.RunInTxn(func(txn *txnkv.KVTxn) error {
|
||||
return txn.Delete(key)
|
||||
})
|
||||
}
|
||||
+13
-6
@@ -2,6 +2,7 @@ package filesys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/seaweedfs/fuse"
|
||||
|
||||
@@ -10,6 +11,10 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
const (
|
||||
XATTR_PREFIX = "xattr-" // same as filer
|
||||
)
|
||||
|
||||
func getxattr(entry *filer_pb.Entry, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
||||
|
||||
if entry == nil {
|
||||
@@ -18,7 +23,7 @@ func getxattr(entry *filer_pb.Entry, req *fuse.GetxattrRequest, resp *fuse.Getxa
|
||||
if entry.Extended == nil {
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
data, found := entry.Extended[req.Name]
|
||||
data, found := entry.Extended[XATTR_PREFIX+req.Name]
|
||||
if !found {
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
@@ -47,7 +52,7 @@ func setxattr(entry *filer_pb.Entry, req *fuse.SetxattrRequest) error {
|
||||
if entry.Extended == nil {
|
||||
entry.Extended = make(map[string][]byte)
|
||||
}
|
||||
data, _ := entry.Extended[req.Name]
|
||||
data, _ := entry.Extended[XATTR_PREFIX+req.Name]
|
||||
|
||||
newData := make([]byte, int(req.Position)+len(req.Xattr))
|
||||
|
||||
@@ -55,7 +60,7 @@ func setxattr(entry *filer_pb.Entry, req *fuse.SetxattrRequest) error {
|
||||
|
||||
copy(newData[int(req.Position):], req.Xattr)
|
||||
|
||||
entry.Extended[req.Name] = newData
|
||||
entry.Extended[XATTR_PREFIX+req.Name] = newData
|
||||
|
||||
return nil
|
||||
|
||||
@@ -71,13 +76,13 @@ func removexattr(entry *filer_pb.Entry, req *fuse.RemovexattrRequest) error {
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
_, found := entry.Extended[req.Name]
|
||||
_, found := entry.Extended[XATTR_PREFIX+req.Name]
|
||||
|
||||
if !found {
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
delete(entry.Extended, req.Name)
|
||||
delete(entry.Extended, XATTR_PREFIX+req.Name)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -90,7 +95,9 @@ func listxattr(entry *filer_pb.Entry, req *fuse.ListxattrRequest, resp *fuse.Lis
|
||||
}
|
||||
|
||||
for k := range entry.Extended {
|
||||
resp.Append(k)
|
||||
if strings.HasPrefix(k, XATTR_PREFIX) {
|
||||
resp.Append(k[len(XATTR_PREFIX):])
|
||||
}
|
||||
}
|
||||
|
||||
size := req.Size
|
||||
|
||||
@@ -3,8 +3,8 @@ package ftpd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"net"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -33,10 +33,10 @@ type IamS3ApiConfigure struct {
|
||||
}
|
||||
|
||||
type IamServerOption struct {
|
||||
Masters []pb.ServerAddress
|
||||
Filer pb.ServerAddress
|
||||
Port int
|
||||
GrpcDialOption grpc.DialOption
|
||||
Masters []pb.ServerAddress
|
||||
Filer pb.ServerAddress
|
||||
Port int
|
||||
GrpcDialOption grpc.DialOption
|
||||
}
|
||||
|
||||
type IamApiServer struct {
|
||||
@@ -49,7 +49,7 @@ var s3ApiConfigure IamS3ApiConfig
|
||||
func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) {
|
||||
s3ApiConfigure = IamS3ApiConfigure{
|
||||
option: option,
|
||||
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", "",option.Masters),
|
||||
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", "", option.Masters),
|
||||
}
|
||||
s3Option := s3api.S3ApiServerOption{Filer: option.Filer}
|
||||
iamApiServer = &IamApiServer{
|
||||
|
||||
@@ -2,6 +2,7 @@ package broker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/log_buffer"
|
||||
"io"
|
||||
"strings"
|
||||
@@ -141,7 +142,7 @@ func (broker *MessageBroker) Subscribe(stream messaging_pb.SeaweedMessaging_Subs
|
||||
func (broker *MessageBroker) readPersistedLogBuffer(tp *TopicPartition, startTime time.Time, eachLogEntryFn func(logEntry *filer_pb.LogEntry) error) (err error) {
|
||||
startTime = startTime.UTC()
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", startTime.Year(), startTime.Month(), startTime.Day())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d.segment", startTime.Hour(), startTime.Minute())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d", startTime.Hour(), startTime.Minute())
|
||||
|
||||
sizeBuf := make([]byte, 4)
|
||||
startTsNs := startTime.UnixNano()
|
||||
@@ -153,7 +154,8 @@ func (broker *MessageBroker) readPersistedLogBuffer(tp *TopicPartition, startTim
|
||||
dayDir := fmt.Sprintf("%s/%s", topicDir, dayEntry.Name)
|
||||
return filer_pb.List(broker, dayDir, "", func(hourMinuteEntry *filer_pb.Entry, isLast bool) error {
|
||||
if dayEntry.Name == startDate {
|
||||
if strings.Compare(hourMinuteEntry.Name, startHourMinute) < 0 {
|
||||
hourMinute := util.FileNameBase(hourMinuteEntry.Name)
|
||||
if strings.Compare(hourMinute, startHourMinute) < 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ func Assign(masterFn GetMasterFn, grpcDialOption grpc.DialOption, primaryRequest
|
||||
return grpcErr
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return fmt.Errorf("assignRequest: %v", resp.Error)
|
||||
}
|
||||
|
||||
ret.Count = resp.Count
|
||||
ret.Fid = resp.Fid
|
||||
ret.Url = resp.Location.Url
|
||||
@@ -80,10 +84,6 @@ func Assign(masterFn GetMasterFn, grpcDialOption grpc.DialOption, primaryRequest
|
||||
})
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return fmt.Errorf("assignRequest: %v", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ func LookupVolumeIds(masterFn GetMasterFn, grpcDialOption grpc.DialOption, vids
|
||||
locations = append(locations, Location{
|
||||
Url: loc.Url,
|
||||
PublicUrl: loc.PublicUrl,
|
||||
GrpcPort: int(loc.GrpcPort),
|
||||
GrpcPort: int(loc.GrpcPort),
|
||||
})
|
||||
}
|
||||
if vidLocations.Error != "" {
|
||||
|
||||
@@ -101,9 +101,13 @@ func SeaweedList(client SeaweedFilerClient, parentDirectoryPath, prefix string,
|
||||
func doSeaweedList(client SeaweedFilerClient, fullDirPath util.FullPath, prefix string, fn EachEntryFunciton, startFrom string, inclusive bool, limit uint32) (err error) {
|
||||
// Redundancy limit to make it correctly judge whether it is the last file.
|
||||
redLimit := limit
|
||||
if limit != math.MaxInt32 && limit != 0 {
|
||||
|
||||
if limit < math.MaxInt32 && limit != 0 {
|
||||
redLimit = limit + 1
|
||||
}
|
||||
if redLimit > math.MaxInt32 {
|
||||
redLimit = math.MaxInt32
|
||||
}
|
||||
request := &ListEntriesRequest{
|
||||
Directory: string(fullDirPath),
|
||||
Prefix: prefix,
|
||||
|
||||
@@ -244,4 +244,4 @@ func WithOneOfGrpcFilerClients(filerAddresses []ServerAddress, grpcDialOption gr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type ServerAddress string
|
||||
type ServerAddresses string
|
||||
|
||||
func NewServerAddress(host string, port int, grpcPort int) ServerAddress {
|
||||
if grpcPort == port+10000 {
|
||||
if grpcPort == 0 || grpcPort == port+10000 {
|
||||
return ServerAddress(util.JoinHostPort(host, port))
|
||||
}
|
||||
return ServerAddress(util.JoinHostPort(host, port) + "." + strconv.Itoa(grpcPort))
|
||||
|
||||
@@ -58,6 +58,8 @@ service VolumeServer {
|
||||
}
|
||||
rpc WriteNeedleBlob (WriteNeedleBlobRequest) returns (WriteNeedleBlobResponse) {
|
||||
}
|
||||
rpc ReadAllNeedles (ReadAllNeedlesRequest) returns (stream ReadAllNeedlesResponse) {
|
||||
}
|
||||
|
||||
rpc VolumeTailSender (VolumeTailSenderRequest) returns (stream VolumeTailSenderResponse) {
|
||||
}
|
||||
@@ -284,6 +286,16 @@ message WriteNeedleBlobRequest {
|
||||
message WriteNeedleBlobResponse {
|
||||
}
|
||||
|
||||
message ReadAllNeedlesRequest {
|
||||
repeated uint32 volume_ids = 1;
|
||||
}
|
||||
message ReadAllNeedlesResponse {
|
||||
uint32 volume_id = 1;
|
||||
uint64 needle_id = 2;
|
||||
uint32 cookie = 3;
|
||||
bytes needle_blob = 5;
|
||||
}
|
||||
|
||||
message VolumeTailSenderRequest {
|
||||
uint32 volume_id = 1;
|
||||
uint64 since_ns = 2;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -269,7 +269,7 @@ func (s *s3RemoteStorageClient) CreateBucket(name string) (err error) {
|
||||
|
||||
func (s *s3RemoteStorageClient) DeleteBucket(name string) (err error) {
|
||||
_, err = s.conn.DeleteBucket(&s3.DeleteBucketInput{
|
||||
Bucket: aws.String(name),
|
||||
Bucket: aws.String(name),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete bucket %s: %v", name, err)
|
||||
|
||||
@@ -27,6 +27,8 @@ type ListAllMyBucketsResult struct {
|
||||
|
||||
func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
glog.V(3).Infof("ListBucketsHandler")
|
||||
|
||||
var identity *Identity
|
||||
var s3Err s3err.ErrorCode
|
||||
if s3a.iam.isEnabled() {
|
||||
@@ -75,6 +77,7 @@ func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Reques
|
||||
func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("PutBucketHandler %s", bucket)
|
||||
|
||||
// avoid duplicated buckets
|
||||
errCode := s3err.ErrNone
|
||||
@@ -128,6 +131,7 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request)
|
||||
func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("DeleteBucketHandler %s", bucket)
|
||||
|
||||
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
||||
s3err.WriteErrorResponse(w, err, r)
|
||||
@@ -162,6 +166,7 @@ func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Reque
|
||||
func (s3a *S3ApiServer) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("HeadBucketHandler %s", bucket)
|
||||
|
||||
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
||||
s3err.WriteErrorResponse(w, err, r)
|
||||
|
||||
@@ -27,6 +27,8 @@ func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request
|
||||
|
||||
srcBucket, srcObject := pathToBucketAndObject(cpSrcPath)
|
||||
|
||||
glog.V(3).Infof("CopyObjectHandler %s %s => %s %s", srcBucket, srcObject, dstBucket, dstObject)
|
||||
|
||||
if (srcBucket == dstBucket && srcObject == dstObject || cpSrcPath == "") && isReplace(r) {
|
||||
fullPath := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, dstBucket, dstObject))
|
||||
dir, name := fullPath.DirAndName()
|
||||
@@ -139,6 +141,8 @@ func (s3a *S3ApiServer) CopyObjectPartHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(3).Infof("CopyObjectPartHandler %s %s => %s part %d", srcBucket, srcObject, dstBucket, partID)
|
||||
|
||||
// check partID with maximum part ID for multipart objects
|
||||
if partID > globalMaxPartID {
|
||||
s3err.WriteErrorResponse(w, s3err.ErrInvalidMaxParts, r)
|
||||
|
||||
@@ -41,6 +41,7 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request)
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("PutObjectHandler %s %s", bucket, object)
|
||||
|
||||
_, err := validateContentMd5(r.Header)
|
||||
if err != nil {
|
||||
@@ -118,6 +119,7 @@ func urlPathEscape(object string) string {
|
||||
func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("GetObjectHandler %s %s", bucket, object)
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
s3err.WriteErrorResponse(w, s3err.ErrNotImplemented, r)
|
||||
@@ -134,6 +136,7 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
|
||||
func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("HeadObjectHandler %s %s", bucket, object)
|
||||
|
||||
destUrl := fmt.Sprintf("http://%s%s/%s%s",
|
||||
s3a.option.Filer.ToHttpAddress(), s3a.option.BucketsPath, bucket, urlPathEscape(object))
|
||||
@@ -145,6 +148,7 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
|
||||
func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("DeleteObjectHandler %s %s", bucket, object)
|
||||
|
||||
destUrl := fmt.Sprintf("http://%s%s/%s%s?recursive=true",
|
||||
s3a.option.Filer.ToHttpAddress(), s3a.option.BucketsPath, bucket, urlPathEscape(object))
|
||||
@@ -192,6 +196,7 @@ type DeleteObjectsResponse struct {
|
||||
func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("DeleteMultipleObjectsHandler %s", bucket)
|
||||
|
||||
deleteXMLBytes, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
@@ -291,7 +296,7 @@ var passThroughHeaders = []string{
|
||||
|
||||
func (s3a *S3ApiServer) proxyToFiler(w http.ResponseWriter, r *http.Request, destUrl string, responseFn func(proxyResponse *http.Response, w http.ResponseWriter)) {
|
||||
|
||||
glog.V(2).Infof("s3 proxying %s to %s", r.Method, destUrl)
|
||||
glog.V(3).Infof("s3 proxying %s to %s", r.Method, destUrl)
|
||||
|
||||
proxyReq, err := http.NewRequest(r.Method, destUrl, r.Body)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/policy"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -24,6 +25,8 @@ func (s3a *S3ApiServer) PostPolicyBucketHandler(w http.ResponseWriter, r *http.R
|
||||
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
|
||||
glog.V(3).Infof("PostPolicyBucketHandler %s", bucket)
|
||||
|
||||
reader, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
s3err.WriteErrorResponse(w, s3err.ErrMalformedPOSTRequest, r)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// PutObjectAclHandler Put object ACL
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html
|
||||
func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
}
|
||||
|
||||
// PutObjectRetentionHandler Put object Retention
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html
|
||||
func (s3a *S3ApiServer) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
}
|
||||
|
||||
// PutObjectLegalHoldHandler Put object Legal Hold
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html
|
||||
func (s3a *S3ApiServer) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
}
|
||||
|
||||
// PutObjectLockConfigurationHandler Put object Lock configuration
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html
|
||||
func (s3a *S3ApiServer) PutObjectLockConfigurationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
func (s3a *S3ApiServer) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("GetObjectTaggingHandler %s %s", bucket, object)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
@@ -42,6 +43,7 @@ func (s3a *S3ApiServer) GetObjectTaggingHandler(w http.ResponseWriter, r *http.R
|
||||
func (s3a *S3ApiServer) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("PutObjectTaggingHandler %s %s", bucket, object)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
@@ -97,6 +99,7 @@ func (s3a *S3ApiServer) PutObjectTaggingHandler(w http.ResponseWriter, r *http.R
|
||||
func (s3a *S3ApiServer) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
glog.V(3).Infof("DeleteObjectTaggingHandler %s %s", bucket, object)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
|
||||
@@ -40,6 +40,7 @@ func (s3a *S3ApiServer) ListObjectsV2Handler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// collect parameters
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("ListObjectsV2Handler %s", bucket)
|
||||
|
||||
originalPrefix, continuationToken, startAfter, delimiter, _, maxKeys := getListObjectsV2Args(r.URL.Query())
|
||||
|
||||
@@ -95,6 +96,7 @@ func (s3a *S3ApiServer) ListObjectsV1Handler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// collect parameters
|
||||
bucket, _ := getBucketAndObject(r)
|
||||
glog.V(3).Infof("ListObjectsV1Handler %s", bucket)
|
||||
|
||||
originalPrefix, marker, delimiter, maxKeys := getListObjectsV1Args(r.URL.Query())
|
||||
|
||||
|
||||
@@ -90,6 +90,15 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||
// DeleteObjectTagging
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.DeleteObjectTaggingHandler, ACTION_TAGGING), "DELETE")).Queries("tagging", "")
|
||||
|
||||
// PutObjectACL
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.PutObjectAclHandler, ACTION_WRITE), "PUT")).Queries("acl", "")
|
||||
// PutObjectRetention
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.PutObjectRetentionHandler, ACTION_WRITE), "PUT")).Queries("retention", "")
|
||||
// PutObjectLegalHold
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.PutObjectLegalHoldHandler, ACTION_WRITE), "PUT")).Queries("legal-hold", "")
|
||||
// PutObjectLockConfiguration
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.PutObjectLockConfigurationHandler, ACTION_WRITE), "PUT")).Queries("object-lock", "")
|
||||
|
||||
// CopyObject
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(track(s3a.iam.Auth(s3a.CopyObjectHandler, ACTION_WRITE), "COPY"))
|
||||
// PutObject
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
package sequence
|
||||
|
||||
/*
|
||||
Note :
|
||||
(1) store the sequence in the ETCD cluster, and local file(sequence.dat)
|
||||
(2) batch get the sequences from ETCD cluster, and store the max sequence id in the local file
|
||||
(3) the sequence range is : [currentSeqId, maxSeqId), when the currentSeqId >= maxSeqId, fetch the new maxSeqId.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go.etcd.io/etcd/client"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// EtcdKeyPrefix = "/seaweedfs"
|
||||
EtcdKeySequence = "/master/sequence"
|
||||
EtcdContextTimeoutSecond = 100 * time.Second
|
||||
DefaultEtcdSteps uint64 = 500 // internal counter
|
||||
SequencerFileName = "sequencer.dat"
|
||||
FileMaxSequenceLength = 128
|
||||
)
|
||||
|
||||
type EtcdSequencer struct {
|
||||
sequenceLock sync.Mutex
|
||||
|
||||
// available sequence range : [currentSeqId, maxSeqId)
|
||||
currentSeqId uint64
|
||||
maxSeqId uint64
|
||||
|
||||
keysAPI client.KeysAPI
|
||||
seqFile *os.File
|
||||
}
|
||||
|
||||
func NewEtcdSequencer(etcdUrls string, metaFolder string) (*EtcdSequencer, error) {
|
||||
file, err := openSequenceFile(metaFolder + "/" + SequencerFileName)
|
||||
if nil != err {
|
||||
return nil, fmt.Errorf("open sequence file fialed, %v", err)
|
||||
}
|
||||
|
||||
cli, err := client.New(client.Config{
|
||||
Endpoints: strings.Split(etcdUrls, ","),
|
||||
Username: "",
|
||||
Password: "",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keysApi := client.NewKeysAPI(cli)
|
||||
|
||||
// TODO: the current sequence id in local file is not used
|
||||
maxValue, _, err := readSequenceFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read sequence from file failed, %v", err)
|
||||
}
|
||||
glog.V(4).Infof("read sequence from file : %d", maxValue)
|
||||
|
||||
newSeq, err := setMaxSequenceToEtcd(keysApi, maxValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sequencer := &EtcdSequencer{maxSeqId: newSeq,
|
||||
currentSeqId: newSeq,
|
||||
keysAPI: keysApi,
|
||||
seqFile: file,
|
||||
}
|
||||
return sequencer, nil
|
||||
}
|
||||
|
||||
func (es *EtcdSequencer) NextFileId(count uint64) uint64 {
|
||||
es.sequenceLock.Lock()
|
||||
defer es.sequenceLock.Unlock()
|
||||
|
||||
if (es.currentSeqId + count) >= es.maxSeqId {
|
||||
reqSteps := DefaultEtcdSteps
|
||||
if count > DefaultEtcdSteps {
|
||||
reqSteps += count
|
||||
}
|
||||
maxId, err := batchGetSequenceFromEtcd(es.keysAPI, reqSteps)
|
||||
glog.V(4).Infof("get max sequence id from etcd, %d", maxId)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return 0
|
||||
}
|
||||
es.currentSeqId, es.maxSeqId = maxId-reqSteps, maxId
|
||||
glog.V(4).Infof("current id : %d, max id : %d", es.currentSeqId, es.maxSeqId)
|
||||
|
||||
if err := writeSequenceFile(es.seqFile, es.maxSeqId, es.currentSeqId); err != nil {
|
||||
glog.Errorf("flush sequence to file failed, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ret := es.currentSeqId
|
||||
es.currentSeqId += count
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
instead of collecting the max value from volume server,
|
||||
the max value should be saved in local config file and ETCD cluster
|
||||
*/
|
||||
func (es *EtcdSequencer) SetMax(seenValue uint64) {
|
||||
es.sequenceLock.Lock()
|
||||
defer es.sequenceLock.Unlock()
|
||||
if seenValue > es.maxSeqId {
|
||||
maxId, err := setMaxSequenceToEtcd(es.keysAPI, seenValue)
|
||||
if err != nil {
|
||||
glog.Errorf("set Etcd Max sequence failed : %v", err)
|
||||
return
|
||||
}
|
||||
es.currentSeqId, es.maxSeqId = maxId, maxId
|
||||
|
||||
if err := writeSequenceFile(es.seqFile, maxId, maxId); err != nil {
|
||||
glog.Errorf("flush sequence to file failed, %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (es *EtcdSequencer) GetMax() uint64 {
|
||||
return es.maxSeqId
|
||||
}
|
||||
|
||||
func (es *EtcdSequencer) Peek() uint64 {
|
||||
return es.currentSeqId
|
||||
}
|
||||
|
||||
func batchGetSequenceFromEtcd(kvApi client.KeysAPI, step uint64) (uint64, error) {
|
||||
if step <= 0 {
|
||||
return 0, fmt.Errorf("the step must be large than 1")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), EtcdContextTimeoutSecond)
|
||||
var endSeqValue uint64 = 0
|
||||
defer cancel()
|
||||
for {
|
||||
getResp, err := kvApi.Get(ctx, EtcdKeySequence, &client.GetOptions{Recursive: false, Quorum: true})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if getResp.Node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
prevValue := getResp.Node.Value
|
||||
prevSeqValue, err := strconv.ParseUint(prevValue, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get sequence from etcd failed, %v", err)
|
||||
}
|
||||
endSeqValue = prevSeqValue + step
|
||||
endSeqStr := strconv.FormatUint(endSeqValue, 10)
|
||||
|
||||
_, err = kvApi.Set(ctx, EtcdKeySequence, endSeqStr, &client.SetOptions{PrevValue: prevValue})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
return endSeqValue, nil
|
||||
}
|
||||
|
||||
/**
|
||||
update the value of the key EtcdKeySequence in ETCD cluster with the parameter of maxSeq,
|
||||
when the value of the key EtcdKeySequence is equal to or large than the parameter maxSeq,
|
||||
return the value of EtcdKeySequence in the ETCD cluster;
|
||||
when the value of the EtcdKeySequence is less than the parameter maxSeq,
|
||||
return the value of the parameter maxSeq
|
||||
*/
|
||||
func setMaxSequenceToEtcd(kvApi client.KeysAPI, maxSeq uint64) (uint64, error) {
|
||||
maxSeqStr := strconv.FormatUint(maxSeq, 10)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), EtcdContextTimeoutSecond)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
getResp, err := kvApi.Get(ctx, EtcdKeySequence, &client.GetOptions{Recursive: false, Quorum: true})
|
||||
if err != nil {
|
||||
if ce, ok := err.(client.Error); ok && (ce.Code == client.ErrorCodeKeyNotFound) {
|
||||
_, err := kvApi.Create(ctx, EtcdKeySequence, maxSeqStr)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if ce, ok = err.(client.Error); ok && (ce.Code == client.ErrorCodeNodeExist) {
|
||||
continue
|
||||
}
|
||||
return 0, err
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if getResp.Node == nil {
|
||||
continue
|
||||
}
|
||||
prevSeqStr := getResp.Node.Value
|
||||
prevSeq, err := strconv.ParseUint(prevSeqStr, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if prevSeq >= maxSeq {
|
||||
return prevSeq, nil
|
||||
}
|
||||
|
||||
_, err = kvApi.Set(ctx, EtcdKeySequence, maxSeqStr, &client.SetOptions{PrevValue: prevSeqStr})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openSequenceFile(file string) (*os.File, error) {
|
||||
_, err := os.Stat(file)
|
||||
if os.IsNotExist(err) {
|
||||
fid, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeSequenceFile(fid, 1, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fid, nil
|
||||
} else {
|
||||
return os.OpenFile(file, os.O_RDWR|os.O_CREATE, 0644)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
read sequence and step from sequence file
|
||||
*/
|
||||
func readSequenceFile(file *os.File) (uint64, uint64, error) {
|
||||
sequence := make([]byte, FileMaxSequenceLength)
|
||||
size, err := file.ReadAt(sequence, 0)
|
||||
if (err != nil) && (err != io.EOF) {
|
||||
err := fmt.Errorf("cannot read file %s, %v", file.Name(), err)
|
||||
return 0, 0, err
|
||||
}
|
||||
sequence = sequence[0:size]
|
||||
seqs := strings.Split(string(sequence), ":")
|
||||
maxId, err := strconv.ParseUint(seqs[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("parse sequence from file failed, %v", err)
|
||||
}
|
||||
|
||||
if len(seqs) > 1 {
|
||||
step, err := strconv.ParseUint(seqs[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("parse sequence from file failed, %v", err)
|
||||
}
|
||||
return maxId, step, nil
|
||||
}
|
||||
|
||||
return maxId, 0, nil
|
||||
}
|
||||
|
||||
/**
|
||||
write the sequence and step to sequence file
|
||||
*/
|
||||
func writeSequenceFile(file *os.File, sequence, step uint64) error {
|
||||
_ = step
|
||||
seqStr := fmt.Sprintf("%d:%d", sequence, sequence)
|
||||
if _, err := file.Seek(0, 0); err != nil {
|
||||
err = fmt.Errorf("cannot seek to the beginning of %s: %v", file.Name(), err)
|
||||
return err
|
||||
}
|
||||
if err := file.Truncate(0); err != nil {
|
||||
return fmt.Errorf("truncate sequence file faield : %v", err)
|
||||
}
|
||||
if _, err := file.WriteString(seqStr); err != nil {
|
||||
return fmt.Errorf("write file %s failed, %v", file.Name(), err)
|
||||
}
|
||||
if err := file.Sync(); err != nil {
|
||||
return fmt.Errorf("flush file %s failed, %v", file.Name(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// the UT helper method
|
||||
// func deleteEtcdKey(kvApi client.KeysAPI, key string) error {
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), EtcdContextTimeoutSecond)
|
||||
// defer cancel()
|
||||
// _, err := kvApi.Delete(ctx, key, &client.DeleteOptions{Dir: false})
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
@@ -116,7 +116,7 @@ func (fs *FilerServer) DownloadToLocal(ctx context.Context, req *filer_pb.Downlo
|
||||
replicas = append(replicas, &volume_server_pb.FetchAndWriteNeedleRequest_Replica{
|
||||
Url: r.Url,
|
||||
PublicUrl: r.PublicUrl,
|
||||
GrpcPort: int32(r.GrpcPort),
|
||||
GrpcPort: int32(r.GrpcPort),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,6 @@ func (fs *FilerServer) SubscribeMetadata(req *filer_pb.SubscribeMetadataRequest,
|
||||
|
||||
if processedTsNs != 0 {
|
||||
lastReadTime = time.Unix(0, processedTsNs)
|
||||
} else {
|
||||
if readInMemoryLogErr == log_buffer.ResumeFromDiskError {
|
||||
time.Sleep(1127 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(4).Infof("read in memory %v aggregated subscribe %s from %+v", clientName, req.PathPrefix, lastReadTime)
|
||||
@@ -66,6 +61,7 @@ func (fs *FilerServer) SubscribeMetadata(req *filer_pb.SubscribeMetadataRequest,
|
||||
}, eachLogEntryFn)
|
||||
if readInMemoryLogErr != nil {
|
||||
if readInMemoryLogErr == log_buffer.ResumeFromDiskError {
|
||||
time.Sleep(1127 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
glog.Errorf("processed to %v: %v", lastReadTime, readInMemoryLogErr)
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/postgres2"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis2"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/redis3"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/filer/sqlite"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/notification"
|
||||
|
||||
@@ -64,7 +64,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// set etag
|
||||
etag := filer.ETagEntry(entry)
|
||||
if ifm := r.Header.Get("If-Match"); ifm != "" && (ifm != "\""+etag+"\"" && ifm != etag){
|
||||
if ifm := r.Header.Get("If-Match"); ifm != "" && (ifm != "\""+etag+"\"" && ifm != etag) {
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
@@ -97,7 +97,10 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// print out the header from extended properties
|
||||
for k, v := range entry.Extended {
|
||||
w.Header().Set(k, string(v))
|
||||
if !strings.HasPrefix(k, "xattr-") {
|
||||
// "xattr-" prefix is set in filesys.XATTR_PREFIX
|
||||
w.Header().Set(k, string(v))
|
||||
}
|
||||
}
|
||||
|
||||
//Seaweed custom header are not visible to Vue or javascript
|
||||
@@ -174,6 +177,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
Directory: dir,
|
||||
Name: name,
|
||||
}); err != nil {
|
||||
glog.Errorf("DownloadToLocal %s: %v", entry.FullPath, err)
|
||||
return fmt.Errorf("cache %s: %v", entry.FullPath, err)
|
||||
} else {
|
||||
chunks = resp.Entry.Chunks
|
||||
|
||||
@@ -24,7 +24,7 @@ func (ms *MasterServer) SendHeartbeat(stream master_pb.Seaweed_SendHeartbeatServ
|
||||
defer func() {
|
||||
if dn != nil {
|
||||
dn.Counter--
|
||||
if dn.Counter > 0 {
|
||||
if dn.Counter > 0 {
|
||||
glog.V(0).Infof("disconnect phantom volume server %s:%d remaining %d", dn.Ip, dn.Port, dn.Counter)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,8 +42,11 @@ func (ms *MasterServer) ProcessGrowRequest() {
|
||||
return !found
|
||||
})
|
||||
|
||||
option := req.Option
|
||||
vl := ms.Topo.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType)
|
||||
|
||||
// not atomic but it's okay
|
||||
if !found && ms.shouldVolumeGrow(req.Option) {
|
||||
if !found && vl.ShouldGrowVolumes(option) {
|
||||
filter.Store(req, nil)
|
||||
// we have lock called inside vg
|
||||
go func() {
|
||||
@@ -51,6 +54,7 @@ func (ms *MasterServer) ProcessGrowRequest() {
|
||||
start := time.Now()
|
||||
_, err := ms.vg.AutomaticGrowByType(req.Option, ms.grpcDialOption, ms.Topo, req.Count)
|
||||
glog.V(1).Infoln("finished automatic volume grow, cost ", time.Now().Sub(start))
|
||||
vl.DoneGrowRequest()
|
||||
|
||||
if req.ErrCh != nil {
|
||||
req.ErrCh <- err
|
||||
@@ -130,10 +134,13 @@ func (ms *MasterServer) Assign(ctx context.Context, req *master_pb.AssignRequest
|
||||
MemoryMapMaxSizeMb: req.MemoryMapMaxSizeMb,
|
||||
}
|
||||
|
||||
if ms.shouldVolumeGrow(option) {
|
||||
vl := ms.Topo.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType)
|
||||
|
||||
if !vl.HasGrowRequest() && vl.ShouldGrowVolumes(option) {
|
||||
if ms.Topo.AvailableSpaceFor(option) <= 0 {
|
||||
return nil, fmt.Errorf("no free volumes left for " + option.String())
|
||||
}
|
||||
vl.AddGrowRequest()
|
||||
ms.vgCh <- &topology.VolumeGrowRequest{
|
||||
Option: option,
|
||||
Count: int(req.WritableVolumeCount),
|
||||
@@ -155,19 +162,19 @@ func (ms *MasterServer) Assign(ctx context.Context, req *master_pb.AssignRequest
|
||||
replicas = append(replicas, &master_pb.Location{
|
||||
Url: r.Url(),
|
||||
PublicUrl: r.PublicUrl,
|
||||
GrpcPort: uint32(r.GrpcPort),
|
||||
GrpcPort: uint32(r.GrpcPort),
|
||||
})
|
||||
}
|
||||
return &master_pb.AssignResponse{
|
||||
Fid: fid,
|
||||
Fid: fid,
|
||||
Location: &master_pb.Location{
|
||||
Url: dn.Url(),
|
||||
PublicUrl: dn.PublicUrl,
|
||||
GrpcPort: uint32(dn.GrpcPort),
|
||||
},
|
||||
Count: count,
|
||||
Auth: string(security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fid)),
|
||||
Replicas: replicas,
|
||||
Count: count,
|
||||
Auth: string(security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fid)),
|
||||
Replicas: replicas,
|
||||
}, nil
|
||||
}
|
||||
//glog.V(4).Infoln("waiting for volume growing...")
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
|
||||
const (
|
||||
SequencerType = "master.sequencer.type"
|
||||
SequencerEtcdUrls = "master.sequencer.sequencer_etcd_urls"
|
||||
SequencerSnowflakeId = "master.sequencer.sequencer_snowflake_id"
|
||||
)
|
||||
|
||||
@@ -286,15 +285,6 @@ func (ms *MasterServer) createSequencer(option *MasterOption) sequence.Sequencer
|
||||
seqType := strings.ToLower(v.GetString(SequencerType))
|
||||
glog.V(1).Infof("[%s] : [%s]", SequencerType, seqType)
|
||||
switch strings.ToLower(seqType) {
|
||||
case "etcd":
|
||||
var err error
|
||||
urls := v.GetString(SequencerEtcdUrls)
|
||||
glog.V(0).Infof("[%s] : [%s]", SequencerEtcdUrls, urls)
|
||||
seq, err = sequence.NewEtcdSequencer(urls, option.MetaFolder)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
seq = nil
|
||||
}
|
||||
case "snowflake":
|
||||
var err error
|
||||
snowflakeId := v.GetInt(SequencerSnowflakeId)
|
||||
|
||||
@@ -113,13 +113,16 @@ func (ms *MasterServer) dirAssignHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if ms.shouldVolumeGrow(option) {
|
||||
vl := ms.Topo.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType)
|
||||
|
||||
if !vl.HasGrowRequest() && vl.ShouldGrowVolumes(option) {
|
||||
glog.V(0).Infof("dirAssign volume growth %v from %v", option.String(), r.RemoteAddr)
|
||||
if ms.Topo.AvailableSpaceFor(option) <= 0 {
|
||||
writeJsonQuiet(w, r, http.StatusNotFound, operation.AssignResult{Error: "No free volumes left for " + option.String()})
|
||||
return
|
||||
}
|
||||
errCh := make(chan error, 1)
|
||||
vl.AddGrowRequest()
|
||||
ms.vgCh <- &topology.VolumeGrowRequest{
|
||||
Option: option,
|
||||
Count: writableVolumeCount,
|
||||
|
||||
@@ -132,13 +132,6 @@ func (ms *MasterServer) submitFromMasterServerHandler(w http.ResponseWriter, r *
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MasterServer) shouldVolumeGrow(option *topology.VolumeGrowOption) bool {
|
||||
vl := ms.Topo.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType)
|
||||
active, high := vl.GetActiveVolumeCount(option)
|
||||
//glog.V(0).Infof("active volume: %d, high usage volume: %d\n", active, high)
|
||||
return active <= high
|
||||
}
|
||||
|
||||
func (ms *MasterServer) getVolumeGrowOption(r *http.Request) (*topology.VolumeGrowOption, error) {
|
||||
replicationString := r.FormValue("replication")
|
||||
if replicationString == "" {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
)
|
||||
|
||||
func (vs *VolumeServer) ReadAllNeedles(req *volume_server_pb.ReadAllNeedlesRequest, stream volume_server_pb.VolumeServer_ReadAllNeedlesServer) (err error) {
|
||||
|
||||
for _, vid := range req.VolumeIds {
|
||||
if err := vs.streaReadOneVolume(needle.VolumeId(vid), stream, err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vs *VolumeServer) streaReadOneVolume(vid needle.VolumeId, stream volume_server_pb.VolumeServer_ReadAllNeedlesServer, err error) error {
|
||||
v := vs.store.GetVolume(vid)
|
||||
if v == nil {
|
||||
return fmt.Errorf("not found volume id %d", vid)
|
||||
}
|
||||
|
||||
scanner := &storage.VolumeFileScanner4ReadAll{
|
||||
Stream: stream,
|
||||
V: v,
|
||||
}
|
||||
|
||||
offset := int64(v.SuperBlock.BlockSize())
|
||||
|
||||
err = storage.ScanVolumeFileFrom(v.Version(), v.DataBackend, offset, scanner)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (vs *VolumeServer) FetchAndWriteNeedle(ctx context.Context, req *volume_ser
|
||||
}
|
||||
}
|
||||
}()
|
||||
if len(req.Replicas)>0{
|
||||
if len(req.Replicas) > 0 {
|
||||
fileId := needle.NewFileId(v.Id, req.NeedleId, req.Cookie)
|
||||
for _, replica := range req.Replicas {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -27,18 +27,18 @@ import (
|
||||
)
|
||||
|
||||
type WebDavOption struct {
|
||||
Filer pb.ServerAddress
|
||||
DomainName string
|
||||
BucketsPath string
|
||||
GrpcDialOption grpc.DialOption
|
||||
Collection string
|
||||
Replication string
|
||||
DiskType string
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Cipher bool
|
||||
CacheDir string
|
||||
CacheSizeMB int64
|
||||
Filer pb.ServerAddress
|
||||
DomainName string
|
||||
BucketsPath string
|
||||
GrpcDialOption grpc.DialOption
|
||||
Collection string
|
||||
Replication string
|
||||
DiskType string
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Cipher bool
|
||||
CacheDir string
|
||||
CacheSizeMB int64
|
||||
}
|
||||
|
||||
type WebDavServer struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user