feat: added modules

This commit is contained in:
Marvin Zhang
2024-06-14 16:37:48 +08:00
parent dc21bce11f
commit 6a60433d25
157 changed files with 5887 additions and 305 deletions

9
fs/.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*.go]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

54
fs/.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Test and coverage
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Go environment
uses: actions/setup-go@v2.1.3
with:
# The Go version to download (if necessary) and use. Supports semver spec and ranges.
go-version: 1.15
# - name: Download Binary Files
# run: |
# mkdir -p $GITHUB_WORKSPACE/seaweedfs
# curl https://github.com/chrislusf/seaweedfs/releases/download/2.48/linux_amd64.tar.gz -o $GITHUB_WORKSPACE/seaweedfs/linux_amd64.tar.gz
# cd $GITHUB_WORKSPACE/seaweedfs
# ls -l
# tar -zxf linux_amd64.tar.gz
- name: Download Binary Files
uses: fabriciobastian/download-release-asset-action@v1.0.6
with:
# A specific release version. Defaults to latest
version: 2.48 # default is latest
# Relative path to the repository in the format user/repo e.g.: myuser/my-repository
repository: chrislusf/seaweedfs # default is
# The name of the asset to download from the release
file: linux_amd64.tar.gz
# Path to the directory where to download the asset
out: seaweedfs # optional, default is .
- name: Extract Binary Files
run: |
cd $GITHUB_WORKSPACE/seaweedfs
tar -zxf linux_amd64.tar.gz
- name: Validate Binary Files
run: |
cd $GITHUB_WORKSPACE/seaweedfs
ls -l weed
- name: Run Tests
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic -coverpkg github.com/crawlab-team/crawlab/fs
- name: Codecov
uses: codecov/codecov-action@v1.5.0
with:
# Repository upload token - get it from codecov.io. Required only for private repositories
token: ${{ secrets.CODECOV_TOKEN }}
# Comma-separated list of files to upload

23
fs/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
tmp/
.idea/
.DS_Store
coverage.txt
filerldb2
seaweedfs
*.txt

8
fs/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM golang:1.15
WORKDIR /app
ADD ./go.mod /app
ADD ./go.sum /app
RUN go mod download
CMD ["sh", "./bin/test.sh"]

29
fs/LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Crawlab Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

5
fs/README.md Normal file
View File

@@ -0,0 +1,5 @@
# crawlab-fs
[![codecov](https://codecov.io/gh/crawlab-team/crawlab-fs/branch/main/graph/badge.svg?token=KOXJPXWAKI)](https://codecov.io/gh/crawlab-team/crawlab-fs)
Backend file system module for Crawlab

24
fs/bin/start.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
if [ -e ./tmp ]; then
:
else
mkdir ./tmp
fi
if [ -x /usr/local/bin/weed ]; then
weed server \
-dir ./tmp \
-master.dir ./tmp \
-volume.dir.idx ./tmp \
-ip localhost \
-ip.bind 0.0.0.0 \
-filer
else
./seaweedfs/weed server \
-dir ./tmp \
-master.dir ./tmp \
-volume.dir.idx ./tmp \
-ip localhost \
-ip.bind 0.0.0.0 \
-filer
fi

3
fs/bin/stop.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
kill -9 `ps axu|grep weed|grep -v grep|awk '{print \$2}'|xargs`

19
fs/constants.go Normal file
View File

@@ -0,0 +1,19 @@
package fs
import "os"
const (
FilerResponseNotFoundErrorMessage = "response status code: 404"
FilerStatusNotFoundErrorMessage = "Status:404 Not Found"
)
const (
DefaultDirMode = os.FileMode(0766)
DefaultFileMode = os.FileMode(0666)
)
const (
MethodUpdateFile = "update-file"
MethodUploadFile = "upload-file"
MethodUploadDir = "upload-dir"
)

13
fs/docker-compose.yml Normal file
View File

@@ -0,0 +1,13 @@
version: '2'
services:
server:
image: chrislusf/seaweedfs # use a remote image
container_name: seaweedfs-server
restart: always
# command: "server -dir /data -master.dir /data -volume.dir.idx /data -ip localhost -ip.bind 0.0.0.0 -filer -encryptVolumeData"
command: "server -dir /data -master.dir /data -volume.dir.idx /data -ip localhost -ip.bind 0.0.0.0 -filer"
ports:
- 8888:8888
#volumes:
# - /data/seaweedfs:/data

5
fs/errors.go Normal file
View File

@@ -0,0 +1,5 @@
package fs
import "errors"
var ErrorFsNotExists = errors.New("not exists")

17
fs/go.mod Normal file
View File

@@ -0,0 +1,17 @@
module github.com/crawlab-team/crawlab/fs
go 1.22
replace (
github.com/crawlab-team/crawlab/trace => ../trace
)
require (
github.com/apex/log v1.9.0
github.com/cenkalti/backoff/v4 v4.1.0
github.com/crawlab-team/goseaweedfs v0.6.0-beta.20211101.1936.0.20220912021203-dfee5f74dd69
github.com/emirpasic/gods v1.18.1 // indirect
github.com/google/uuid v1.1.1
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.6.1
)

102
fs/go.sum Normal file
View File

@@ -0,0 +1,102 @@
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/crawlab-team/crawlab/trace v0.1.0 h1:uCqfdqNfb+NwqdkQrBkcYfQ9iqGJ76MbPw1wK8n7xGg=
github.com/crawlab-team/crawlab/trace v0.1.0/go.mod h1:LcWyn68HoT+d29CHM8L41pFHxsAcBMF1xjqJmWdyFh8=
github.com/crawlab-team/goseaweedfs v0.6.0-beta.20211101.1936 h1:c4SgTj2baDqD2UYa1eCpj3ukOF3mXOjvOCP4cWwgfyw=
github.com/crawlab-team/goseaweedfs v0.6.0-beta.20211101.1936/go.mod h1:u+rwfqb0rnYllTLjCctE/z1Yp+TC8L+CbbWH8E2NstA=
github.com/crawlab-team/goseaweedfs v0.6.0-beta.20211101.1936.0.20220912021203-dfee5f74dd69 h1:qPLsh2aWqI5HioWBymzQirt+HQxfRgd7BSoOqfN33Q0=
github.com/crawlab-team/goseaweedfs v0.6.0-beta.20211101.1936.0.20220912021203-dfee5f74dd69/go.mod h1:u+rwfqb0rnYllTLjCctE/z1Yp+TC8L+CbbWH8E2NstA=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/linxGnu/gumble v1.0.0 h1:OAJud8Hy4rmV9I5p/KTRiVpwwklMTd9Ankza3Mz7a4M=
github.com/linxGnu/gumble v1.0.0/go.mod h1:iyhNJpBHvJ0q2Hr41iiZRJyj6LLF47i2a9C9zLiucVY=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/scryner/lfreequeue v0.0.0-20121212074822-473f33702129/go.mod h1:0OrdloYlIayHGsgKYlwEnmdrPWmuYtbdS6Dm71PprFM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/ztrue/tracerr v0.3.0 h1:lDi6EgEYhPYPnKcjsYzmWw4EkFEoA/gfe+I9Y5f+h6Y=
github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

31
fs/interface.go Normal file
View File

@@ -0,0 +1,31 @@
package fs
import (
"github.com/crawlab-team/goseaweedfs"
"time"
)
type Manager interface {
Init() (err error)
Close() (err error)
ListDir(remotePath string, isRecursive bool) (files []goseaweedfs.FilerFileInfo, err error)
UploadFile(localPath, remotePath string, args ...interface{}) (err error)
UploadDir(localPath, remotePath string, args ...interface{}) (err error)
DownloadFile(remotePath, localPath string, args ...interface{}) (err error)
DownloadDir(remotePath, localPath string, args ...interface{}) (err error)
DeleteFile(remotePath string) (err error)
DeleteDir(remotePath string) (err error)
SyncLocalToRemote(localPath, remotePath string, args ...interface{}) (err error)
SyncRemoteToLocal(remotePath, localPath string, args ...interface{}) (err error)
GetFile(remotePath string, args ...interface{}) (data []byte, err error)
GetFileInfo(remotePath string) (file *goseaweedfs.FilerFileInfo, err error)
UpdateFile(remotePath string, data []byte, args ...interface{}) (err error)
Exists(remotePath string, args ...interface{}) (ok bool, err error)
SetFilerUrl(url string)
SetFilerAuthKey(authKey string)
SetTimeout(timeout time.Duration)
SetWorkerNum(num int)
SetRetryInterval(interval time.Duration)
SetRetryNum(num int)
SetMaxQps(qps int)
}

111
fs/lib/copy/copy.go Normal file
View File

@@ -0,0 +1,111 @@
package copy
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"syscall"
)
func CopyDirectory(scrDir, dest string) error {
entries, err := ioutil.ReadDir(scrDir)
if err != nil {
return err
}
for _, entry := range entries {
sourcePath := filepath.Join(scrDir, entry.Name())
destPath := filepath.Join(dest, entry.Name())
fileInfo, err := os.Stat(sourcePath)
if err != nil {
return err
}
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("failed to get raw syscall.Stat_t data for '%s'", sourcePath)
}
switch fileInfo.Mode() & os.ModeType {
case os.ModeDir:
if err := CreateIfNotExists(destPath, 0755); err != nil {
return err
}
if err := CopyDirectory(sourcePath, destPath); err != nil {
return err
}
case os.ModeSymlink:
if err := CopySymLink(sourcePath, destPath); err != nil {
return err
}
default:
if err := Copy(sourcePath, destPath); err != nil {
return err
}
}
if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
return err
}
isSymlink := entry.Mode()&os.ModeSymlink != 0
if !isSymlink {
if err := os.Chmod(destPath, entry.Mode()); err != nil {
return err
}
}
}
return nil
}
func Copy(srcFile, dstFile string) error {
out, err := os.Create(dstFile)
if err != nil {
return err
}
defer out.Close()
in, err := os.Open(srcFile)
defer in.Close()
if err != nil {
return err
}
_, err = io.Copy(out, in)
if err != nil {
return err
}
return nil
}
func Exists(filePath string) bool {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return false
}
return true
}
func CreateIfNotExists(dir string, perm os.FileMode) error {
if Exists(dir) {
return nil
}
if err := os.MkdirAll(dir, perm); err != nil {
return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
}
return nil
}
func CopySymLink(source, dest string) error {
link, err := os.Readlink(source)
if err != nil {
return err
}
return os.Symlink(link, dest)
}

47
fs/options.go Normal file
View File

@@ -0,0 +1,47 @@
package fs
import "time"
type Option func(m Manager)
func WithFilerUrl(url string) Option {
return func(m Manager) {
m.SetFilerUrl(url)
}
}
func WithFilerAuthKey(authKey string) Option {
return func(m Manager) {
m.SetFilerAuthKey(authKey)
}
}
func WithTimeout(timeout time.Duration) Option {
return func(m Manager) {
m.SetTimeout(timeout)
}
}
func WithWorkerNum(num int) Option {
return func(m Manager) {
m.SetWorkerNum(num)
}
}
func WithRetryInterval(interval time.Duration) Option {
return func(m Manager) {
m.SetRetryInterval(interval)
}
}
func WithRetryNum(num int) Option {
return func(m Manager) {
m.SetRetryNum(num)
}
}
func WithMaxQps(qps int) Option {
return func(m Manager) {
m.SetMaxQps(qps)
}
}

722
fs/seaweedfs_manager.go Normal file
View File

@@ -0,0 +1,722 @@
package fs
import (
"bytes"
"errors"
"fmt"
"github.com/cenkalti/backoff/v4"
"github.com/crawlab-team/crawlab/trace"
"github.com/crawlab-team/goseaweedfs"
"github.com/emirpasic/gods/queues/linkedlistqueue"
"github.com/google/uuid"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"
)
type seaweedFsManagerFn func(params seaweedFsManagerParams) (res seaweedFsManagerResults)
type seaweedFsManagerHandle struct {
params seaweedFsManagerParams
fn seaweedFsManagerFn
resChan chan seaweedFsManagerResults
}
type seaweedFsManagerParams struct {
localPath string
remotePath string
isRecursive bool
collection string
ttl string
urlValues url.Values
data []byte
}
type seaweedFsManagerResults struct {
files []goseaweedfs.FilerFileInfo
file *goseaweedfs.FilerFileInfo
data []byte
ok bool
err error
}
type SeaweedFsManager struct {
// settings variables
filerUrl string
timeout time.Duration
authKey string
workerNum int
retryNum uint64
retryInterval time.Duration
maxQps int
// internals
f *goseaweedfs.Filer
q *linkedlistqueue.Queue
cr int
ch chan seaweedFsManagerHandle
closed bool
}
func (m *SeaweedFsManager) Init() (err error) {
// filer options
var filerOpts []goseaweedfs.FilerOption
// auth key
if m.authKey != "" {
filerOpts = append(filerOpts, goseaweedfs.WithFilerAuthKey(m.authKey))
}
// handle channel
m.ch = make(chan seaweedFsManagerHandle, m.workerNum)
// filer instance
m.f, err = goseaweedfs.NewFiler(m.filerUrl, &http.Client{Timeout: m.timeout}, filerOpts...)
if err != nil {
return trace.TraceError(err)
}
// start async
go m.start()
return nil
}
func (m *SeaweedFsManager) Close() (err error) {
m.closed = true
if err := m.f.Close(); err != nil {
return trace.TraceError(err)
}
return nil
}
func (m *SeaweedFsManager) ListDir(remotePath string, isRecursive bool) (files []goseaweedfs.FilerFileInfo, err error) {
params := seaweedFsManagerParams{
remotePath: remotePath,
isRecursive: isRecursive,
}
res := m.process(params, m.listDir)
return res.files, res.err
}
func (m *SeaweedFsManager) ListDirRecursive(remotePath string) (files []goseaweedfs.FilerFileInfo, err error) {
params := seaweedFsManagerParams{
remotePath: remotePath,
}
res := m.process(params, m.listDirRecursive)
return res.files, res.err
}
func (m *SeaweedFsManager) UploadFile(localPath, remotePath string, args ...interface{}) (err error) {
localPath, err = filepath.Abs(localPath)
if err != nil {
return trace.TraceError(err)
}
collection, ttl := getCollectionAndTtlFromArgs(args...)
params := seaweedFsManagerParams{
localPath: localPath,
remotePath: remotePath,
collection: collection,
ttl: ttl,
}
res := m.process(params, m.uploadFile)
return res.err
}
func (m *SeaweedFsManager) UploadDir(localPath, remotePath string, args ...interface{}) (err error) {
localPath, err = filepath.Abs(localPath)
if err != nil {
return trace.TraceError(err)
}
collection, ttl := getCollectionAndTtlFromArgs(args...)
params := seaweedFsManagerParams{
localPath: localPath,
remotePath: remotePath,
collection: collection,
ttl: ttl,
}
res := m.process(params, m.uploadDir)
return res.err
}
func (m *SeaweedFsManager) DownloadFile(remotePath, localPath string, args ...interface{}) (err error) {
localPath, err = filepath.Abs(localPath)
if err != nil {
return trace.TraceError(err)
}
collection, ttl := getCollectionAndTtlFromArgs(args...)
urlValues := getUrlValuesFromArgs(args...)
params := seaweedFsManagerParams{
localPath: localPath,
remotePath: remotePath,
collection: collection,
ttl: ttl,
urlValues: urlValues,
}
res := m.process(params, m.downloadFile)
return res.err
}
func (m *SeaweedFsManager) DownloadDir(remotePath, localPath string, args ...interface{}) (err error) {
localPath, err = filepath.Abs(localPath)
if err != nil {
return trace.TraceError(err)
}
params := seaweedFsManagerParams{
remotePath: remotePath,
}
res := m.process(params, m.downloadDir)
return res.err
}
func (m *SeaweedFsManager) DeleteFile(remotePath string) (err error) {
params := seaweedFsManagerParams{
remotePath: remotePath,
}
res := m.process(params, m.deleteFile)
return res.err
}
func (m *SeaweedFsManager) DeleteDir(remotePath string) (err error) {
params := seaweedFsManagerParams{
remotePath: remotePath,
}
res := m.process(params, m.deleteDir)
return res.err
}
func (m *SeaweedFsManager) SyncLocalToRemote(localPath, remotePath string, args ...interface{}) (err error) {
localPath, err = filepath.Abs(localPath)
if err != nil {
return trace.TraceError(err)
}
collection, ttl := getCollectionAndTtlFromArgs(args...)
params := seaweedFsManagerParams{
localPath: localPath,
remotePath: remotePath,
collection: collection,
ttl: ttl,
}
res := m.process(params, m.syncLocalToRemote)
return res.err
}
func (m *SeaweedFsManager) SyncRemoteToLocal(remotePath, localPath string, args ...interface{}) (err error) {
collection, ttl := getCollectionAndTtlFromArgs(args...)
params := seaweedFsManagerParams{
localPath: localPath,
remotePath: remotePath,
collection: collection,
ttl: ttl,
}
res := m.process(params, m.syncRemoteToLocal)
return res.err
}
func (m *SeaweedFsManager) GetFile(remotePath string, args ...interface{}) (data []byte, err error) {
urlValues := getUrlValuesFromArgs(args...)
params := seaweedFsManagerParams{
remotePath: remotePath,
urlValues: urlValues,
}
res := m.process(params, m.getFile)
return res.data, res.err
}
func (m *SeaweedFsManager) GetFileInfo(remotePath string) (file *goseaweedfs.FilerFileInfo, err error) {
params := seaweedFsManagerParams{
remotePath: remotePath,
}
res := m.process(params, m.getFileInfo)
return res.file, res.err
}
func (m *SeaweedFsManager) UpdateFile(remotePath string, data []byte, args ...interface{}) (err error) {
collection, ttl := getCollectionAndTtlFromArgs(args...)
params := seaweedFsManagerParams{
remotePath: remotePath,
collection: collection,
ttl: ttl,
data: data,
}
res := m.process(params, m.updateFile)
return res.err
}
func (m *SeaweedFsManager) Exists(remotePath string, args ...interface{}) (ok bool, err error) {
_, err = m.GetFile(remotePath, args...)
if err == nil {
// exists
return true, nil
}
if strings.Contains(err.Error(), FilerStatusNotFoundErrorMessage) {
// not exists
return false, nil
}
return ok, trace.TraceError(err)
}
func (m *SeaweedFsManager) SetFilerUrl(url string) {
m.filerUrl = url
}
func (m *SeaweedFsManager) SetFilerAuthKey(authKey string) {
m.authKey = authKey
}
func (m *SeaweedFsManager) SetTimeout(timeout time.Duration) {
m.timeout = timeout
}
func (m *SeaweedFsManager) SetWorkerNum(num int) {
m.workerNum = num
}
func (m *SeaweedFsManager) SetRetryInterval(interval time.Duration) {
m.retryInterval = interval
}
func (m *SeaweedFsManager) SetRetryNum(num int) {
m.retryNum = uint64(num)
}
func (m *SeaweedFsManager) SetMaxQps(qps int) {
m.maxQps = qps
}
func (m *SeaweedFsManager) newHandle(params seaweedFsManagerParams, fn seaweedFsManagerFn) (handle seaweedFsManagerHandle) {
return seaweedFsManagerHandle{
params: params,
fn: fn,
resChan: make(chan seaweedFsManagerResults),
}
}
func (m *SeaweedFsManager) start() {
for {
if m.closed {
return
}
handle := <-m.ch
go func() {
if err := backoff.Retry(func() error {
res := handle.fn(handle.params)
if res.err != nil {
return res.err
}
handle.resChan <- res
return nil
}, backoff.WithMaxRetries(
backoff.NewConstantBackOff(m.retryInterval), m.retryNum),
); err != nil {
handle.resChan <- m.error(err)
}
m.wait()
}()
}
}
func (m *SeaweedFsManager) process(params seaweedFsManagerParams, fn seaweedFsManagerFn) (res seaweedFsManagerResults) {
handle := m.newHandle(params, fn)
//log.Infof("handle: %v", handle)
m.ch <- handle
res = <-handle.resChan
return
}
func (m *SeaweedFsManager) error(err error) (res seaweedFsManagerResults) {
if err != nil {
trace.PrintError(err)
}
return seaweedFsManagerResults{err: err}
}
func (m *SeaweedFsManager) wait() {
ms := float32(1) / float32(m.maxQps) * 1e3
d := time.Duration(ms) * time.Millisecond
time.Sleep(d)
}
func (m *SeaweedFsManager) getCollectionAndTtlArgsFromParams(params seaweedFsManagerParams) (args []interface{}) {
if params.collection != "" {
args = append(args, params.collection)
}
if params.ttl != "" {
args = append(args, params.ttl)
}
return args
}
func (m *SeaweedFsManager) listDir(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
var err error
if params.isRecursive {
res.files, err = m.ListDirRecursive(params.remotePath)
} else {
res.files, err = m.f.ListDir(params.remotePath)
}
if err != nil {
return m.error(err)
}
return
}
func (m *SeaweedFsManager) listDirRecursive(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
entries, err := m.f.ListDir(params.remotePath)
if err != nil {
return m.error(err)
}
for _, file := range entries {
file = goseaweedfs.GetFileWithExtendedFields(file)
if file.IsDir {
file.Children, err = m.ListDirRecursive(file.FullPath)
if err != nil {
return m.error(err)
}
}
res.files = append(res.files, file)
}
return
}
func (m *SeaweedFsManager) uploadFile(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
r, err := m.f.UploadFile(params.localPath, params.remotePath, params.collection, params.ttl)
if err != nil {
return m.error(err)
}
if r.Error != "" {
err = errors.New(r.Error)
return m.error(err)
}
return
}
func (m *SeaweedFsManager) uploadDir(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
args := m.getCollectionAndTtlArgsFromParams(params)
if strings.HasSuffix(params.localPath, "/") {
params.localPath = params.localPath[:(len(params.localPath) - 1)]
}
if !strings.HasPrefix(params.remotePath, "/") {
params.remotePath = "/" + params.remotePath
}
files, err := goseaweedfs.ListFilesRecursive(params.localPath)
if err != nil {
return m.error(err)
}
for _, info := range files {
newFilePath := params.remotePath + strings.Replace(info.Path, params.localPath, "", -1)
if err := m.UploadFile(info.Path, newFilePath, args...); err != nil {
return m.error(err)
}
}
return
}
func (m *SeaweedFsManager) downloadFile(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
err := m.f.Download(params.remotePath, params.urlValues, func(reader io.Reader) error {
data, err := ioutil.ReadAll(reader)
if err != nil {
return trace.TraceError(err)
}
dirPath := filepath.Dir(params.localPath)
_, err = os.Stat(dirPath)
if err != nil {
// if not exists, create a new directory
if err := os.MkdirAll(dirPath, DefaultDirMode); err != nil {
return trace.TraceError(err)
}
}
fileMode := DefaultFileMode
fileInfo, err := os.Stat(params.localPath)
if err == nil {
// if file already exists, save file mode and remove it
fileMode = fileInfo.Mode()
if err := os.Remove(params.localPath); err != nil {
return trace.TraceError(err)
}
}
if err := ioutil.WriteFile(params.localPath, data, fileMode); err != nil {
return trace.TraceError(err)
}
return nil
})
if err != nil {
return m.error(err)
}
return
}
func (m *SeaweedFsManager) downloadDir(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
args := m.getCollectionAndTtlArgsFromParams(params)
var files []goseaweedfs.FilerFileInfo
files, res.err = m.ListDir(params.remotePath, true)
for _, file := range files {
if file.IsDir {
if err := m.DownloadDir(file.FullPath, path.Join(params.localPath, file.Name), args...); err != nil {
return m.error(err)
}
} else {
if err := m.DownloadFile(file.FullPath, path.Join(params.localPath, file.Name), args...); err != nil {
return m.error(err)
}
}
}
return
}
func (m *SeaweedFsManager) deleteFile(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
if err := m.f.DeleteFile(params.remotePath); err != nil {
return m.error(err)
}
return
}
func (m *SeaweedFsManager) deleteDir(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
if err := m.f.DeleteDir(params.remotePath); err != nil {
return m.error(err)
}
return
}
func (m *SeaweedFsManager) syncLocalToRemote(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
// args
args := m.getCollectionAndTtlArgsFromParams(params)
// raise error if local path does not exist
if _, err := os.Stat(params.localPath); err != nil {
return m.error(err)
}
// get files and maps
localFiles, remoteFiles, localFilesMap, remoteFilesMap, err := getFilesAndFilesMaps(m.f, params.localPath, params.remotePath)
if err != nil {
return m.error(err)
}
// compare remote files with local files and delete files absent in local files
for _, remoteFile := range remoteFiles {
// attempt to get corresponding local file
_, ok := localFilesMap[remoteFile.FullPath]
if !ok {
// file does not exist on local, delete
if remoteFile.IsDir {
if err := m.DeleteDir(remoteFile.FullPath); err != nil {
return m.error(err)
}
} else {
if err := m.DeleteFile(remoteFile.FullPath); err != nil {
return m.error(err)
}
}
}
}
// compare local files with remote files and upload files with difference
for _, localFile := range localFiles {
// skip .git
if IsGitFile(localFile) {
continue
}
// corresponding remote file path
fileRemotePath := fmt.Sprintf("%s%s", params.remotePath, strings.Replace(localFile.Path, params.localPath, "", -1))
// attempt to get corresponding remote file
remoteFile, ok := remoteFilesMap[fileRemotePath]
if !ok {
// file does not exist on remote, upload
if err := m.UploadFile(localFile.Path, fileRemotePath, args...); err != nil {
return m.error(err)
}
} else {
// file exists on remote, upload if md5sum values are different
if remoteFile.Md5 != localFile.Md5 {
if err := m.UploadFile(localFile.Path, fileRemotePath, args...); err != nil {
return m.error(err)
}
}
}
}
return
}
func (m *SeaweedFsManager) syncRemoteToLocal(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
// args
args := m.getCollectionAndTtlArgsFromParams(params)
// create directory if local path does not exist
if _, err := os.Stat(params.localPath); err != nil {
if err := os.MkdirAll(params.localPath, os.ModePerm); err != nil {
return m.error(err)
}
}
// get files and maps
localFiles, remoteFiles, localFilesMap, remoteFilesMap, err := getFilesAndFilesMaps(m.f, params.localPath, params.remotePath)
if err != nil {
return m.error(err)
}
// compare local files with remote files and delete files absent on remote
for _, localFile := range localFiles {
// skip .git
if IsGitFile(localFile) {
continue
}
// corresponding remote file path
fileRemotePath := fmt.Sprintf("%s%s", params.remotePath, strings.Replace(localFile.Path, params.localPath, "", -1))
// attempt to get corresponding remote file
_, ok := remoteFilesMap[fileRemotePath]
if !ok {
// file does not exist on remote, upload
if err := os.Remove(localFile.Path); err != nil {
return m.error(err)
}
}
}
// compare remote files with local files and download if files with difference
for _, remoteFile := range remoteFiles {
// directory
if remoteFile.IsDir {
localDirRelativePath := strings.Replace(remoteFile.FullPath, params.remotePath, "", 1)
localDirPath := fmt.Sprintf("%s%s", params.localPath, localDirRelativePath)
if err := m.SyncRemoteToLocal(remoteFile.FullPath, localDirPath); err != nil {
return m.error(err)
}
continue
}
// local file path
localFileRelativePath := strings.Replace(remoteFile.FullPath, params.remotePath, "", 1)
localFilePath := fmt.Sprintf("%s%s", params.localPath, localFileRelativePath)
// attempt to get corresponding local file
localFile, ok := localFilesMap[remoteFile.FullPath]
if !ok {
// file does not exist on local, download
if err := m.DownloadFile(remoteFile.FullPath, localFilePath); err != nil {
return m.error(err)
}
} else {
// file exists on remote, download if md5sum values are different
if remoteFile.Md5 != localFile.Md5 {
if err := m.DownloadFile(remoteFile.FullPath, localFilePath, args...); err != nil {
return m.error(err)
}
}
}
}
return
}
func (m *SeaweedFsManager) getFile(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
var buf bytes.Buffer
res.err = m.f.Download(params.remotePath, params.urlValues, func(reader io.Reader) error {
_, err := io.Copy(&buf, reader)
if err != nil {
return trace.TraceError(err)
}
return nil
})
res.data = buf.Bytes()
return
}
func (m *SeaweedFsManager) getFileInfo(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
arr := strings.Split(params.remotePath, "/")
dirName := strings.Join(arr[:(len(arr)-1)], "/")
files, err := m.f.ListDir(dirName)
if err != nil {
return m.error(err)
}
for _, f := range files {
if f.FullPath == params.remotePath {
res.file = &f
return
}
}
return m.error(ErrorFsNotExists)
}
func (m *SeaweedFsManager) updateFile(params seaweedFsManagerParams) (res seaweedFsManagerResults) {
tmpRootDir := os.TempDir()
tmpDirPath := path.Join(tmpRootDir, ".seaweedfs")
if _, err := os.Stat(tmpDirPath); err != nil {
if err := os.MkdirAll(tmpDirPath, os.ModePerm); err != nil {
return m.error(err)
}
}
tmpFilePath := path.Join(tmpDirPath, fmt.Sprintf(".%s", uuid.New().String()))
if _, err := os.Stat(tmpFilePath); err == nil {
if err := os.Remove(tmpFilePath); err != nil {
return m.error(err)
}
}
if err := ioutil.WriteFile(tmpFilePath, params.data, os.ModePerm); err != nil {
return m.error(err)
}
params2 := seaweedFsManagerParams{
localPath: tmpFilePath,
remotePath: params.remotePath,
collection: params.collection,
ttl: params.ttl,
}
if res := m.uploadFile(params2); res.err != nil {
return m.error(res.err)
}
if err := os.Remove(tmpFilePath); err != nil {
return m.error(err)
}
return
}
func NewSeaweedFsManager(opts ...Option) (m2 Manager, err error) {
// manager
m := &SeaweedFsManager{
filerUrl: "http://localhost:8888",
timeout: 5 * time.Minute,
workerNum: 1,
retryInterval: 500 * time.Millisecond,
retryNum: 3,
maxQps: 5,
q: linkedlistqueue.New(),
}
// apply options
for _, opt := range opts {
opt(m)
}
// initialize
if err := m.Init(); err != nil {
return nil, err
}
return m, nil
}
var _seaweedFsManager Manager
func GetSeaweedFsManager(opts ...Option) (m2 Manager, err error) {
if _seaweedFsManager == nil {
_seaweedFsManager, err = NewSeaweedFsManager(opts...)
if err != nil {
return nil, err
}
}
return _seaweedFsManager, nil
}

56
fs/test/base.go Normal file
View File

@@ -0,0 +1,56 @@
package test
import (
fs "github.com/crawlab-team/crawlab/fs"
"os"
"testing"
"time"
)
func init() {
var err error
T, err = NewTest()
if err != nil {
panic(err)
}
}
var T *Test
type Test struct {
m fs.Manager
}
func (t *Test) Setup(t2 *testing.T) {
t.Cleanup()
t2.Cleanup(t.Cleanup)
}
func (t *Test) Cleanup() {
_ = T.m.DeleteDir("/test")
// wait to avoid caching
time.Sleep(200 * time.Millisecond)
}
func NewTest() (res *Test, err error) {
// test
t := &Test{}
// filer url
filerUrl := os.Getenv("CRAWLAB_FILER_URL")
if filerUrl == "" {
filerUrl = "http://localhost:8888"
}
// manager
t.m, err = fs.NewSeaweedFsManager(
fs.WithFilerUrl(filerUrl),
fs.WithTimeout(10*time.Second),
)
if err != nil {
return nil, err
}
return t, nil
}

269
fs/test/bindata.go Normal file
View File

@@ -0,0 +1,269 @@
// Code generated for package test by go-bindata DO NOT EDIT. (@generated)
// sources:
// bin/start.sh
// bin/stop.sh
package test
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
// Name return file name
func (fi bindataFileInfo) Name() string {
return fi.name
}
// Size return file size
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
// Mode return file modify time
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool {
return fi.mode&os.ModeDir != 0
}
// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _binStartSh = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x8f\x4b\x0e\xc2\x30\x0c\x44\xf7\x3e\xc5\x20\xd6\x4d\x58\xc3\x51\x80\x45\x4b\x1c\xd5\x22\x69\xab\x38\x2d\x3d\x3e\x6a\xa0\x7c\xc4\x05\x90\x57\x7e\xa3\x19\x8f\xb7\x1b\xdb\x48\x67\xb5\x25\xf1\x38\xa2\x62\x18\x9b\xe3\x80\xf3\x01\xb9\xe5\x8e\x80\x3d\x71\x50\x26\x20\x5e\x9d\xa4\x87\x4c\x5e\xe8\x69\x98\x61\x47\x4d\x36\xf4\x97\x3a\x94\xa8\x1b\xb3\xfb\xb0\x97\x55\x39\x4d\x9c\x70\x22\x00\xa8\x5e\x31\x2b\x88\xb5\x66\x4e\xe6\x87\x4f\x7d\x18\x23\x2f\xdc\x88\x9b\xbf\x35\x19\x50\x6e\xb6\xbd\xe6\x37\x33\x8d\x74\x0e\x3b\x53\x66\xc5\x5e\x02\xa7\xf5\x0b\x63\x95\xeb\xa5\x94\x57\xfb\x37\xdd\xbc\xd0\x3d\x00\x00\xff\xff\x09\xef\x85\x28\x89\x01\x00\x00")
func binStartShBytes() ([]byte, error) {
return bindataRead(
_binStartSh,
"bin/start.sh",
)
}
func binStartSh() (*asset, error) {
bytes, err := binStartShBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "bin/start.sh", size: 393, mode: os.FileMode(493), modTime: time.Unix(1621153670, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _binStopSh = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x52\x56\xd4\x4f\xca\xcc\xd3\x2f\xce\xe0\xe2\xca\xce\xcc\xc9\x51\xd0\xb5\x54\x48\x28\x28\x56\x48\xac\x28\xad\x49\x2f\x4a\x2d\x50\x28\x4f\x4d\x4d\x81\xb0\x74\xcb\x14\x40\x74\x4d\x62\x79\xb6\x82\x7a\x75\x41\x51\x66\x5e\x89\x42\x8c\x8a\x51\xad\x7a\x4d\x45\x62\x51\x7a\x71\x02\x20\x00\x00\xff\xff\x6b\x5f\x02\x48\x4a\x00\x00\x00")
func binStopShBytes() ([]byte, error) {
return bindataRead(
_binStopSh,
"bin/stop.sh",
)
}
func binStopSh() (*asset, error) {
bytes, err := binStopShBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "bin/stop.sh", size: 74, mode: os.FileMode(493), modTime: time.Unix(1621154552, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"bin/start.sh": binStartSh,
"bin/stop.sh": binStopSh,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"bin": &bintree{nil, map[string]*bintree{
"start.sh": &bintree{binStartSh, map[string]*bintree{}},
"stop.sh": &bintree{binStopSh, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

21
fs/test/main_test.go Normal file
View File

@@ -0,0 +1,21 @@
package test
import (
"testing"
)
func TestMain(m *testing.M) {
// before test
//if err := StartTestSeaweedFs(); err != nil {
// panic(err)
//}
// test
m.Run()
// close
_ = T.m.Close()
// after test
//_ = StopTestSeaweedFs()
}

473
fs/test/seaweedfs_test.go Normal file
View File

@@ -0,0 +1,473 @@
package test
import (
"fmt"
"github.com/apex/log"
"github.com/crawlab-team/crawlab/fs"
"github.com/crawlab-team/crawlab/fs/lib/copy"
"github.com/stretchr/testify/require"
"io/ioutil"
"os"
"strings"
"sync"
"testing"
"time"
)
func TestNewSeaweedFsManager(t *testing.T) {
_, err := fs.NewSeaweedFsManager()
require.Nil(t, err)
}
func TestSeaweedFsManager_ListDir(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data/nested", "/test/data/nested")
require.Nil(t, err)
valid := false
files, err := T.m.ListDir("/test/data", true)
require.Nil(t, err)
for _, f1 := range files {
if f1.Name == "nested" && f1.Children != nil {
for _, f2 := range f1.Children {
if f2.Name == "nested_test_data.txt" {
valid = true
}
}
}
}
require.True(t, valid)
}
func TestSeaweedFsManager_UploadFile(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadFile("./data/test_data.txt", "/test/data/test_data.txt")
require.Nil(t, err)
files, err := T.m.ListDir("/test/data", true)
require.Nil(t, err)
valid := false
for _, file := range files {
if file.Name == "test_data.txt" {
valid = true
}
}
require.True(t, valid)
}
func TestSeaweedFsManager_UploadDir(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data/nested", "/test/data/nested")
require.Nil(t, err)
valid := false
files, err := T.m.ListDir("/test/data", true)
require.Nil(t, err)
for _, f1 := range files {
if f1.Name == "nested" && f1.Children != nil {
for _, f2 := range f1.Children {
if f2.Name == "nested_test_data.txt" {
valid = true
}
}
}
}
require.True(t, valid)
}
func TestSeaweedFsManager_GetFile(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadFile("./data/test_data.txt", "/test/data/test_data.txt")
require.Nil(t, err)
data, err := T.m.GetFile("/test/data/test_data.txt")
require.Equal(t, "this is a test data", string(data))
}
func TestSeaweedFsManager_DownloadFile(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadFile("./data/test_data.txt", "/test/data/test_data.txt")
require.Nil(t, err)
err = T.m.DownloadFile("/test/data/test_data.txt", "./tmp/test_data.txt")
require.Nil(t, err)
data, err := ioutil.ReadFile("./tmp/test_data.txt")
require.Nil(t, err)
require.NotEmpty(t, data)
}
func TestSeaweedFsManager_DownloadDir(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data/nested", "/test/data/nested")
require.Nil(t, err)
err = T.m.DownloadDir("/test/data", "./tmp/data")
require.Nil(t, err)
data, err := ioutil.ReadFile("./data/nested/nested_test_data.txt")
require.Nil(t, err)
require.NotEmpty(t, data)
}
func TestSeaweedFsManager_DeleteFile(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadFile("./data/test_data.txt", "/test/data/test_data.txt")
require.Nil(t, err)
err = T.m.DeleteFile("/test/data/test_data.txt")
require.Nil(t, err)
files, err := T.m.ListDir("/test/data", true)
require.Nil(t, err)
require.Equal(t, 0, len(files))
}
func TestSeaweedFsManager_DeleteDir(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data", "/test/data")
require.Nil(t, err)
err = T.m.DeleteDir("/test/data/nested")
require.Nil(t, err)
files, err := T.m.ListDir("/test/data", true)
require.Nil(t, err)
valid := true
for _, file := range files {
if file.Name == "nested" && file.IsDir {
valid = false
}
}
require.True(t, valid)
}
func TestSeaweedFsManager_SyncLocalToRemote(t *testing.T) {
var err error
T.Setup(t)
err = copy.CopyDirectory("./data", "./tmp/data")
require.Nil(t, err)
err = T.m.SyncLocalToRemote("./tmp/data", "/test/data")
require.Nil(t, err)
data, err := T.m.GetFile("/test/data/test_data.txt")
require.Nil(t, err)
require.Equal(t, "this is a test data", string(data))
data, err = T.m.GetFile("/test/data/nested/nested_test_data.txt")
require.Nil(t, err)
require.Equal(t, "this is nested test data", string(data))
err = ioutil.WriteFile("./tmp/data/test_data.txt", []byte("this is changed data"), os.ModePerm)
require.Nil(t, err)
err = T.m.SyncLocalToRemote("./tmp/data", "/test/data")
require.Nil(t, err)
data, err = T.m.GetFile("/test/data/test_data.txt")
require.Equal(t, "this is changed data", string(data))
err = os.Remove("./tmp/data/test_data.txt")
require.Nil(t, err)
err = T.m.SyncLocalToRemote("./tmp/data", "/test/data")
require.Nil(t, err)
valid := true
files, err := T.m.ListDir("/test/data", true)
for _, file := range files {
if file.Name == "test_data.txt" {
valid = false
}
}
require.True(t, valid)
// check if directory is deleted after sync
err = ioutil.WriteFile("./tmp/test.txt", []byte("test"), os.ModePerm)
require.Nil(t, err)
err = T.m.UploadFile("./tmp/test.txt", "/test/data/folder1/test.txt")
require.Nil(t, err)
time.Sleep(1 * time.Second)
err = T.m.SyncLocalToRemote("./tmp/data", "/test/data")
require.Nil(t, err)
valid = true
files, err = T.m.ListDir("/test/data", true)
for _, file := range files {
if strings.Contains(file.FullPath, "folder1") {
valid = false
}
}
require.True(t, valid)
}
func TestSeaweedFsManager_SyncRemoteToLocal(t *testing.T) {
var err error
T.Setup(t)
if _, err := os.Stat("./tmp/data"); err == nil {
err = os.RemoveAll("./tmp/data")
require.Nil(t, err)
}
err = T.m.UploadDir("./data", "/test/data")
require.Nil(t, err)
err = T.m.SyncRemoteToLocal("/test/data", "./tmp/data")
require.Nil(t, err)
data, err := ioutil.ReadFile("./tmp/data/test_data.txt")
require.Nil(t, err)
require.Equal(t, "this is a test data", string(data))
data, err = ioutil.ReadFile("./tmp/data/nested/nested_test_data.txt")
require.Nil(t, err)
require.Equal(t, "this is nested test data", string(data))
err = T.m.UpdateFile("/test/data/test_data.txt", []byte("this is changed data"))
require.Nil(t, err)
err = T.m.SyncRemoteToLocal("/test/data", "./tmp/data")
require.Nil(t, err)
data, err = ioutil.ReadFile("./tmp/data/test_data.txt")
require.Equal(t, "this is changed data", string(data))
err = T.m.DeleteFile("/test/data/test_data.txt")
require.Nil(t, err)
err = T.m.SyncRemoteToLocal("/test/data", "./tmp/data")
require.Nil(t, err)
_, err = os.Stat("./tmp/data/test_data.txt")
require.NotNil(t, err)
}
func TestSeaweedFsManager_UpdateFile(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data", "/test/data")
require.Nil(t, err)
err = T.m.UpdateFile("/test/data/test_data.txt", []byte("this is changed data"))
require.Nil(t, err)
data, err := T.m.GetFile("/test/data/test_data.txt")
require.Nil(t, err)
require.Equal(t, "this is changed data", string(data))
}
func TestSeaweedFsManager_Exists(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data", "/test/data")
require.Nil(t, err)
ok, err := T.m.Exists("/test/data/test_data.txt")
require.Nil(t, err)
require.True(t, ok)
ok, err = T.m.Exists("/test/data/test_data_404.txt")
require.Nil(t, err)
require.False(t, ok)
}
func TestSeaweedFsManager_ListDirPressure(t *testing.T) {
var err error
T.Setup(t)
err = T.m.UploadDir("./data/nested", "/test/data/nested")
require.Nil(t, err)
n := int(1e3)
doneNum := 0
errNum := 0
startTs := time.Now()
wg := sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
_, err := T.m.ListDir("test/data", true)
wg.Done()
if err != nil {
errNum++
}
doneNum++
log.Infof("list dir: %d/%d", doneNum, n)
require.Nil(t, err)
}(i)
}
wg.Wait()
endTs := time.Now()
duration := endTs.Sub(startTs).Milliseconds()
fmt.Println(fmt.Sprintf("total: %d", n))
fmt.Println(fmt.Sprintf("errors: %d", errNum))
fmt.Println(fmt.Sprintf("error rate: %.3f", float32(errNum)/float32(n)))
fmt.Println(fmt.Sprintf("duration: %dms", duration))
}
func TestSeaweedFsManager_UploadFilePressure(t *testing.T) {
var err error
T.Setup(t)
n := int(1e3)
doneNum := 0
errNum := 0
startTs := time.Now()
wg := sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
err = T.m.UploadFile("./data/test_data.txt", fmt.Sprintf("/test/data/test_data_%d.txt", i))
wg.Done()
if err != nil {
errNum++
}
doneNum++
log.Infof("upload file: %d/%d", doneNum, n)
require.Nil(t, err)
}(i)
}
wg.Wait()
endTs := time.Now()
duration := endTs.Sub(startTs).Milliseconds()
fmt.Println(fmt.Sprintf("total: %d", n))
fmt.Println(fmt.Sprintf("errors: %d", errNum))
fmt.Println(fmt.Sprintf("error rate: %.3f", float32(errNum)/float32(n)))
fmt.Println(fmt.Sprintf("duration: %dms", duration))
}
func TestSeaweedFsManager_UploadDirPressure(t *testing.T) {
var err error
T.Setup(t)
n := int(1e3)
doneNum := 0
errNum := 0
startTs := time.Now()
wg := sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
err = T.m.UploadDir("./data/nested", "/test/data")
wg.Done()
if err != nil {
errNum++
}
doneNum++
log.Infof("upload dir: %d/%d", doneNum, n)
require.Nil(t, err)
}(i)
}
wg.Wait()
endTs := time.Now()
duration := endTs.Sub(startTs).Milliseconds()
fmt.Println(fmt.Sprintf("total: %d", n))
fmt.Println(fmt.Sprintf("errors: %d", errNum))
fmt.Println(fmt.Sprintf("error rate: %.3f", float32(errNum)/float32(n)))
fmt.Println(fmt.Sprintf("duration: %dms", duration))
}
func TestSeaweedFsManager_SyncRemoteToLocalPressure(t *testing.T) {
var err error
T.Setup(t)
if _, err := os.Stat("./tmp/data"); err == nil {
err = os.RemoveAll("./tmp/data")
require.Nil(t, err)
}
err = T.m.UploadDir("./data", "/test/data")
require.Nil(t, err)
n := int(1e3)
doneNum := 0
errNum := 0
startTs := time.Now()
wg := sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
err = T.m.SyncRemoteToLocal("/test/data", fmt.Sprintf("./tmp/data_%d", i))
wg.Done()
if err != nil {
errNum++
}
doneNum++
log.Infof("updated: %d/%d", doneNum, n)
require.Nil(t, err)
}(i)
}
wg.Wait()
endTs := time.Now()
duration := endTs.Sub(startTs).Milliseconds()
fmt.Println(fmt.Sprintf("total: %d", n))
fmt.Println(fmt.Sprintf("errors: %d", errNum))
fmt.Println(fmt.Sprintf("error rate: %.3f", float32(errNum)/float32(n)))
fmt.Println(fmt.Sprintf("duration: %dms", duration))
}
func TestSeaweedFsManager_UpdateFilePressure(t *testing.T) {
var err error
T.Setup(t)
n := int(5e3)
doneNum := 0
errNum := 0
startTs := time.Now()
wg := sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
err = T.m.UpdateFile(fmt.Sprintf("/test/data/test_data_%5d.txt", i), []byte(fmt.Sprintf("this is test data: %5d", i)))
wg.Done()
if err != nil {
errNum++
}
doneNum++
log.Infof("updated: %d/%d", doneNum, n)
require.Nil(t, err)
}(i)
}
wg.Wait()
endTs := time.Now()
duration := endTs.Sub(startTs).Milliseconds()
fmt.Println(fmt.Sprintf("total: %d", n))
fmt.Println(fmt.Sprintf("errors: %d", errNum))
fmt.Println(fmt.Sprintf("error rate: %.3f", float32(errNum)/float32(n)))
fmt.Println(fmt.Sprintf("duration: %dms", duration))
}

124
fs/test/utils.go Normal file
View File

@@ -0,0 +1,124 @@
package test
import (
"github.com/apex/log"
"github.com/cenkalti/backoff/v4"
"github.com/crawlab-team/crawlab/trace"
"github.com/google/uuid"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"time"
)
func init() {
var err error
TmpDir, err = filepath.Abs("tmp")
if err != nil {
panic(err)
}
if _, err := os.Stat(TmpDir); err != nil {
if err := os.MkdirAll(TmpDir, os.ModePerm); err != nil {
panic(err)
}
}
//TmpDir = getTmpDir()
}
var TmpDir string
func StartTestSeaweedFs() (err error) {
// skip if CRAWLAB_IGNORE_WEED is set true
if os.Getenv("CRAWLAB_IGNORE_WEED") != "" {
return nil
}
// write to start.sh and stop.sh
if err := writeShFiles(TmpDir); err != nil {
return trace.TraceError(err)
}
// run weed
go runCmd(exec.Command("sh", "./start.sh"), TmpDir)
// wait for containers to be ready
time.Sleep(5 * time.Second)
f := func() error {
_, err := T.m.ListDir("/", true)
if err != nil {
return err
}
return nil
}
b := backoff.WithMaxRetries(backoff.NewConstantBackOff(5*time.Second), 5)
nt := func(err error, duration time.Duration) {
log.Infof("seaweedfs services not ready, re-attempt in %.1f seconds", duration.Seconds())
}
err = backoff.RetryNotify(f, b, nt)
if err != nil {
return trace.TraceError(err)
}
return nil
}
func StopTestSeaweedFs() (err error) {
// skip if CRAWLAB_IGNORE_WEED is set true
if os.Getenv("CRAWLAB_IGNORE_WEED") != "" {
return nil
}
// stop seaweedfs
if err := runCmd(exec.Command("sh", "./stop.sh"), TmpDir); err != nil {
return trace.TraceError(err)
}
time.Sleep(5 * time.Second)
// remove tmp folder
if err := os.RemoveAll(TmpDir); err != nil {
return trace.TraceError(err)
}
return nil
}
func writeShFiles(dirPath string) (err error) {
fileNames := []string{
"start.sh",
"stop.sh",
}
for _, fileName := range fileNames {
data, err := Asset("bin/" + fileName)
if err != nil {
return trace.TraceError(err)
}
filePath := path.Join(dirPath, fileName)
if err := ioutil.WriteFile(filePath, data, os.FileMode(0766)); err != nil {
return trace.TraceError(err)
}
}
return nil
}
func runCmd(cmd *exec.Cmd, dirPath string) (err error) {
log.Infof("running cmd: %v", cmd)
cmd.Dir = dirPath
//cmd.Stdout = os.Stdout
//cmd.Stderr = os.Stdout
return cmd.Run()
}
func getTmpDir() string {
id, _ := uuid.NewUUID()
tmpDir := path.Join(os.TempDir(), id.String())
if _, err := os.Stat(tmpDir); err != nil {
if err := os.MkdirAll(tmpDir, os.FileMode(0766)); err != nil {
panic(err)
}
}
return tmpDir
}

89
fs/utils.go Normal file
View File

@@ -0,0 +1,89 @@
package fs
import (
"fmt"
"github.com/crawlab-team/goseaweedfs"
"net/url"
"path/filepath"
"regexp"
"strings"
)
func IsGitFile(file goseaweedfs.FileInfo) (res bool) {
// skip .git
res, err := regexp.MatchString("/?\\.git/", file.Path)
if err != nil {
return false
}
return res
}
func getCollectionAndTtlFromArgs(args ...interface{}) (collection, ttl string) {
if len(args) > 0 {
collection = args[0].(string)
}
if len(args) > 1 {
ttl = args[1].(string)
}
return
}
func getUrlValuesFromArgs(args ...interface{}) (values url.Values) {
if len(args) > 0 {
values = args[0].(url.Values)
}
return values
}
func getFilesAndFilesMaps(f *goseaweedfs.Filer, localPath, remotePath string) (localFiles []goseaweedfs.FileInfo, remoteFiles []goseaweedfs.FilerFileInfo, localFilesMap map[string]goseaweedfs.FileInfo, remoteFilesMap map[string]goseaweedfs.FilerFileInfo, err error) {
// declare maps
localFilesMap = map[string]goseaweedfs.FileInfo{}
remoteFilesMap = map[string]goseaweedfs.FilerFileInfo{}
// cache local files info
localFiles, err = goseaweedfs.ListLocalFilesRecursive(localPath)
if err != nil {
return localFiles, remoteFiles, localFilesMap, remoteFilesMap, err
}
for _, file := range localFiles {
fileRemotePath := fmt.Sprintf("%s%s", remotePath, strings.Replace(file.Path, localPath, "", -1))
localFilesMap[fileRemotePath] = file
// directory
dirRemotePath := filepath.Dir(fileRemotePath)
_, ok := localFilesMap[dirRemotePath]
if !ok {
localFilesMap[dirRemotePath] = goseaweedfs.FileInfo{
Name: filepath.Base(dirRemotePath),
Path: dirRemotePath,
}
}
}
// cache remote files info
remoteFiles, err = f.ListDirRecursive(remotePath)
if err != nil {
if err.Error() != FilerResponseNotFoundErrorMessage {
return localFiles, remoteFiles, localFilesMap, remoteFilesMap, err
}
err = nil
}
remoteFiles = getFlattenRemoteFiles(remoteFiles)
for _, file := range remoteFiles {
remoteFilesMap[file.FullPath] = file
}
return
}
func getFlattenRemoteFiles(files []goseaweedfs.FilerFileInfo) (flattenFiles []goseaweedfs.FilerFileInfo) {
flattenFiles = []goseaweedfs.FilerFileInfo{}
for _, file := range files {
flattenFiles = append(flattenFiles, file)
if file.IsDir {
flattenFiles = append(flattenFiles, getFlattenRemoteFiles(file.Children)...)
}
}
return
}