mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-02 04:32:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbd0afd37e | ||
|
|
7704469d29 | ||
|
|
723ae11db4 | ||
|
|
d5fcb0f474 | ||
|
|
ea13e0523b | ||
|
|
d155f907c2 | ||
|
|
9b0e8ef026 | ||
|
|
8a52379ecb | ||
|
|
70af0ec24c | ||
|
|
cff8bb6554 | ||
|
|
8e2f3b510e | ||
|
|
912ef2bc53 | ||
|
|
9449100757 | ||
|
|
d3d3609a03 | ||
|
|
6fc272f913 | ||
|
|
00a75d7c99 | ||
|
|
e1a3ffcdbf | ||
|
|
4a15e9c830 | ||
|
|
76d9b3bf42 | ||
|
|
97f8b67a64 | ||
|
|
6b995d2985 | ||
|
|
6da8eef54f | ||
|
|
5d01dd28a6 | ||
|
|
8d34eb0050 | ||
|
|
6e1f936efd | ||
|
|
b2ee5873fb | ||
|
|
b067853162 | ||
|
|
e1a8e3f900 | ||
|
|
8676db0f6a | ||
|
|
bd8b9b0c2e | ||
|
|
78859e35fc | ||
|
|
9e7a2772b1 | ||
|
|
4882972478 | ||
|
|
d116432775 | ||
|
|
eed492b73b | ||
|
|
ec08a1670b | ||
|
|
62d3d3aea0 | ||
|
|
e91b9c85a2 | ||
|
|
a8624c2e4f | ||
|
|
da4edf3651 | ||
|
|
c543762e23 | ||
|
|
36492c47ec | ||
|
|
8f8e9ddbde | ||
|
|
875ee5c0d4 | ||
|
|
e4f2d9eb4a | ||
|
|
4fc673341f | ||
|
|
6b591b02af | ||
|
|
b597406699 | ||
|
|
702b1cb876 | ||
|
|
b3dd0ba446 | ||
|
|
8c8b8e2835 | ||
|
|
d522df0ecd | ||
|
|
666859f7cd | ||
|
|
bc47835997 | ||
|
|
8d65ad1444 | ||
|
|
692f0614d1 | ||
|
|
707936f482 | ||
|
|
2c20ef72ae | ||
|
|
d77e3c96e9 | ||
|
|
95ffed87c0 | ||
|
|
6fbfe73683 | ||
|
|
dc31b19469 | ||
|
|
f781cce500 | ||
|
|
8c82fb7e5f | ||
|
|
3e0a79ef05 |
@@ -34,4 +34,4 @@ jobs:
|
||||
run: cd weed; go build -v .
|
||||
|
||||
- name: Test
|
||||
run: cd weed; go test -v .
|
||||
run: cd weed; go test -v ./...
|
||||
|
||||
@@ -349,6 +349,8 @@ Most other distributed file systems seem more complicated than necessary.
|
||||
|
||||
SeaweedFS is meant to be fast and simple, in both setup and operation. If you do not understand how it works when you reach here, we've failed! Please raise an issue with any questions or update this file with clarifications.
|
||||
|
||||
SeaweedFS is constantly moving forward. Same with other systems. These comparisons can be outdated quickly. Please help to keep them updated.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
### Compared to HDFS ###
|
||||
@@ -370,13 +372,14 @@ The architectures are mostly the same. SeaweedFS aims to store and read files fa
|
||||
* SeaweedFS Filer metadata store can be any well-known and proven data stores, e.g., Cassandra, Mongodb, Redis, Elastic Search, MySql, Postgres, MemSql, TiDB, CockroachDB, Etcd etc, and is easy to customized.
|
||||
* SeaweedFS Volume server also communicates directly with clients via HTTP, supporting range queries, direct uploads, etc.
|
||||
|
||||
| System | File Meta | File Content Read| POSIX | REST API | Optimized for large number of small files |
|
||||
| System | File Metadata | File Content Read| POSIX | REST API | Optimized for large number of small files |
|
||||
| ------------- | ------------------------------- | ---------------- | ------ | -------- | ------------------------- |
|
||||
| SeaweedFS | lookup volume id, cacheable | O(1) disk seek | | Yes | Yes |
|
||||
| SeaweedFS Filer| Linearly Scalable, Customizable | O(1) disk seek | FUSE | Yes | Yes |
|
||||
| GlusterFS | hashing | | FUSE, NFS | | |
|
||||
| Ceph | hashing + rules | | FUSE | Yes | |
|
||||
| MooseFS | in memory | | FUSE | | No |
|
||||
| MinIO | separate meta file for each file | | | Yes | No |
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
@@ -418,6 +421,22 @@ SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Red
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
### Compared to MinIO ###
|
||||
|
||||
MinIO follows AWS S3 closely and is ideal for testing for S3 API. It has good UI, policies, versionings, etc. SeaweedFS is trying to catch up here. It is also possible to put MinIO as a gateway in front of SeaweedFS later.
|
||||
|
||||
MinIO metadata are in simple files. Each file write will incur meta file writes.
|
||||
|
||||
MinIO does not have optimization for large number of small files.
|
||||
|
||||
MinIO has multiple disk IO to read one file. SeaweedFS has O(1) disk reads.
|
||||
|
||||
MinIO has full-time erasure coding. SeaweedFS uses replication on hot data for faster speed and optionally applies erasure coding on warm data.
|
||||
|
||||
MinIO does not have POSIX-like API support.
|
||||
|
||||
MinIO has specific requirements on storage layout. It is not flexible to adjust capacity. In SeaweedFS, just start one volume server pointing to the master. That's all.
|
||||
|
||||
## Dev Plan ##
|
||||
|
||||
More tools and documentation, on how to maintain and scale the system. For example, how to move volumes, automatically balancing data, how to grow volumes, how to check system status, etc.
|
||||
|
||||
+21
-2
@@ -1,5 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
isArgPassed() {
|
||||
arg="$1"
|
||||
argWithEqualSign="$1="
|
||||
shift
|
||||
while [ $# -gt 0 ]; do
|
||||
passedArg="$1"
|
||||
shift
|
||||
case $passedArg in
|
||||
$arg)
|
||||
return 0
|
||||
;;
|
||||
$argWithEqualSign*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
|
||||
'master')
|
||||
@@ -9,7 +28,7 @@ case "$1" in
|
||||
|
||||
'volume')
|
||||
ARGS="-dir=/data -max=0"
|
||||
if [[ $@ == *"-max="* ]]; then
|
||||
if isArgPassed "-max" "$@"; then
|
||||
ARGS="-dir=/data"
|
||||
fi
|
||||
exec /usr/bin/weed $@ $ARGS
|
||||
@@ -17,7 +36,7 @@ case "$1" in
|
||||
|
||||
'server')
|
||||
ARGS="-dir=/data -volume.max=0 -master.volumePreallocate -master.volumeSizeLimitMB=1024"
|
||||
if [[ $@ == *"-volume.max="* ]]; then
|
||||
if isArgPassed "-volume.max" "$@"; then
|
||||
ARGS="-dir=/data -master.volumePreallocate -master.volumeSizeLimitMB=1024"
|
||||
fi
|
||||
exec /usr/bin/weed $@ $ARGS
|
||||
|
||||
@@ -11,7 +11,7 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.33.5
|
||||
github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/chrislusf/raft v1.0.1
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
@@ -27,6 +27,7 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gocql/gocql v0.0.0-20190829130954-e163eff7a8c6
|
||||
github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/btree v1.0.0
|
||||
github.com/google/uuid v1.1.1
|
||||
@@ -59,6 +60,7 @@ require (
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
|
||||
github.com/seaweedfs/fuse v0.0.0-20190510212405-310228904eff
|
||||
github.com/seaweedfs/goexif v1.0.2
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
@@ -78,6 +80,7 @@ require (
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.16.0
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/sync v0.0.0-20200930132711-30421366ff76 // indirect
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5
|
||||
google.golang.org/api v0.9.0
|
||||
|
||||
@@ -69,6 +69,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chrislusf/raft v1.0.1 h1:Wa4ffkmkysW7cX3T/gMC/Mk3PhnOXhsqOVwQJcMndhw=
|
||||
github.com/chrislusf/raft v1.0.1/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68=
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011 h1:vN1GvfLgDg8kIPCdhuVKAjlYpxG1B86jiKejB6MC/Q0=
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||
@@ -450,6 +452,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/gunit v1.3.4/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
|
||||
@@ -613,6 +617,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200930132711-30421366ff76 h1:JnxiSYT3Nm0BT2a8CyvYyM6cnrWpidecD1UuSYbhKm0=
|
||||
golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
version: 2.02
|
||||
version: 2.04
|
||||
@@ -4,7 +4,7 @@ global:
|
||||
registry: ""
|
||||
repository: ""
|
||||
imageName: chrislusf/seaweedfs
|
||||
imageTag: "2.02"
|
||||
imageTag: "2.04"
|
||||
imagePullPolicy: IfNotPresent
|
||||
imagePullSecrets: imagepullsecret
|
||||
restartPolicy: Always
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.4.8</version>
|
||||
<version>1.4.9</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.4.8</version>
|
||||
<version>1.4.9</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.4.8</version>
|
||||
<version>1.4.9</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package seaweedfs.client;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -272,15 +273,20 @@ public class FilerClient {
|
||||
|
||||
public boolean createEntry(String parent, FilerProto.Entry entry) {
|
||||
try {
|
||||
filerGrpcClient.getBlockingStub().createEntry(FilerProto.CreateEntryRequest.newBuilder()
|
||||
FilerProto.CreateEntryResponse createEntryResponse =
|
||||
filerGrpcClient.getBlockingStub().createEntry(FilerProto.CreateEntryRequest.newBuilder()
|
||||
.setDirectory(parent)
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
if (Strings.isNullOrEmpty(createEntryResponse.getError())) {
|
||||
return true;
|
||||
}
|
||||
LOG.warn("createEntry {}/{} error: {}", parent, entry.getName(), createEntryResponse.getError());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LOG.warn("createEntry {}/{}: {}", parent, entry.getName(), e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean updateEntry(String parent, FilerProto.Entry entry) {
|
||||
@@ -290,7 +296,7 @@ public class FilerClient {
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
LOG.warn("createEntry {}/{}: {}", parent, entry.getName(), e);
|
||||
LOG.warn("updateEntry {}/{}: {}", parent, entry.getName(), e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class SeaweedRead {
|
||||
|
||||
//TODO parallel this
|
||||
long readCount = 0;
|
||||
int startOffset = bufferOffset;
|
||||
long startOffset = position;
|
||||
for (ChunkView chunkView : chunkViews) {
|
||||
|
||||
if (startOffset < chunkView.logicOffset) {
|
||||
@@ -57,7 +57,7 @@ public class SeaweedRead {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = readChunkView(position, buffer, startOffset, chunkView, locations);
|
||||
int len = readChunkView(startOffset, buffer, bufferOffset + readCount, chunkView, locations);
|
||||
|
||||
LOG.debug("read [{},{}) {} size {}", startOffset, startOffset + len, chunkView.fileId, chunkView.size);
|
||||
|
||||
@@ -66,7 +66,7 @@ public class SeaweedRead {
|
||||
|
||||
}
|
||||
|
||||
long limit = Math.min(bufferLength, fileSize);
|
||||
long limit = Math.min(bufferOffset + bufferLength, fileSize);
|
||||
|
||||
if (startOffset < limit) {
|
||||
long gap = limit - startOffset;
|
||||
@@ -78,7 +78,7 @@ public class SeaweedRead {
|
||||
return readCount;
|
||||
}
|
||||
|
||||
private static int readChunkView(long position, byte[] buffer, int startOffset, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
private static int readChunkView(long startOffset, byte[] buffer, long bufOffset, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
|
||||
byte[] chunkData = chunkCache.getChunk(chunkView.fileId);
|
||||
|
||||
@@ -88,17 +88,51 @@ public class SeaweedRead {
|
||||
}
|
||||
|
||||
int len = (int) chunkView.size;
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} buffer.length:{} startOffset:{} len:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, buffer.length, startOffset, len);
|
||||
System.arraycopy(chunkData, startOffset - (int) (chunkView.logicOffset - chunkView.offset), buffer, startOffset, len);
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} chunkView[{};{}) buf[{},{})/{} startOffset:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, chunkView.logicOffset, chunkView.logicOffset + chunkView.size, bufOffset, bufOffset + len, buffer.length, startOffset);
|
||||
System.arraycopy(chunkData, (int) (startOffset - chunkView.logicOffset + chunkView.offset), buffer, (int) bufOffset, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static byte[] doFetchFullChunkData(ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
|
||||
HttpGet request = new HttpGet(
|
||||
String.format("http://%s/%s", locations.getLocations(0).getUrl(), chunkView.fileId));
|
||||
byte[] data = null;
|
||||
IOException lastException = null;
|
||||
for (long waitTime = 1000L; waitTime < 10 * 1000; waitTime += waitTime / 2) {
|
||||
for (FilerProto.Location location : locations.getLocationsList()) {
|
||||
String url = String.format("http://%s/%s", location.getUrl(), chunkView.fileId);
|
||||
try {
|
||||
data = doFetchOneFullChunkData(chunkView, url);
|
||||
lastException = null;
|
||||
break;
|
||||
} catch (IOException ioe) {
|
||||
LOG.debug("doFetchFullChunkData {} :{}", url, ioe);
|
||||
lastException = ioe;
|
||||
}
|
||||
}
|
||||
if (data != null) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (lastException != null) {
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
LOG.debug("doFetchFullChunkData fid:{} chunkData.length:{}", chunkView.fileId, data.length);
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
public static byte[] doFetchOneFullChunkData(ChunkView chunkView, String url) throws IOException {
|
||||
|
||||
HttpGet request = new HttpGet(url);
|
||||
|
||||
request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
|
||||
|
||||
@@ -142,7 +176,7 @@ public class SeaweedRead {
|
||||
data = Gzip.decompress(data);
|
||||
}
|
||||
|
||||
LOG.debug("doFetchFullChunkData fid:{} chunkData.length:{}", chunkView.fileId, data.length);
|
||||
LOG.debug("doFetchOneFullChunkData url:{} chunkData.length:{}", url, data.length);
|
||||
|
||||
return data;
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.4.8</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.4.9</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.4.8</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.4.9</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics, seaweedBufferSize);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
|
||||
@@ -205,10 +205,9 @@ public class SeaweedFileSystemStore {
|
||||
|
||||
}
|
||||
|
||||
public FSInputStream openFileForRead(final Path path, FileSystem.Statistics statistics,
|
||||
int bufferSize) throws IOException {
|
||||
public FSInputStream openFileForRead(final Path path, FileSystem.Statistics statistics) throws IOException {
|
||||
|
||||
LOG.debug("openFileForRead path:{} bufferSize:{}", path, bufferSize);
|
||||
LOG.debug("openFileForRead path:{}", path);
|
||||
|
||||
FilerProto.Entry entry = lookupEntry(path);
|
||||
|
||||
@@ -219,8 +218,7 @@ public class SeaweedFileSystemStore {
|
||||
return new SeaweedInputStream(filerGrpcClient,
|
||||
statistics,
|
||||
path.toUri().getPath(),
|
||||
entry,
|
||||
bufferSize);
|
||||
entry);
|
||||
}
|
||||
|
||||
public void setOwner(Path path, String owner, String group) {
|
||||
|
||||
@@ -25,7 +25,6 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
private final FilerProto.Entry entry;
|
||||
private final List<SeaweedRead.VisibleInterval> visibleIntervalList;
|
||||
private final long contentLength;
|
||||
private final int bufferSize; // default buffer size
|
||||
|
||||
private long position = 0; // cursor of the file
|
||||
|
||||
@@ -35,14 +34,12 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String path,
|
||||
final FilerProto.Entry entry,
|
||||
final int bufferSize) throws IOException {
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.statistics = statistics;
|
||||
this.path = path;
|
||||
this.entry = entry;
|
||||
this.contentLength = SeaweedRead.fileSize(entry);
|
||||
this.bufferSize = bufferSize;
|
||||
|
||||
this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(filerGrpcClient, entry.getChunksList());
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.4.8</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.4.9</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.4.8</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.4.9</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics, seaweedBufferSize);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
|
||||
@@ -205,10 +205,9 @@ public class SeaweedFileSystemStore {
|
||||
|
||||
}
|
||||
|
||||
public FSInputStream openFileForRead(final Path path, FileSystem.Statistics statistics,
|
||||
int bufferSize) throws IOException {
|
||||
public FSInputStream openFileForRead(final Path path, FileSystem.Statistics statistics) throws IOException {
|
||||
|
||||
LOG.debug("openFileForRead path:{} bufferSize:{}", path, bufferSize);
|
||||
LOG.debug("openFileForRead path:{}", path);
|
||||
|
||||
FilerProto.Entry entry = lookupEntry(path);
|
||||
|
||||
@@ -219,8 +218,7 @@ public class SeaweedFileSystemStore {
|
||||
return new SeaweedInputStream(filerGrpcClient,
|
||||
statistics,
|
||||
path.toUri().getPath(),
|
||||
entry,
|
||||
bufferSize);
|
||||
entry);
|
||||
}
|
||||
|
||||
public void setOwner(Path path, String owner, String group) {
|
||||
|
||||
@@ -25,7 +25,6 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
private final FilerProto.Entry entry;
|
||||
private final List<SeaweedRead.VisibleInterval> visibleIntervalList;
|
||||
private final long contentLength;
|
||||
private final int bufferSize; // default buffer size
|
||||
|
||||
private long position = 0; // cursor of the file
|
||||
|
||||
@@ -35,14 +34,12 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String path,
|
||||
final FilerProto.Entry entry,
|
||||
final int bufferSize) throws IOException {
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.statistics = statistics;
|
||||
this.path = path;
|
||||
this.entry = entry;
|
||||
this.contentLength = SeaweedRead.fileSize(entry);
|
||||
this.bufferSize = bufferSize;
|
||||
|
||||
this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(filerGrpcClient, entry.getChunksList());
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjectTagging(t *testing.T) {
|
||||
|
||||
input := &s3.PutObjectInput{
|
||||
Bucket: aws.String("theBucket"),
|
||||
Key: aws.String("testDir/testObject"),
|
||||
}
|
||||
|
||||
svc.PutObject(input)
|
||||
|
||||
printTags()
|
||||
|
||||
setTags()
|
||||
|
||||
printTags()
|
||||
|
||||
clearTags()
|
||||
|
||||
printTags()
|
||||
|
||||
}
|
||||
|
||||
func printTags() {
|
||||
response, err := svc.GetObjectTagging(
|
||||
&s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String("theBucket"),
|
||||
Key: aws.String("testDir/testObject"),
|
||||
})
|
||||
|
||||
fmt.Println("printTags")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(response.TagSet)
|
||||
}
|
||||
|
||||
func setTags() {
|
||||
|
||||
response, err := svc.PutObjectTagging(&s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String("theBucket"),
|
||||
Key: aws.String("testDir/testObject"),
|
||||
Tagging: &s3.Tagging{
|
||||
TagSet: []*s3.Tag{
|
||||
{
|
||||
Key: aws.String("kye2"),
|
||||
Value: aws.String("value2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
fmt.Println("setTags")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(response.String())
|
||||
}
|
||||
|
||||
func clearTags() {
|
||||
|
||||
response, err := svc.DeleteObjectTagging(&s3.DeleteObjectTaggingInput{
|
||||
Bucket: aws.String("theBucket"),
|
||||
Key: aws.String("testDir/testObject"),
|
||||
})
|
||||
|
||||
fmt.Println("clearTags")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(response.String())
|
||||
}
|
||||
@@ -282,14 +282,19 @@ func readFiles(fileIdLineChan chan string, s *stat) {
|
||||
start := time.Now()
|
||||
var bytesRead int
|
||||
var err error
|
||||
url, err := b.masterClient.LookupFileId(fid)
|
||||
urls, err := b.masterClient.LookupFileId(fid)
|
||||
if err != nil {
|
||||
s.failed++
|
||||
println("!!!! ", fid, " location not found!!!!!")
|
||||
continue
|
||||
}
|
||||
var bytes []byte
|
||||
bytes, err = util.Get(url)
|
||||
for _, url := range urls {
|
||||
bytes, err = util.Get(url)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
bytesRead = len(bytes)
|
||||
if err == nil {
|
||||
s.completed++
|
||||
|
||||
+29
-9
@@ -1,15 +1,16 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/raft/protobuf"
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/util/grace"
|
||||
|
||||
@@ -41,6 +42,7 @@ type MasterOptions struct {
|
||||
disableHttp *bool
|
||||
metricsAddress *string
|
||||
metricsIntervalSec *int
|
||||
raftResumeState *bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -59,6 +61,7 @@ func init() {
|
||||
m.disableHttp = cmdMaster.Flag.Bool("disableHttp", false, "disable http requests, only gRPC operations are allowed.")
|
||||
m.metricsAddress = cmdMaster.Flag.String("metrics.address", "", "Prometheus gateway address <host>:<port>")
|
||||
m.metricsIntervalSec = cmdMaster.Flag.Int("metrics.intervalSeconds", 15, "Prometheus push interval in seconds")
|
||||
m.raftResumeState = cmdMaster.Flag.Bool("resumeState", false, "resume previous state on start master server")
|
||||
}
|
||||
|
||||
var cmdMaster = &Command{
|
||||
@@ -118,10 +121,10 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) {
|
||||
glog.Fatalf("Master startup error: %v", e)
|
||||
}
|
||||
// start raftServer
|
||||
raftServer := weed_server.NewRaftServer(security.LoadClientTLS(util.GetViper(), "grpc.master"),
|
||||
peers, myMasterAddress, util.ResolvePath(*masterOption.metaFolder), ms.Topo, 5)
|
||||
raftServer, err := weed_server.NewRaftServer(security.LoadClientTLS(util.GetViper(), "grpc.master"),
|
||||
peers, myMasterAddress, util.ResolvePath(*masterOption.metaFolder), ms.Topo, 5, *masterOption.raftResumeState)
|
||||
if raftServer == nil {
|
||||
glog.Fatalf("please verify %s is writable, see https://github.com/chrislusf/seaweedfs/issues/717", *masterOption.metaFolder)
|
||||
glog.Fatalf("please verify %s is writable, see https://github.com/chrislusf/seaweedfs/issues/717: %s", *masterOption.metaFolder, err)
|
||||
}
|
||||
ms.SetRaftServer(raftServer)
|
||||
r.HandleFunc("/cluster/status", raftServer.StatusHandler).Methods("GET")
|
||||
@@ -139,6 +142,15 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) {
|
||||
glog.V(0).Infof("Start Seaweed Master %s grpc server at %s:%d", util.Version(), *masterOption.ipBind, grpcPort)
|
||||
go grpcS.Serve(grpcL)
|
||||
|
||||
go func() {
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
if ms.Topo.RaftServer.Leader() == "" && ms.Topo.RaftServer.IsLogEmpty() && isTheFirstOne(myMasterAddress, peers) {
|
||||
if ms.MasterClient.FindLeader(myMasterAddress) == "" {
|
||||
raftServer.DoJoinCommand()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go ms.MasterClient.KeepConnectedToMaster()
|
||||
|
||||
// start http server
|
||||
@@ -172,6 +184,14 @@ func checkPeers(masterIp string, masterPort int, peers string) (masterAddress st
|
||||
return
|
||||
}
|
||||
|
||||
func isTheFirstOne(self string, peers []string) bool {
|
||||
sort.Strings(peers)
|
||||
if len(peers) <= 0 {
|
||||
return true
|
||||
}
|
||||
return self == peers[0]
|
||||
}
|
||||
|
||||
func (m *MasterOptions) toMasterOption(whiteList []string) *weed_server.MasterOption {
|
||||
return &weed_server.MasterOption{
|
||||
Host: *m.ip,
|
||||
|
||||
+10
-12
@@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MountOptions struct {
|
||||
@@ -25,9 +26,10 @@ type MountOptions struct {
|
||||
}
|
||||
|
||||
var (
|
||||
mountOptions MountOptions
|
||||
mountCpuProfile *string
|
||||
mountMemProfile *string
|
||||
mountOptions MountOptions
|
||||
mountCpuProfile *string
|
||||
mountMemProfile *string
|
||||
mountReadRetryTime *time.Duration
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -46,11 +48,13 @@ func init() {
|
||||
mountOptions.allowOthers = cmdMount.Flag.Bool("allowOthers", true, "allows other users to access the file system")
|
||||
mountOptions.umaskString = cmdMount.Flag.String("umask", "022", "octal umask, e.g., 022, 0111")
|
||||
mountOptions.nonempty = cmdMount.Flag.Bool("nonempty", false, "allows the mounting over a non-empty directory")
|
||||
mountCpuProfile = cmdMount.Flag.String("cpuprofile", "", "cpu profile output file")
|
||||
mountMemProfile = cmdMount.Flag.String("memprofile", "", "memory profile output file")
|
||||
mountOptions.outsideContainerClusterMode = cmdMount.Flag.Bool("outsideContainerClusterMode", false, "allows other users to access the file system")
|
||||
mountOptions.outsideContainerClusterMode = cmdMount.Flag.Bool("outsideContainerClusterMode", false, "allows other users to access volume servers with publicUrl")
|
||||
mountOptions.uidMap = cmdMount.Flag.String("map.uid", "", "map local uid to uid on filer, comma-separated <local_uid>:<filer_uid>")
|
||||
mountOptions.gidMap = cmdMount.Flag.String("map.gid", "", "map local gid to gid on filer, comma-separated <local_gid>:<filer_gid>")
|
||||
|
||||
mountCpuProfile = cmdMount.Flag.String("cpuprofile", "", "cpu profile output file")
|
||||
mountMemProfile = cmdMount.Flag.String("memprofile", "", "memory profile output file")
|
||||
mountReadRetryTime = cmdMount.Flag.Duration("readRetryTime", 6*time.Second, "maximum read retry wait time")
|
||||
}
|
||||
|
||||
var cmdMount = &Command{
|
||||
@@ -68,11 +72,5 @@ var cmdMount = &Command{
|
||||
|
||||
On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
|
||||
|
||||
If the SeaweedFS system runs in a container cluster, e.g. managed by kubernetes or docker compose,
|
||||
the volume servers are not accessible by their own ip addresses.
|
||||
In "outsideContainerClusterMode", the mount will use the filer ip address instead, assuming:
|
||||
* All volume server containers are accessible through the same hostname or IP address as the filer.
|
||||
* All volume server container ports are open external to the cluster.
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -28,6 +30,10 @@ import (
|
||||
func runMount(cmd *Command, args []string) bool {
|
||||
|
||||
grace.SetupProfiling(*mountCpuProfile, *mountMemProfile)
|
||||
if *mountReadRetryTime < time.Second {
|
||||
*mountReadRetryTime = time.Second
|
||||
}
|
||||
filer.ReadWaitTime = *mountReadRetryTime
|
||||
|
||||
umask, umaskErr := strconv.ParseUint(*mountOptions.umaskString, 8, 64)
|
||||
if umaskErr != nil {
|
||||
@@ -92,6 +98,29 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
}
|
||||
fileInfo, err := os.Stat(dir)
|
||||
|
||||
uid, gid := uint32(0), uint32(0)
|
||||
mountMode := os.ModeDir | 0777
|
||||
if err == nil {
|
||||
mountMode = os.ModeDir | fileInfo.Mode()
|
||||
uid, gid = util.GetFileUidGid(fileInfo)
|
||||
fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, fileInfo.Mode())
|
||||
} else {
|
||||
fmt.Printf("can not stat %s\n", dir)
|
||||
return false
|
||||
}
|
||||
|
||||
if uid == 0 {
|
||||
if u, err := user.Current(); err == nil {
|
||||
if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil {
|
||||
uid = uint32(parsedId)
|
||||
}
|
||||
if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil {
|
||||
gid = uint32(parsedId)
|
||||
}
|
||||
fmt.Printf("current uid=%d gid=%d\n", uid, gid)
|
||||
}
|
||||
}
|
||||
|
||||
// mapping uid, gid
|
||||
uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
|
||||
if err != nil {
|
||||
@@ -150,6 +179,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
CacheSizeMB: *option.cacheSizeMB,
|
||||
DataCenter: *option.dataCenter,
|
||||
EntryCacheTtl: 3 * time.Second,
|
||||
MountUid: uid,
|
||||
MountGid: gid,
|
||||
MountMode: mountMode,
|
||||
MountCtime: fileInfo.ModTime(),
|
||||
MountMtime: time.Now(),
|
||||
Umask: umask,
|
||||
|
||||
+13
-1
@@ -54,7 +54,13 @@ var cmdS3 = &Command{
|
||||
{
|
||||
"identities": [
|
||||
{
|
||||
"name": "some_name",
|
||||
"name": "anonymous",
|
||||
"actions": [
|
||||
"Read"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "some_admin_user",
|
||||
"credentials": [
|
||||
{
|
||||
"accessKey": "some_access_key1",
|
||||
@@ -64,6 +70,8 @@ var cmdS3 = &Command{
|
||||
"actions": [
|
||||
"Admin",
|
||||
"Read",
|
||||
"List",
|
||||
"Tagging",
|
||||
"Write"
|
||||
]
|
||||
},
|
||||
@@ -89,6 +97,8 @@ var cmdS3 = &Command{
|
||||
],
|
||||
"actions": [
|
||||
"Read",
|
||||
"List",
|
||||
"Tagging",
|
||||
"Write"
|
||||
]
|
||||
},
|
||||
@@ -102,6 +112,8 @@ var cmdS3 = &Command{
|
||||
],
|
||||
"actions": [
|
||||
"Read:bucket1",
|
||||
"List:bucket1",
|
||||
"Tagging:bucket1",
|
||||
"Write:bucket1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ func init() {
|
||||
masterOptions.garbageThreshold = cmdServer.Flag.Float64("garbageThreshold", 0.3, "threshold to vacuum and reclaim spaces")
|
||||
masterOptions.metricsAddress = cmdServer.Flag.String("metrics.address", "", "Prometheus gateway address")
|
||||
masterOptions.metricsIntervalSec = cmdServer.Flag.Int("metrics.intervalSeconds", 15, "Prometheus push interval in seconds")
|
||||
masterOptions.raftResumeState = cmdServer.Flag.Bool("resumeState", false, "resume previous state on start master server")
|
||||
|
||||
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")
|
||||
|
||||
@@ -138,6 +138,11 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
|
||||
glog.Fatalf("The max specified in -max not a valid number %s", maxString)
|
||||
}
|
||||
}
|
||||
if len(v.folderMaxLimits) == 1 && len(v.folders) > 1 {
|
||||
for i := 0; i < len(v.folders)-1; i++ {
|
||||
v.folderMaxLimits = append(v.folderMaxLimits, v.folderMaxLimits[0])
|
||||
}
|
||||
}
|
||||
if len(v.folders) != len(v.folderMaxLimits) {
|
||||
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
@@ -84,21 +85,40 @@ func ResolveOneChunkManifest(lookupFileIdFn LookupFileIdFunctionType, chunk *fil
|
||||
|
||||
// TODO fetch from cache for weed mount?
|
||||
func fetchChunk(lookupFileIdFn LookupFileIdFunctionType, fileId string, cipherKey []byte, isGzipped bool) ([]byte, error) {
|
||||
urlString, err := lookupFileIdFn(fileId)
|
||||
urlStrings, err := lookupFileIdFn(fileId)
|
||||
if err != nil {
|
||||
glog.Errorf("operation LookupFileId %s failed, err: %v", fileId, err)
|
||||
return nil, err
|
||||
}
|
||||
return retriedFetchChunkData(urlStrings, cipherKey, isGzipped, true, 0, 0)
|
||||
}
|
||||
|
||||
func retriedFetchChunkData(urlStrings []string, cipherKey []byte, isGzipped bool, isFullChunk bool, offset int64, size int) ([]byte, error) {
|
||||
|
||||
var err error
|
||||
var buffer bytes.Buffer
|
||||
err = util.ReadUrlAsStream(urlString, cipherKey, isGzipped, true, 0, 0, func(data []byte) {
|
||||
buffer.Write(data)
|
||||
})
|
||||
if err != nil {
|
||||
glog.V(0).Infof("read %s failed, err: %v", fileId, err)
|
||||
return nil, err
|
||||
|
||||
for waitTime := time.Second; waitTime < ReadWaitTime; waitTime += waitTime / 2 {
|
||||
for _, urlString := range urlStrings {
|
||||
err = util.ReadUrlAsStream(urlString, cipherKey, isGzipped, isFullChunk, offset, size, func(data []byte) {
|
||||
buffer.Write(data)
|
||||
})
|
||||
if err != nil {
|
||||
glog.V(0).Infof("read %s failed, err: %v", urlString, err)
|
||||
buffer.Reset()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
glog.V(0).Infof("sleep for %v before retrying reading", waitTime)
|
||||
time.Sleep(waitTime)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func MaybeManifestize(saveFunc SaveDataAsChunkFunctionType, inputChunks []*filer_pb.FileChunk) (chunks []*filer_pb.FileChunk, err error) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func TotalSize(chunks []*filer_pb.FileChunk) (size uint64) {
|
||||
@@ -42,12 +44,12 @@ func ETagChunks(chunks []*filer_pb.FileChunk) (etag string) {
|
||||
if len(chunks) == 1 {
|
||||
return chunks[0].ETag
|
||||
}
|
||||
|
||||
h := fnv.New32a()
|
||||
md5_digests := [][]byte{}
|
||||
for _, c := range chunks {
|
||||
h.Write([]byte(c.ETag))
|
||||
md5_decoded, _ := hex.DecodeString(c.ETag)
|
||||
md5_digests = append(md5_digests, md5_decoded)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum32())
|
||||
return fmt.Sprintf("%x-%d", util.Md5(bytes.Join(md5_digests, nil)), len(chunks))
|
||||
}
|
||||
|
||||
func CompactFileChunks(lookupFileIdFn LookupFileIdFunctionType, chunks []*filer_pb.FileChunk) (compacted, garbage []*filer_pb.FileChunk) {
|
||||
|
||||
@@ -77,7 +77,7 @@ func (f *Filer) doBatchDeleteFolderMetaAndData(ctx context.Context, entry *Entry
|
||||
if lastFileName == "" && !isRecursive && len(entries) > 0 {
|
||||
// only for first iteration in the loop
|
||||
glog.Errorf("deleting a folder %s has children: %+v ...", entry.FullPath, entries[0].Name())
|
||||
return nil, nil,fmt.Errorf("fail to delete non-empty folder: %s", entry.FullPath)
|
||||
return nil, nil, fmt.Errorf("fail to delete non-empty folder: %s", entry.FullPath)
|
||||
}
|
||||
|
||||
for _, sub := range entries {
|
||||
|
||||
+114
-36
@@ -3,52 +3,88 @@ package filer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util/chunk_cache"
|
||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||
"github.com/golang/groupcache/singleflight"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ReadWaitTime = 6 * time.Second
|
||||
)
|
||||
|
||||
type ChunkReadAt struct {
|
||||
masterClient *wdclient.MasterClient
|
||||
chunkViews []*ChunkView
|
||||
lookupFileId func(fileId string) (targetUrl string, err error)
|
||||
lookupFileId LookupFileIdFunctionType
|
||||
readerLock sync.Mutex
|
||||
fileSize int64
|
||||
|
||||
chunkCache chunk_cache.ChunkCache
|
||||
fetchGroup singleflight.Group
|
||||
lastChunkFileId string
|
||||
lastChunkData []byte
|
||||
chunkCache chunk_cache.ChunkCache
|
||||
}
|
||||
|
||||
// var _ = io.ReaderAt(&ChunkReadAt{})
|
||||
|
||||
type LookupFileIdFunctionType func(fileId string) (targetUrl string, err error)
|
||||
type LookupFileIdFunctionType func(fileId string) (targetUrls []string, err error)
|
||||
|
||||
func LookupFn(filerClient filer_pb.FilerClient) LookupFileIdFunctionType {
|
||||
return func(fileId string) (targetUrl string, err error) {
|
||||
err = filerClient.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
vid := VolumeId(fileId)
|
||||
resp, err := client.LookupVolume(context.Background(), &filer_pb.LookupVolumeRequest{
|
||||
VolumeIds: []string{vid},
|
||||
|
||||
vidCache := make(map[string]*filer_pb.Locations)
|
||||
return func(fileId string) (targetUrls []string, err error) {
|
||||
vid := VolumeId(fileId)
|
||||
locations, found := vidCache[vid]
|
||||
|
||||
waitTime := time.Second
|
||||
for !found && waitTime < ReadWaitTime {
|
||||
// println("looking up volume", vid)
|
||||
err = filerClient.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
resp, err := client.LookupVolume(context.Background(), &filer_pb.LookupVolumeRequest{
|
||||
VolumeIds: []string{vid},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
locations = resp.LocationsMap[vid]
|
||||
if locations == nil || len(locations.Locations) == 0 {
|
||||
glog.V(0).Infof("failed to locate %s", fileId)
|
||||
return fmt.Errorf("failed to locate %s", fileId)
|
||||
}
|
||||
vidCache[vid] = locations
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
glog.V(1).Infof("wait for volume %s", vid)
|
||||
time.Sleep(waitTime)
|
||||
waitTime += waitTime / 2
|
||||
}
|
||||
|
||||
locations := resp.LocationsMap[vid]
|
||||
if locations == nil || len(locations.Locations) == 0 {
|
||||
glog.V(0).Infof("failed to locate %s", fileId)
|
||||
return fmt.Errorf("failed to locate %s", fileId)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeServerAddress := filerClient.AdjustedUrl(locations.Locations[0].Url)
|
||||
for _, loc := range locations.Locations {
|
||||
volumeServerAddress := filerClient.AdjustedUrl(loc)
|
||||
targetUrl := fmt.Sprintf("http://%s/%s", volumeServerAddress, fileId)
|
||||
targetUrls = append(targetUrls, targetUrl)
|
||||
}
|
||||
|
||||
targetUrl = fmt.Sprintf("http://%s/%s", volumeServerAddress, fileId)
|
||||
for i := len(targetUrls) - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
targetUrls[i], targetUrls[j] = targetUrls[j], targetUrls[i]
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -76,10 +112,16 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
var buffer []byte
|
||||
startOffset, remaining := offset, int64(len(p))
|
||||
var nextChunk *ChunkView
|
||||
for i, chunk := range c.chunkViews {
|
||||
if remaining <= 0 {
|
||||
break
|
||||
}
|
||||
if i+1 < len(c.chunkViews) {
|
||||
nextChunk = c.chunkViews[i+1]
|
||||
} else {
|
||||
nextChunk = nil
|
||||
}
|
||||
if startOffset < chunk.LogicOffset {
|
||||
gap := int(chunk.LogicOffset - startOffset)
|
||||
glog.V(4).Infof("zero [%d,%d)", startOffset, startOffset+int64(gap))
|
||||
@@ -95,7 +137,7 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("read [%d,%d), %d/%d chunk %s [%d,%d)", chunkStart, chunkStop, i, len(c.chunkViews), chunk.FileId, chunk.LogicOffset-chunk.Offset, chunk.LogicOffset-chunk.Offset+int64(chunk.Size))
|
||||
buffer, err = c.readFromWholeChunkData(chunk)
|
||||
buffer, err = c.readFromWholeChunkData(chunk, nextChunk)
|
||||
if err != nil {
|
||||
glog.Errorf("fetching chunk %+v: %v\n", chunk, err)
|
||||
return
|
||||
@@ -123,27 +165,63 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) {
|
||||
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) readFromWholeChunkData(chunkView *ChunkView) (chunkData []byte, err error) {
|
||||
func (c *ChunkReadAt) readFromWholeChunkData(chunkView *ChunkView, nextChunkViews ...*ChunkView) (chunkData []byte, err error) {
|
||||
|
||||
glog.V(4).Infof("readFromWholeChunkData %s offset %d [%d,%d) size at least %d", chunkView.FileId, chunkView.Offset, chunkView.LogicOffset, chunkView.LogicOffset+int64(chunkView.Size), chunkView.ChunkSize)
|
||||
if c.lastChunkFileId == chunkView.FileId {
|
||||
return c.lastChunkData, nil
|
||||
}
|
||||
|
||||
chunkData = c.chunkCache.GetChunk(chunkView.FileId, chunkView.ChunkSize)
|
||||
if chunkData != nil {
|
||||
glog.V(4).Infof("cache hit %s [%d,%d)", chunkView.FileId, chunkView.LogicOffset-chunkView.Offset, chunkView.LogicOffset-chunkView.Offset+int64(len(chunkData)))
|
||||
} else {
|
||||
glog.V(4).Infof("doFetchFullChunkData %s", chunkView.FileId)
|
||||
chunkData, err = c.doFetchFullChunkData(chunkView.FileId, chunkView.CipherKey, chunkView.IsGzipped)
|
||||
if err != nil {
|
||||
return
|
||||
v, doErr := c.readOneWholeChunk(chunkView)
|
||||
|
||||
if doErr != nil {
|
||||
return nil, doErr
|
||||
}
|
||||
|
||||
chunkData = v.([]byte)
|
||||
|
||||
c.lastChunkData = chunkData
|
||||
c.lastChunkFileId = chunkView.FileId
|
||||
|
||||
for _, nextChunkView := range nextChunkViews {
|
||||
if c.chunkCache != nil && nextChunkView != nil {
|
||||
go c.readOneWholeChunk(nextChunkView)
|
||||
}
|
||||
c.chunkCache.SetChunk(chunkView.FileId, chunkData)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) doFetchFullChunkData(fileId string, cipherKey []byte, isGzipped bool) ([]byte, error) {
|
||||
func (c *ChunkReadAt) readOneWholeChunk(chunkView *ChunkView) (interface{}, error) {
|
||||
|
||||
return fetchChunk(c.lookupFileId, fileId, cipherKey, isGzipped)
|
||||
var err error
|
||||
|
||||
return c.fetchGroup.Do(chunkView.FileId, func() (interface{}, error) {
|
||||
|
||||
glog.V(4).Infof("readFromWholeChunkData %s offset %d [%d,%d) size at least %d", chunkView.FileId, chunkView.Offset, chunkView.LogicOffset, chunkView.LogicOffset+int64(chunkView.Size), chunkView.ChunkSize)
|
||||
|
||||
data := c.chunkCache.GetChunk(chunkView.FileId, chunkView.ChunkSize)
|
||||
if data != nil {
|
||||
glog.V(4).Infof("cache hit %s [%d,%d)", chunkView.FileId, chunkView.LogicOffset-chunkView.Offset, chunkView.LogicOffset-chunkView.Offset+int64(len(data)))
|
||||
} else {
|
||||
var err error
|
||||
data, err = c.doFetchFullChunkData(chunkView)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
c.chunkCache.SetChunk(chunkView.FileId, data)
|
||||
}
|
||||
return data, err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) doFetchFullChunkData(chunkView *ChunkView) ([]byte, error) {
|
||||
|
||||
glog.V(4).Infof("+ doFetchFullChunkData %s", chunkView.FileId)
|
||||
|
||||
data, err := fetchChunk(c.lookupFileId, chunkView.FileId, chunkView.CipherKey, chunkView.IsGzipped)
|
||||
|
||||
glog.V(4).Infof("- doFetchFullChunkData %s", chunkView.FileId)
|
||||
|
||||
return data, err
|
||||
|
||||
}
|
||||
|
||||
+26
-20
@@ -17,28 +17,28 @@ func StreamContent(masterClient *wdclient.MasterClient, w io.Writer, chunks []*f
|
||||
// fmt.Printf("start to stream content for chunks: %+v\n", chunks)
|
||||
chunkViews := ViewFromChunks(masterClient.LookupFileId, chunks, offset, size)
|
||||
|
||||
fileId2Url := make(map[string]string)
|
||||
fileId2Url := make(map[string][]string)
|
||||
|
||||
for _, chunkView := range chunkViews {
|
||||
|
||||
urlString, err := masterClient.LookupFileId(chunkView.FileId)
|
||||
urlStrings, err := masterClient.LookupFileId(chunkView.FileId)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("operation LookupFileId %s failed, err: %v", chunkView.FileId, err)
|
||||
return err
|
||||
}
|
||||
fileId2Url[chunkView.FileId] = urlString
|
||||
fileId2Url[chunkView.FileId] = urlStrings
|
||||
}
|
||||
|
||||
for _, chunkView := range chunkViews {
|
||||
|
||||
urlString := fileId2Url[chunkView.FileId]
|
||||
err := util.ReadUrlAsStream(urlString, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size), func(data []byte) {
|
||||
w.Write(data)
|
||||
})
|
||||
urlStrings := fileId2Url[chunkView.FileId]
|
||||
|
||||
data, err := retriedFetchChunkData(urlStrings, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size))
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read %s failed, err: %v", chunkView.FileId, err)
|
||||
return err
|
||||
}
|
||||
w.Write(data)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -51,25 +51,24 @@ func ReadAll(masterClient *wdclient.MasterClient, chunks []*filer_pb.FileChunk)
|
||||
|
||||
buffer := bytes.Buffer{}
|
||||
|
||||
lookupFileIdFn := func(fileId string) (targetUrl string, err error) {
|
||||
lookupFileIdFn := func(fileId string) (targetUrls []string, err error) {
|
||||
return masterClient.LookupFileId(fileId)
|
||||
}
|
||||
|
||||
chunkViews := ViewFromChunks(lookupFileIdFn, chunks, 0, math.MaxInt64)
|
||||
|
||||
for _, chunkView := range chunkViews {
|
||||
urlString, err := lookupFileIdFn(chunkView.FileId)
|
||||
urlStrings, err := lookupFileIdFn(chunkView.FileId)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("operation LookupFileId %s failed, err: %v", chunkView.FileId, err)
|
||||
return nil, err
|
||||
}
|
||||
err = util.ReadUrlAsStream(urlString, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size), func(data []byte) {
|
||||
buffer.Write(data)
|
||||
})
|
||||
|
||||
data, err := retriedFetchChunkData(urlStrings, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size))
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read %s failed, err: %v", chunkView.FileId, err)
|
||||
return nil, err
|
||||
}
|
||||
buffer.Write(data)
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
@@ -89,7 +88,7 @@ var _ = io.ReadSeeker(&ChunkStreamReader{})
|
||||
|
||||
func NewChunkStreamReaderFromFiler(masterClient *wdclient.MasterClient, chunks []*filer_pb.FileChunk) *ChunkStreamReader {
|
||||
|
||||
lookupFileIdFn := func(fileId string) (targetUrl string, err error) {
|
||||
lookupFileIdFn := func(fileId string) (targetUrl []string, err error) {
|
||||
return masterClient.LookupFileId(fileId)
|
||||
}
|
||||
|
||||
@@ -169,17 +168,24 @@ func (c *ChunkStreamReader) Seek(offset int64, whence int) (int64, error) {
|
||||
}
|
||||
|
||||
func (c *ChunkStreamReader) fetchChunkToBuffer(chunkView *ChunkView) error {
|
||||
urlString, err := c.lookupFileId(chunkView.FileId)
|
||||
urlStrings, err := c.lookupFileId(chunkView.FileId)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("operation LookupFileId %s failed, err: %v", chunkView.FileId, err)
|
||||
return err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
err = util.ReadUrlAsStream(urlString, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size), func(data []byte) {
|
||||
buffer.Write(data)
|
||||
})
|
||||
for _, urlString := range urlStrings {
|
||||
err = util.ReadUrlAsStream(urlString, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size), func(data []byte) {
|
||||
buffer.Write(data)
|
||||
})
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read %s failed, err: %v", chunkView.FileId, err)
|
||||
buffer.Reset()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read %s failed, err: %v", chunkView.FileId, err)
|
||||
return err
|
||||
}
|
||||
c.buffer = buffer.Bytes()
|
||||
|
||||
+5
-4
@@ -82,9 +82,9 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f
|
||||
func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) {
|
||||
attr.Inode = 1 // filer2.FullPath(dir.Path).AsInode()
|
||||
attr.Valid = time.Hour
|
||||
attr.Uid = dir.entry.Attributes.Uid
|
||||
attr.Gid = dir.entry.Attributes.Gid
|
||||
attr.Mode = os.FileMode(dir.entry.Attributes.FileMode)
|
||||
attr.Uid = dir.wfs.option.MountUid
|
||||
attr.Gid = dir.wfs.option.MountGid
|
||||
attr.Mode = dir.wfs.option.MountMode
|
||||
attr.Crtime = dir.wfs.option.MountCtime
|
||||
attr.Ctime = dir.wfs.option.MountCtime
|
||||
attr.Mtime = dir.wfs.option.MountMtime
|
||||
@@ -354,7 +354,8 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error {
|
||||
func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error {
|
||||
|
||||
glog.V(3).Infof("remove directory entry: %v", req)
|
||||
err := filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, true, false, false, false, []int32{dir.wfs.signature})
|
||||
ignoreRecursiveErr := true // ignore recursion error since the OS should manage it
|
||||
err := filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, true, false, ignoreRecursiveErr, false, []int32{dir.wfs.signature})
|
||||
if err != nil {
|
||||
glog.V(0).Infof("remove %s/%s: %v", dir.FullPath(), req.Name, err)
|
||||
if strings.Contains(err.Error(), "non-empty") {
|
||||
|
||||
@@ -79,7 +79,7 @@ func (fh *FileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp *fus
|
||||
|
||||
if err != nil {
|
||||
glog.Warningf("file handle read %s %d: %v", fh.f.fullpath(), totalRead, err)
|
||||
return nil
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
if totalRead > int64(len(buff)) {
|
||||
|
||||
@@ -37,6 +37,9 @@ type Option struct {
|
||||
EntryCacheTtl time.Duration
|
||||
Umask os.FileMode
|
||||
|
||||
MountUid uint32
|
||||
MountGid uint32
|
||||
MountMode os.FileMode
|
||||
MountCtime time.Time
|
||||
MountMtime time.Time
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ func (wfs *WFS) deleteFileIds(grpcDialOption grpc.DialOption, client filer_pb.Se
|
||||
}
|
||||
for _, loc := range locations.Locations {
|
||||
lr.Locations = append(lr.Locations, operation.Location{
|
||||
Url: wfs.AdjustedUrl(loc.Url),
|
||||
Url: wfs.AdjustedUrl(loc),
|
||||
PublicUrl: loc.PublicUrl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
@@ -26,15 +23,9 @@ func (wfs *WFS) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error) erro
|
||||
|
||||
}
|
||||
|
||||
func (wfs *WFS) AdjustedUrl(hostAndPort string) string {
|
||||
if !wfs.option.OutsideContainerClusterMode {
|
||||
return hostAndPort
|
||||
func (wfs *WFS) AdjustedUrl(location *filer_pb.Location) string {
|
||||
if wfs.option.OutsideContainerClusterMode {
|
||||
return location.PublicUrl
|
||||
}
|
||||
commaIndex := strings.Index(hostAndPort, ":")
|
||||
if commaIndex < 0 {
|
||||
return hostAndPort
|
||||
}
|
||||
filerCommaIndex := strings.Index(wfs.option.FilerGrpcAddress, ":")
|
||||
return fmt.Sprintf("%s:%s", wfs.option.FilerGrpcAddress[:filerCommaIndex], hostAndPort[commaIndex+1:])
|
||||
|
||||
return location.Url
|
||||
}
|
||||
|
||||
@@ -38,8 +38,12 @@ func (wfs *WFS) saveDataAsChunk(dir string) filer.SaveDataAsChunkFunctionType {
|
||||
return fmt.Errorf("assign volume failure %v: %v", request, resp.Error)
|
||||
}
|
||||
|
||||
fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth)
|
||||
host = wfs.AdjustedUrl(host)
|
||||
fileId, auth = resp.FileId, security.EncodedJwt(resp.Auth)
|
||||
loc := &filer_pb.Location{
|
||||
Url: resp.Url,
|
||||
PublicUrl: resp.PublicUrl,
|
||||
}
|
||||
host = wfs.AdjustedUrl(loc)
|
||||
collection, replication = resp.Collection, resp.Replication
|
||||
|
||||
return nil
|
||||
|
||||
@@ -108,6 +108,6 @@ func (broker *MessageBroker) WithFilerClient(fn func(filer_pb.SeaweedFilerClient
|
||||
|
||||
}
|
||||
|
||||
func (broker *MessageBroker) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (broker *MessageBroker) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ var (
|
||||
|
||||
type FilerClient interface {
|
||||
WithFilerClient(fn func(SeaweedFilerClient) error) error
|
||||
AdjustedUrl(hostAndPort string) string
|
||||
AdjustedUrl(location *Location) string
|
||||
}
|
||||
|
||||
func GetEntry(filerClient FilerClient, fullFilePath util.FullPath) (entry *Entry, err error) {
|
||||
|
||||
@@ -275,6 +275,7 @@ message GetMasterConfigurationResponse {
|
||||
uint32 metrics_interval_seconds = 2;
|
||||
repeated StorageBackend storage_backends = 3;
|
||||
string default_replication = 4;
|
||||
string leader = 5;
|
||||
}
|
||||
|
||||
message ListMasterClientsRequest {
|
||||
|
||||
+118
-109
@@ -2280,6 +2280,7 @@ type GetMasterConfigurationResponse struct {
|
||||
MetricsIntervalSeconds uint32 `protobuf:"varint,2,opt,name=metrics_interval_seconds,json=metricsIntervalSeconds,proto3" json:"metrics_interval_seconds,omitempty"`
|
||||
StorageBackends []*StorageBackend `protobuf:"bytes,3,rep,name=storage_backends,json=storageBackends,proto3" json:"storage_backends,omitempty"`
|
||||
DefaultReplication string `protobuf:"bytes,4,opt,name=default_replication,json=defaultReplication,proto3" json:"default_replication,omitempty"`
|
||||
Leader string `protobuf:"bytes,5,opt,name=leader,proto3" json:"leader,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetMasterConfigurationResponse) Reset() {
|
||||
@@ -2342,6 +2343,13 @@ func (x *GetMasterConfigurationResponse) GetDefaultReplication() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetMasterConfigurationResponse) GetLeader() string {
|
||||
if x != nil {
|
||||
return x.Leader
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListMasterClientsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -3205,7 +3213,7 @@ var file_master_proto_rawDesc = []byte{
|
||||
0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xfa, 0x01, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x92, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x65,
|
||||
0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
|
||||
@@ -3221,115 +3229,116 @@ var file_master_proto_rawDesc = []byte{
|
||||
0x6e, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x72,
|
||||
0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
|
||||
0x65, 0x22, 0x42, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25,
|
||||
0x0a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72,
|
||||
0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x16, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41,
|
||||
0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f,
|
||||
0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69,
|
||||
0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63,
|
||||
0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61,
|
||||
0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e,
|
||||
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e,
|
||||
0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25,
|
||||
0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75,
|
||||
0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf7, 0x08,
|
||||
0x0a, 0x07, 0x53, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74,
|
||||
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61,
|
||||
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0d, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e,
|
||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70,
|
||||
0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75,
|
||||
0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53,
|
||||
0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44,
|
||||
0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70,
|
||||
0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d,
|
||||
0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a,
|
||||
0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12,
|
||||
0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b,
|
||||
0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f,
|
||||
0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x28, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74,
|
||||
0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61,
|
||||
0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x22, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73,
|
||||
0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
|
||||
0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x24, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x3b, 0x0a, 0x18,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63,
|
||||
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x42, 0x0a, 0x19, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d,
|
||||
0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x8a, 0x01,
|
||||
0x0a, 0x16, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76,
|
||||
0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
|
||||
0x2c, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65,
|
||||
0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a,
|
||||
0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17, 0x4c, 0x65,
|
||||
0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f,
|
||||
0x73, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70,
|
||||
0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x0a, 0x6c,
|
||||
0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x52, 0x65,
|
||||
0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f,
|
||||
0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d,
|
||||
0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a,
|
||||
0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69,
|
||||
0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c,
|
||||
0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65,
|
||||
0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf7, 0x08, 0x0a, 0x07, 0x53, 0x65, 0x61, 0x77, 0x65, 0x65,
|
||||
0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65,
|
||||
0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48,
|
||||
0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0d,
|
||||
0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x2e,
|
||||
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
|
||||
0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d,
|
||||
0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12,
|
||||
0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12,
|
||||
0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b,
|
||||
0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b,
|
||||
0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, 0x2e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63,
|
||||
0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74,
|
||||
0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
||||
0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e,
|
||||
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f, 0x6c, 0x75,
|
||||
0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45,
|
||||
0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75,
|
||||
0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f,
|
||||
0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6f,
|
||||
0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47,
|
||||
0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
||||
0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x73, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62,
|
||||
0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a,
|
||||
0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52,
|
||||
0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
|
||||
0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68,
|
||||
0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f, 0x73, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x66,
|
||||
0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72,
|
||||
0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package repl_util
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/source"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func CopyFromChunkViews(chunkViews []*filer.ChunkView, filerSource *source.FilerSource, writeFunc func(data []byte) error) error {
|
||||
|
||||
for _, chunk := range chunkViews {
|
||||
|
||||
fileUrls, err := filerSource.LookupFileId(chunk.FileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var writeErr error
|
||||
|
||||
for _, fileUrl := range fileUrls {
|
||||
err = util.ReadUrlAsStream(fileUrl+"?readDeleted=true", nil, false, chunk.IsFullChunk(), chunk.Offset, int(chunk.Size), func(data []byte) {
|
||||
writeErr = writeFunc(data)
|
||||
})
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read from %s: %v", fileUrl, err)
|
||||
} else if writeErr != nil {
|
||||
glog.V(1).Infof("copy from %s: %v", fileUrl, writeErr)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/repl_util"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -107,25 +108,13 @@ func (g *AzureSink) CreateEntry(key string, entry *filer_pb.Entry, signatures []
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chunk := range chunkViews {
|
||||
|
||||
fileUrl, err := g.filerSource.LookupFileId(chunk.FileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var writeErr error
|
||||
readErr := util.ReadUrlAsStream(fileUrl+"?readDeleted=true", nil, false, chunk.IsFullChunk(), chunk.Offset, int(chunk.Size), func(data []byte) {
|
||||
_, writeErr = appendBlobURL.AppendBlock(context.Background(), bytes.NewReader(data), azblob.AppendBlobAccessConditions{}, nil)
|
||||
})
|
||||
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
writeFunc := func(data []byte) error {
|
||||
_, writeErr := appendBlobURL.AppendBlock(context.Background(), bytes.NewReader(data), azblob.AppendBlobAccessConditions{}, nil)
|
||||
return writeErr
|
||||
}
|
||||
|
||||
if err := repl_util.CopyFromChunkViews(chunkViews, g.filerSource, writeFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package B2Sink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/repl_util"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
@@ -95,31 +96,18 @@ func (g *B2Sink) CreateEntry(key string, entry *filer_pb.Entry, signatures []int
|
||||
targetObject := bucket.Object(key)
|
||||
writer := targetObject.NewWriter(context.Background())
|
||||
|
||||
for _, chunk := range chunkViews {
|
||||
|
||||
fileUrl, err := g.filerSource.LookupFileId(chunk.FileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var writeErr error
|
||||
readErr := util.ReadUrlAsStream(fileUrl+"?readDeleted=true", nil, false, chunk.IsFullChunk(), chunk.Offset, int(chunk.Size), func(data []byte) {
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
writeErr = err
|
||||
}
|
||||
})
|
||||
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
|
||||
writeFunc := func(data []byte) error {
|
||||
_, writeErr := writer.Write(data)
|
||||
return writeErr
|
||||
}
|
||||
|
||||
return writer.Close()
|
||||
defer writer.Close()
|
||||
|
||||
if err := repl_util.CopyFromChunkViews(chunkViews, g.filerSource, writeFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,6 @@ func (fs *FilerSink) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error)
|
||||
}, fs.grpcAddress, fs.grpcDialOption)
|
||||
|
||||
}
|
||||
func (fs *FilerSink) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (fs *FilerSink) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gcssink
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/repl_util"
|
||||
"os"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
@@ -93,25 +94,14 @@ func (g *GcsSink) CreateEntry(key string, entry *filer_pb.Entry, signatures []in
|
||||
chunkViews := filer.ViewFromChunks(g.filerSource.LookupFileId, entry.Chunks, 0, int64(totalSize))
|
||||
|
||||
wc := g.client.Bucket(g.bucket).Object(key).NewWriter(context.Background())
|
||||
defer wc.Close()
|
||||
|
||||
for _, chunk := range chunkViews {
|
||||
|
||||
fileUrl, err := g.filerSource.LookupFileId(chunk.FileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.ReadUrlAsStream(fileUrl+"?readDeleted=true", nil, false, chunk.IsFullChunk(), chunk.Offset, int(chunk.Size), func(data []byte) {
|
||||
wc.Write(data)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeFunc := func(data []byte) error {
|
||||
_, writeErr := wc.Write(data)
|
||||
return writeErr
|
||||
}
|
||||
|
||||
if err := wc.Close(); err != nil {
|
||||
if err := repl_util.CopyFromChunkViews(chunkViews, g.filerSource, writeFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -157,11 +157,18 @@ func (s3sink *S3Sink) uploadPartCopy(key, uploadId string, partId int64, copySou
|
||||
}
|
||||
|
||||
func (s3sink *S3Sink) buildReadSeeker(chunk *filer.ChunkView) (io.ReadSeeker, error) {
|
||||
fileUrl, err := s3sink.filerSource.LookupFileId(chunk.FileId)
|
||||
fileUrls, err := s3sink.filerSource.LookupFileId(chunk.FileId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, chunk.Size)
|
||||
util.ReadUrl(fileUrl, nil, false, false, chunk.Offset, int(chunk.Size), buf)
|
||||
for _, fileUrl := range fileUrls {
|
||||
_, err = util.ReadUrl(fileUrl, nil, false, false, chunk.Offset, int(chunk.Size), buf)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("read from %s: %v", fileUrl, err)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return bytes.NewReader(buf), nil
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (fs *FilerSource) DoInitialize(grpcAddress string, dir string) (err error)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FilerSource) LookupFileId(part string) (fileUrl string, err error) {
|
||||
func (fs *FilerSource) LookupFileId(part string) (fileUrls []string, err error) {
|
||||
|
||||
vid2Locations := make(map[string]*filer_pb.Locations)
|
||||
|
||||
@@ -64,29 +64,38 @@ func (fs *FilerSource) LookupFileId(part string) (fileUrl string, err error) {
|
||||
|
||||
if err != nil {
|
||||
glog.V(1).Infof("LookupFileId volume id %s: %v", vid, err)
|
||||
return "", fmt.Errorf("LookupFileId volume id %s: %v", vid, err)
|
||||
return nil, fmt.Errorf("LookupFileId volume id %s: %v", vid, err)
|
||||
}
|
||||
|
||||
locations := vid2Locations[vid]
|
||||
|
||||
if locations == nil || len(locations.Locations) == 0 {
|
||||
glog.V(1).Infof("LookupFileId locate volume id %s: %v", vid, err)
|
||||
return "", fmt.Errorf("LookupFileId locate volume id %s: %v", vid, err)
|
||||
return nil, fmt.Errorf("LookupFileId locate volume id %s: %v", vid, err)
|
||||
}
|
||||
|
||||
fileUrl = fmt.Sprintf("http://%s/%s", locations.Locations[0].Url, part)
|
||||
for _, loc := range locations.Locations {
|
||||
fileUrls = append(fileUrls, fmt.Sprintf("http://%s/%s", loc.Url, part))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *FilerSource) ReadPart(part string) (filename string, header http.Header, resp *http.Response, err error) {
|
||||
|
||||
fileUrl, err := fs.LookupFileId(part)
|
||||
fileUrls, err := fs.LookupFileId(part)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
filename, header, resp, err = util.DownloadFile(fileUrl)
|
||||
for _, fileUrl := range fileUrls {
|
||||
filename, header, resp, err = util.DownloadFile(fileUrl)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("fail to read from %s: %v", fileUrl, err)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return filename, header, resp, err
|
||||
}
|
||||
@@ -102,8 +111,8 @@ func (fs *FilerSource) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) erro
|
||||
|
||||
}
|
||||
|
||||
func (fs *FilerSource) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (fs *FilerSource) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
func volumeId(fileId string) string {
|
||||
|
||||
@@ -16,9 +16,11 @@ import (
|
||||
type Action string
|
||||
|
||||
const (
|
||||
ACTION_READ = "Read"
|
||||
ACTION_WRITE = "Write"
|
||||
ACTION_ADMIN = "Admin"
|
||||
ACTION_READ = "Read"
|
||||
ACTION_WRITE = "Write"
|
||||
ACTION_ADMIN = "Admin"
|
||||
ACTION_TAGGING = "Tagging"
|
||||
ACTION_LIST = "List"
|
||||
)
|
||||
|
||||
type Iam interface {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
)
|
||||
|
||||
const (
|
||||
S3TAG_PREFIX = "s3-"
|
||||
)
|
||||
|
||||
func (s3a *S3ApiServer) getTags(parentDirectoryPath string, entryName string) (tags map[string]string, err error) {
|
||||
|
||||
err = s3a.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Name: entryName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tags = make(map[string]string)
|
||||
for k, v := range resp.Entry.Extended {
|
||||
if strings.HasPrefix(k, S3TAG_PREFIX) {
|
||||
tags[k[len(S3TAG_PREFIX):]] = string(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) setTags(parentDirectoryPath string, entryName string, tags map[string]string) (err error) {
|
||||
|
||||
return s3a.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Name: entryName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, _ := range resp.Entry.Extended {
|
||||
if strings.HasPrefix(k, S3TAG_PREFIX) {
|
||||
delete(resp.Entry.Extended, k)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Entry.Extended == nil {
|
||||
resp.Entry.Extended = make(map[string][]byte)
|
||||
}
|
||||
for k, v := range tags {
|
||||
resp.Entry.Extended[S3TAG_PREFIX+k] = []byte(v)
|
||||
}
|
||||
|
||||
return filer_pb.UpdateEntry(client, &filer_pb.UpdateEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Entry: resp.Entry,
|
||||
IsFromOtherCluster: false,
|
||||
Signatures: nil,
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) rmTags(parentDirectoryPath string, entryName string) (err error) {
|
||||
|
||||
return s3a.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
resp, err := filer_pb.LookupEntry(client, &filer_pb.LookupDirectoryEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Name: entryName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasDeletion := false
|
||||
for k, _ := range resp.Entry.Extended {
|
||||
if strings.HasPrefix(k, S3TAG_PREFIX) {
|
||||
delete(resp.Entry.Extended, k)
|
||||
hasDeletion = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasDeletion {
|
||||
return nil
|
||||
}
|
||||
|
||||
return filer_pb.UpdateEntry(client, &filer_pb.UpdateEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Entry: resp.Entry,
|
||||
IsFromOtherCluster: false,
|
||||
Signatures: nil,
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@@ -50,8 +50,8 @@ func (s3a *S3ApiServer) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) err
|
||||
}, s3a.option.FilerGrpcAddress, s3a.option.GrpcDialOption)
|
||||
|
||||
}
|
||||
func (s3a *S3ApiServer) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (s3a *S3ApiServer) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
// If none of the http routes match respond with MethodNotAllowed
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetObjectTaggingHandler - GET object tagging
|
||||
// API reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTagging.html
|
||||
func (s3a *S3ApiServer) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
|
||||
tags, err := s3a.getTags(dir, name)
|
||||
if err != nil {
|
||||
if err == filer_pb.ErrNotFound {
|
||||
glog.Errorf("GetObjectTaggingHandler %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrNoSuchKey, r.URL)
|
||||
} else {
|
||||
glog.Errorf("GetObjectTaggingHandler %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseXML(w, encodeResponse(FromTags(tags)))
|
||||
|
||||
}
|
||||
|
||||
// PutObjectTaggingHandler Put object tagging
|
||||
// API reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectTagging.html
|
||||
func (s3a *S3ApiServer) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
|
||||
tagging := &Tagging{}
|
||||
input, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
glog.Errorf("PutObjectTaggingHandler read input %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||
return
|
||||
}
|
||||
if err = xml.Unmarshal(input, tagging); err != nil {
|
||||
glog.Errorf("PutObjectTaggingHandler Unmarshal %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrMalformedXML, r.URL)
|
||||
return
|
||||
}
|
||||
tags := tagging.ToTags()
|
||||
if len(tags) > 10 {
|
||||
glog.Errorf("PutObjectTaggingHandler tags %s: %d tags more than 10", r.URL, len(tags))
|
||||
writeErrorResponse(w, s3err.ErrInvalidTag, r.URL)
|
||||
return
|
||||
}
|
||||
for k, v := range tags {
|
||||
if len(k) > 128 {
|
||||
glog.Errorf("PutObjectTaggingHandler tags %s: tag key %s longer than 128", r.URL, k)
|
||||
writeErrorResponse(w, s3err.ErrInvalidTag, r.URL)
|
||||
return
|
||||
}
|
||||
if len(v) > 256 {
|
||||
glog.Errorf("PutObjectTaggingHandler tags %s: tag value %s longer than 256", r.URL, v)
|
||||
writeErrorResponse(w, s3err.ErrInvalidTag, r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s3a.setTags(dir, name, tagging.ToTags()); err != nil {
|
||||
if err == filer_pb.ErrNotFound {
|
||||
glog.Errorf("PutObjectTaggingHandler setTags %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrNoSuchKey, r.URL)
|
||||
} else {
|
||||
glog.Errorf("PutObjectTaggingHandler setTags %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
}
|
||||
|
||||
// DeleteObjectTaggingHandler Delete object tagging
|
||||
// API reference: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjectTagging.html
|
||||
func (s3a *S3ApiServer) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
bucket, object := getBucketAndObject(r)
|
||||
|
||||
target := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object))
|
||||
dir, name := target.DirAndName()
|
||||
|
||||
err := s3a.rmTags(dir, name)
|
||||
if err != nil {
|
||||
if err == filer_pb.ErrNotFound {
|
||||
glog.Errorf("DeleteObjectTaggingHandler %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrNoSuchKey, r.URL)
|
||||
} else {
|
||||
glog.Errorf("DeleteObjectTaggingHandler %s: %v", r.URL, err)
|
||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -64,9 +64,16 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||
// AbortMultipartUpload
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.AbortMultipartUploadHandler, ACTION_WRITE), "DELETE")).Queries("uploadId", "{uploadId:.*}")
|
||||
// ListObjectParts
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectPartsHandler, ACTION_WRITE), "GET")).Queries("uploadId", "{uploadId:.*}")
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectPartsHandler, ACTION_READ), "GET")).Queries("uploadId", "{uploadId:.*}")
|
||||
// ListMultipartUploads
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListMultipartUploadsHandler, ACTION_WRITE), "GET")).Queries("uploads", "")
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListMultipartUploadsHandler, ACTION_READ), "GET")).Queries("uploads", "")
|
||||
|
||||
// GetObjectTagging
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.GetObjectTaggingHandler, ACTION_READ), "GET")).Queries("tagging", "")
|
||||
// PutObjectTagging
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.PutObjectTaggingHandler, ACTION_TAGGING), "PUT")).Queries("tagging", "")
|
||||
// DeleteObjectTagging
|
||||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.DeleteObjectTaggingHandler, ACTION_TAGGING), "DELETE")).Queries("tagging", "")
|
||||
|
||||
// CopyObject
|
||||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(track(s3a.iam.Auth(s3a.CopyObjectHandler, ACTION_WRITE), "COPY"))
|
||||
@@ -81,11 +88,11 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||
bucket.Methods("DELETE").HandlerFunc(track(s3a.iam.Auth(s3a.DeleteBucketHandler, ACTION_WRITE), "DELETE"))
|
||||
|
||||
// ListObjectsV2
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV2Handler, ACTION_READ), "LIST")).Queries("list-type", "2")
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV2Handler, ACTION_LIST), "LIST")).Queries("list-type", "2")
|
||||
// GetObject, but directory listing is not supported
|
||||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.GetObjectHandler, ACTION_READ), "GET"))
|
||||
// ListObjectsV1 (Legacy)
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV1Handler, ACTION_READ), "LIST"))
|
||||
bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.ListObjectsV1Handler, ACTION_LIST), "LIST"))
|
||||
|
||||
// PostPolicy
|
||||
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(track(s3a.iam.Auth(s3a.PostPolicyBucketHandler, ACTION_WRITE), "POST"))
|
||||
@@ -112,7 +119,7 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||
}
|
||||
|
||||
// ListBuckets
|
||||
apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_READ), "LIST"))
|
||||
apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST"))
|
||||
|
||||
// NotFound
|
||||
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
|
||||
@@ -61,6 +61,7 @@ const (
|
||||
ErrInternalError
|
||||
ErrInvalidCopyDest
|
||||
ErrInvalidCopySource
|
||||
ErrInvalidTag
|
||||
ErrAuthHeaderEmpty
|
||||
ErrSignatureVersionNotSupported
|
||||
ErrMalformedPOSTRequest
|
||||
@@ -188,6 +189,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Copy Source must mention the source bucket and key: sourcebucket/sourcekey.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidTag: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The Tag value you have provided is invalid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedXML: {
|
||||
Code: "MalformedXML",
|
||||
Description: "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Key string `xml:"Key"`
|
||||
Value string `xml:"Value"`
|
||||
}
|
||||
|
||||
type TagSet struct {
|
||||
Tag []Tag `xml:"Tag"`
|
||||
}
|
||||
|
||||
type Tagging struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Tagging"`
|
||||
TagSet TagSet `xml:"TagSet"`
|
||||
}
|
||||
|
||||
func (t *Tagging) ToTags() map[string]string {
|
||||
output := make(map[string]string)
|
||||
for _, tag := range t.TagSet.Tag {
|
||||
output[tag.Key] = tag.Value
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func FromTags(tags map[string]string) (t *Tagging) {
|
||||
t = &Tagging{}
|
||||
for k, v := range tags {
|
||||
t.TagSet.Tag = append(t.TagSet.Tag, Tag{
|
||||
Key: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXMLUnmarshall(t *testing.T) {
|
||||
|
||||
input := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<TagSet>
|
||||
<Tag>
|
||||
<Key>key1</Key>
|
||||
<Value>value1</Value>
|
||||
</Tag>
|
||||
</TagSet>
|
||||
</Tagging>
|
||||
`
|
||||
|
||||
tags := &Tagging{}
|
||||
|
||||
xml.Unmarshal([]byte(input), tags)
|
||||
|
||||
assert.Equal(t, len(tags.TagSet.Tag), 1)
|
||||
assert.Equal(t, tags.TagSet.Tag[0].Key, "key1")
|
||||
assert.Equal(t, tags.TagSet.Tag[0].Value, "value1")
|
||||
|
||||
}
|
||||
|
||||
func TestXMLMarshall(t *testing.T) {
|
||||
tags := &Tagging{
|
||||
TagSet: TagSet{
|
||||
[]Tag{
|
||||
{
|
||||
Key: "key1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual := string(encodeResponse(tags))
|
||||
|
||||
expected := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><TagSet><Tag><Key>key1</Key><Value>value1</Value></Tag></TagSet></Tagging>`
|
||||
assert.Equal(t, expected, actual)
|
||||
|
||||
}
|
||||
@@ -32,12 +32,12 @@ func (fs *FilerServer) LookupDirectoryEntry(ctx context.Context, req *filer_pb.L
|
||||
|
||||
return &filer_pb.LookupDirectoryEntryResponse{
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: req.Name,
|
||||
IsDirectory: entry.IsDirectory(),
|
||||
Attributes: filer.EntryAttributeToPb(entry),
|
||||
Chunks: entry.Chunks,
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
Name: req.Name,
|
||||
IsDirectory: entry.IsDirectory(),
|
||||
Attributes: filer.EntryAttributeToPb(entry),
|
||||
Chunks: entry.Chunks,
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
HardLinkCounter: entry.HardLinkCounter,
|
||||
},
|
||||
}, nil
|
||||
@@ -77,12 +77,12 @@ func (fs *FilerServer) ListEntries(req *filer_pb.ListEntriesRequest, stream file
|
||||
|
||||
if err := stream.Send(&filer_pb.ListEntriesResponse{
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: entry.Name(),
|
||||
IsDirectory: entry.IsDirectory(),
|
||||
Chunks: entry.Chunks,
|
||||
Attributes: filer.EntryAttributeToPb(entry),
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
Name: entry.Name(),
|
||||
IsDirectory: entry.IsDirectory(),
|
||||
Chunks: entry.Chunks,
|
||||
Attributes: filer.EntryAttributeToPb(entry),
|
||||
Extended: entry.Extended,
|
||||
HardLinkId: entry.HardLinkId,
|
||||
HardLinkCounter: entry.HardLinkCounter,
|
||||
},
|
||||
}); err != nil {
|
||||
@@ -135,16 +135,19 @@ func (fs *FilerServer) LookupVolume(ctx context.Context, req *filer_pb.LookupVol
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (fs *FilerServer) lookupFileId(fileId string) (targetUrl string, err error) {
|
||||
func (fs *FilerServer) lookupFileId(fileId string) (targetUrls []string, err error) {
|
||||
fid, err := needle.ParseFileIdFromString(fileId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
locations, found := fs.filer.MasterClient.GetLocations(uint32(fid.VolumeId))
|
||||
if !found || len(locations) == 0 {
|
||||
return "", fmt.Errorf("not found volume %d in %s", fid.VolumeId, fileId)
|
||||
return nil, fmt.Errorf("not found volume %d in %s", fid.VolumeId, fileId)
|
||||
}
|
||||
return fmt.Sprintf("http://%s/%s", locations[0].Url, fileId), nil
|
||||
for _, loc := range locations {
|
||||
targetUrls = append(targetUrls, fmt.Sprintf("http://%s/%s", loc.Url, fileId))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *FilerServer) CreateEntry(ctx context.Context, req *filer_pb.CreateEntryRequest) (resp *filer_pb.CreateEntryResponse, err error) {
|
||||
@@ -159,11 +162,11 @@ func (fs *FilerServer) CreateEntry(ctx context.Context, req *filer_pb.CreateEntr
|
||||
}
|
||||
|
||||
createErr := fs.filer.CreateEntry(ctx, &filer.Entry{
|
||||
FullPath: util.JoinPath(req.Directory, req.Entry.Name),
|
||||
Attr: filer.PbToEntryAttribute(req.Entry.Attributes),
|
||||
Chunks: chunks,
|
||||
Extended: req.Entry.Extended,
|
||||
HardLinkId: filer.HardLinkId(req.Entry.HardLinkId),
|
||||
FullPath: util.JoinPath(req.Directory, req.Entry.Name),
|
||||
Attr: filer.PbToEntryAttribute(req.Entry.Attributes),
|
||||
Chunks: chunks,
|
||||
Extended: req.Entry.Extended,
|
||||
HardLinkId: filer.HardLinkId(req.Entry.HardLinkId),
|
||||
HardLinkCounter: req.Entry.HardLinkCounter,
|
||||
}, req.OExcl, req.IsFromOtherCluster, req.Signatures)
|
||||
|
||||
@@ -193,11 +196,11 @@ func (fs *FilerServer) UpdateEntry(ctx context.Context, req *filer_pb.UpdateEntr
|
||||
}
|
||||
|
||||
newEntry := &filer.Entry{
|
||||
FullPath: util.JoinPath(req.Directory, req.Entry.Name),
|
||||
Attr: entry.Attr,
|
||||
Extended: req.Entry.Extended,
|
||||
Chunks: chunks,
|
||||
HardLinkId: filer.HardLinkId(req.Entry.HardLinkId),
|
||||
FullPath: util.JoinPath(req.Directory, req.Entry.Name),
|
||||
Attr: entry.Attr,
|
||||
Extended: req.Entry.Extended,
|
||||
Chunks: chunks,
|
||||
HardLinkId: filer.HardLinkId(req.Entry.HardLinkId),
|
||||
HardLinkCounter: req.Entry.HardLinkCounter,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package weed_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -65,21 +68,30 @@ func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Reque
|
||||
lastFileName,
|
||||
shouldDisplayLoadMore,
|
||||
})
|
||||
} else {
|
||||
ui.StatusTpl.Execute(w, struct {
|
||||
Path string
|
||||
Breadcrumbs []ui.Breadcrumb
|
||||
Entries interface{}
|
||||
Limit int
|
||||
LastFileName string
|
||||
ShouldDisplayLoadMore bool
|
||||
}{
|
||||
path,
|
||||
ui.ToBreadcrumb(path),
|
||||
entries,
|
||||
limit,
|
||||
lastFileName,
|
||||
shouldDisplayLoadMore,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var qrImageString string
|
||||
img, err := qrcode.Encode(fmt.Sprintf("http://%s:%d%s", fs.option.Host, fs.option.Port, r.URL.Path), qrcode.Medium, 128)
|
||||
if err == nil {
|
||||
qrImageString = base64.StdEncoding.EncodeToString(img)
|
||||
}
|
||||
|
||||
ui.StatusTpl.Execute(w, struct {
|
||||
Path string
|
||||
Breadcrumbs []ui.Breadcrumb
|
||||
Entries interface{}
|
||||
Limit int
|
||||
LastFileName string
|
||||
ShouldDisplayLoadMore bool
|
||||
QrImage string
|
||||
}{
|
||||
path,
|
||||
ui.ToBreadcrumb(path),
|
||||
entries,
|
||||
limit,
|
||||
lastFileName,
|
||||
shouldDisplayLoadMore,
|
||||
qrImageString,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ var funcMap = template.FuncMap{
|
||||
var StatusTpl = template.Must(template.New("status").Funcs(funcMap).Parse(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SeaweedFS Filer</title>
|
||||
<link rel="stylesheet" href="/seaweedfsstatic/bootstrap/3.3.1/css/bootstrap.min.css">
|
||||
<title>SeaweedFS Filer</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/seaweedfsstatic/bootstrap/3.3.1/css/bootstrap.min.css">
|
||||
<style>
|
||||
body { padding-bottom: 70px; }
|
||||
#drop-area {
|
||||
border: 1px transparent;
|
||||
}
|
||||
@@ -46,6 +48,11 @@ var StatusTpl = template.Must(template.New("status").Funcs(funcMap).Parse(`<!DOC
|
||||
#fileElem {
|
||||
display: none;
|
||||
}
|
||||
.qrImage {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -116,6 +123,14 @@ var StatusTpl = template.Must(template.New("status").Funcs(funcMap).Parse(`<!DOC
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="navbar navbar-fixed-bottom">
|
||||
<img src="data:image/png;base64,{{.QrImage}}" class="qrImage" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -3,6 +3,7 @@ package weed_server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/backend"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -302,3 +303,19 @@ func (ms *MasterServer) ListMasterClients(ctx context.Context, req *master_pb.Li
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (ms *MasterServer) GetMasterConfiguration(ctx context.Context, req *master_pb.GetMasterConfigurationRequest) (*master_pb.GetMasterConfigurationResponse, error) {
|
||||
|
||||
// tell the volume servers about the leader
|
||||
leader, _ := ms.Topo.Leader()
|
||||
|
||||
resp := &master_pb.GetMasterConfigurationResponse{
|
||||
MetricsAddress: ms.option.MetricsAddress,
|
||||
MetricsIntervalSeconds: uint32(ms.option.MetricsIntervalSec),
|
||||
StorageBackends: backend.ToPbStorageBackends(),
|
||||
DefaultReplication: ms.option.DefaultReplicaPlacement,
|
||||
Leader: leader,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package weed_server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/backend"
|
||||
|
||||
"github.com/chrislusf/raft"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/master_pb"
|
||||
@@ -179,15 +177,3 @@ func (ms *MasterServer) LookupEcVolume(ctx context.Context, req *master_pb.Looku
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (ms *MasterServer) GetMasterConfiguration(ctx context.Context, req *master_pb.GetMasterConfigurationRequest) (*master_pb.GetMasterConfigurationResponse, error) {
|
||||
|
||||
resp := &master_pb.GetMasterConfigurationResponse{
|
||||
MetricsAddress: ms.option.MetricsAddress,
|
||||
MetricsIntervalSeconds: uint32(ms.option.MetricsIntervalSec),
|
||||
StorageBackends: backend.ToPbStorageBackends(),
|
||||
DefaultReplication: ms.option.DefaultReplicaPlacement,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -138,14 +138,11 @@ func NewMasterServer(r *mux.Router, option *MasterOption, peers []string) *Maste
|
||||
func (ms *MasterServer) SetRaftServer(raftServer *RaftServer) {
|
||||
ms.Topo.RaftServer = raftServer.raftServer
|
||||
ms.Topo.RaftServer.AddEventListener(raft.LeaderChangeEventType, func(e raft.Event) {
|
||||
glog.V(0).Infof("event: %+v", e)
|
||||
glog.V(0).Infof("leader change event: %+v => %+v", e.PrevValue(), e.Value())
|
||||
if ms.Topo.RaftServer.Leader() != "" {
|
||||
glog.V(0).Infoln("[", ms.Topo.RaftServer.Name(), "]", ms.Topo.RaftServer.Leader(), "becomes leader.")
|
||||
}
|
||||
})
|
||||
ms.Topo.RaftServer.AddEventListener(raft.StateChangeEventType, func(e raft.Event) {
|
||||
glog.V(0).Infof("state change: %+v", e)
|
||||
})
|
||||
if ms.Topo.IsLeader() {
|
||||
glog.V(0).Infoln("[", ms.Topo.RaftServer.Name(), "]", "I am the leader!")
|
||||
} else {
|
||||
|
||||
+79
-54
@@ -2,10 +2,8 @@ package weed_server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -28,7 +26,31 @@ type RaftServer struct {
|
||||
*raft.GrpcServer
|
||||
}
|
||||
|
||||
func NewRaftServer(grpcDialOption grpc.DialOption, peers []string, serverAddr, dataDir string, topo *topology.Topology, pulseSeconds int) *RaftServer {
|
||||
type StateMachine struct {
|
||||
raft.StateMachine
|
||||
topo *topology.Topology
|
||||
}
|
||||
|
||||
func (s StateMachine) Save() ([]byte, error) {
|
||||
state := topology.MaxVolumeIdCommand{
|
||||
MaxVolumeId: s.topo.GetMaxVolumeId(),
|
||||
}
|
||||
glog.V(1).Infof("Save raft state %+v", state)
|
||||
return json.Marshal(state)
|
||||
}
|
||||
|
||||
func (s StateMachine) Recovery(data []byte) error {
|
||||
state := topology.MaxVolumeIdCommand{}
|
||||
err := json.Unmarshal(data, &state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(1).Infof("Recovery raft state %+v", state)
|
||||
s.topo.UpAdjustMaxVolumeId(state.MaxVolumeId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRaftServer(grpcDialOption grpc.DialOption, peers []string, serverAddr, dataDir string, topo *topology.Topology, pulseSeconds int, raftResumeState bool) (*RaftServer, error) {
|
||||
s := &RaftServer{
|
||||
peers: peers,
|
||||
serverAddr: serverAddr,
|
||||
@@ -46,48 +68,66 @@ func NewRaftServer(grpcDialOption grpc.DialOption, peers []string, serverAddr, d
|
||||
transporter := raft.NewGrpcTransporter(grpcDialOption)
|
||||
glog.V(0).Infof("Starting RaftServer with %v", serverAddr)
|
||||
|
||||
// always clear previous metadata
|
||||
os.RemoveAll(path.Join(s.dataDir, "conf"))
|
||||
os.RemoveAll(path.Join(s.dataDir, "log"))
|
||||
os.RemoveAll(path.Join(s.dataDir, "snapshot"))
|
||||
// Clear old cluster configurations if peers are changed
|
||||
if oldPeers, changed := isPeersChanged(s.dataDir, serverAddr, s.peers); changed {
|
||||
glog.V(0).Infof("Peers Change: %v => %v", oldPeers, s.peers)
|
||||
if !raftResumeState {
|
||||
// always clear previous metadata
|
||||
os.RemoveAll(path.Join(s.dataDir, "conf"))
|
||||
os.RemoveAll(path.Join(s.dataDir, "log"))
|
||||
os.RemoveAll(path.Join(s.dataDir, "snapshot"))
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(s.dataDir, "snapshot"), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.raftServer, err = raft.NewServer(s.serverAddr, s.dataDir, transporter, nil, topo, "")
|
||||
stateMachine := StateMachine{topo: topo}
|
||||
s.raftServer, err = raft.NewServer(s.serverAddr, s.dataDir, transporter, stateMachine, topo, "")
|
||||
if err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
s.raftServer.SetHeartbeatInterval(500 * time.Millisecond)
|
||||
s.raftServer.SetElectionTimeout(time.Duration(pulseSeconds) * 500 * time.Millisecond)
|
||||
s.raftServer.Start()
|
||||
if err := s.raftServer.LoadSnapshot(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.raftServer.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, peer := range s.peers {
|
||||
s.raftServer.AddPeer(peer, pb.ServerToGrpcAddress(peer))
|
||||
if err := s.raftServer.AddPeer(peer, pb.ServerToGrpcAddress(peer)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted peers
|
||||
for existsPeerName := range s.raftServer.Peers() {
|
||||
exists, existingPeer := false, ""
|
||||
for _, peer := range s.peers {
|
||||
if pb.ServerToGrpcAddress(peer) == existsPeerName {
|
||||
exists, existingPeer = true, peer
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
if err := s.raftServer.RemovePeer(existsPeerName); err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return nil, err
|
||||
} else {
|
||||
glog.V(0).Infof("removing old peer %s", existingPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.GrpcServer = raft.NewGrpcServer(s.raftServer)
|
||||
|
||||
if s.raftServer.IsLogEmpty() && isTheFirstOne(serverAddr, s.peers) {
|
||||
// Initialize the server by joining itself.
|
||||
glog.V(0).Infoln("Initializing new cluster")
|
||||
|
||||
_, err := s.raftServer.Do(&raft.DefaultJoinCommand{
|
||||
Name: s.raftServer.Name(),
|
||||
ConnectionString: pb.ServerToGrpcAddress(s.serverAddr),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
glog.V(0).Infoln(err)
|
||||
return nil
|
||||
}
|
||||
// s.DoJoinCommand()
|
||||
}
|
||||
|
||||
glog.V(0).Infof("current cluster leader: %v", s.raftServer.Leader())
|
||||
|
||||
return s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *RaftServer) Peers() (members []string) {
|
||||
@@ -100,34 +140,6 @@ func (s *RaftServer) Peers() (members []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func isPeersChanged(dir string, self string, peers []string) (oldPeers []string, changed bool) {
|
||||
confPath := path.Join(dir, "conf")
|
||||
// open conf file
|
||||
b, err := ioutil.ReadFile(confPath)
|
||||
if err != nil {
|
||||
return oldPeers, true
|
||||
}
|
||||
conf := &raft.Config{}
|
||||
if err = json.Unmarshal(b, conf); err != nil {
|
||||
return oldPeers, true
|
||||
}
|
||||
|
||||
for _, p := range conf.Peers {
|
||||
oldPeers = append(oldPeers, p.Name)
|
||||
}
|
||||
oldPeers = append(oldPeers, self)
|
||||
|
||||
if len(peers) == 0 && len(oldPeers) <= 1 {
|
||||
return oldPeers, false
|
||||
}
|
||||
|
||||
sort.Strings(peers)
|
||||
sort.Strings(oldPeers)
|
||||
|
||||
return oldPeers, !reflect.DeepEqual(peers, oldPeers)
|
||||
|
||||
}
|
||||
|
||||
func isTheFirstOne(self string, peers []string) bool {
|
||||
sort.Strings(peers)
|
||||
if len(peers) <= 0 {
|
||||
@@ -135,3 +147,16 @@ func isTheFirstOne(self string, peers []string) bool {
|
||||
}
|
||||
return self == peers[0]
|
||||
}
|
||||
|
||||
func (s *RaftServer) DoJoinCommand() {
|
||||
|
||||
glog.V(0).Infoln("Initializing new cluster")
|
||||
|
||||
if _, err := s.raftServer.Do(&raft.DefaultJoinCommand{
|
||||
Name: s.raftServer.Name(),
|
||||
ConnectionString: pb.ServerToGrpcAddress(s.serverAddr),
|
||||
}); err != nil {
|
||||
glog.Errorf("fail to send join command: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ClusterStatusResult struct {
|
||||
IsLeader bool `json:"IsLeader,omitempty"`
|
||||
Leader string `json:"Leader,omitempty"`
|
||||
Peers []string `json:"Peers,omitempty"`
|
||||
IsLeader bool `json:"IsLeader,omitempty"`
|
||||
Leader string `json:"Leader,omitempty"`
|
||||
Peers []string `json:"Peers,omitempty"`
|
||||
MaxVolumeId needle.VolumeId `json:"MaxVolumeId,omitempty"`
|
||||
}
|
||||
|
||||
func (s *RaftServer) StatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ret := ClusterStatusResult{
|
||||
IsLeader: s.topo.IsLeader(),
|
||||
Peers: s.Peers(),
|
||||
IsLeader: s.topo.IsLeader(),
|
||||
Peers: s.Peers(),
|
||||
MaxVolumeId: s.topo.GetMaxVolumeId(),
|
||||
}
|
||||
|
||||
if leader, e := s.topo.Leader(); e == nil {
|
||||
ret.Leader = leader
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ func (vs *VolumeServer) StopHeartbeat() (isAlreadyStopping bool) {
|
||||
return true
|
||||
}
|
||||
vs.isHeartbeating = false
|
||||
vs.stopChan <- true
|
||||
close(vs.stopChan)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request)
|
||||
} else if hasEcVolume {
|
||||
count, err = vs.store.ReadEcShardNeedle(volumeId, n)
|
||||
}
|
||||
if err != nil && err != storage.ErrorDeleted && r.FormValue("type") != "replicate" && hasVolume {
|
||||
glog.V(4).Infof("read needle: %v", err)
|
||||
// start to fix it from other replicas, if not deleted and hasVolume and is not a replicated request
|
||||
}
|
||||
// glog.V(4).Infoln("read bytes", count, "error", err)
|
||||
if err != nil || count < 0 {
|
||||
glog.V(3).Infof("read %s isNormalVolume %v error: %v", r.URL.Path, hasVolume, err)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/chrislusf/seaweedfs/weed/stats"
|
||||
"github.com/chrislusf/seaweedfs/weed/storage/needle"
|
||||
"github.com/chrislusf/seaweedfs/weed/topology"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -67,7 +68,7 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ret.Name = string(reqNeedle.Name)
|
||||
}
|
||||
ret.Size = uint32(originalSize)
|
||||
ret.ETag = reqNeedle.Etag()
|
||||
ret.ETag = fmt.Sprintf("%x", util.Base64Md5ToBytes(contentMd5))
|
||||
ret.Mime = string(reqNeedle.Mime)
|
||||
setEtag(w, ret.ETag)
|
||||
w.Header().Set("Content-MD5", contentMd5)
|
||||
|
||||
@@ -118,8 +118,8 @@ func (fs *WebDavFileSystem) WithFilerClient(fn func(filer_pb.SeaweedFilerClient)
|
||||
}, fs.option.FilerGrpcAddress, fs.option.GrpcDialOption)
|
||||
|
||||
}
|
||||
func (fs *WebDavFileSystem) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (fs *WebDavFileSystem) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
func clearName(name string) (string, error) {
|
||||
|
||||
@@ -102,8 +102,8 @@ func (ce *CommandEnv) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error
|
||||
|
||||
}
|
||||
|
||||
func (ce *CommandEnv) AdjustedUrl(hostAndPort string) string {
|
||||
return hostAndPort
|
||||
func (ce *CommandEnv) AdjustedUrl(location *filer_pb.Location) string {
|
||||
return location.Url
|
||||
}
|
||||
|
||||
func parseFilerUrl(entryPath string) (filerServer string, filerPort int64, path string, err error) {
|
||||
|
||||
@@ -16,7 +16,7 @@ func CreateVolumeFile(fileName string, preallocate int64, memoryMapSizeMB uint32
|
||||
}
|
||||
if preallocate != 0 {
|
||||
syscall.Fallocate(int(file.Fd()), 1, 0, preallocate)
|
||||
glog.V(0).Infof("Preallocated %d bytes disk space for %s", preallocate, fileName)
|
||||
glog.V(1).Infof("Preallocated %d bytes disk space for %s", preallocate, fileName)
|
||||
}
|
||||
return NewDiskFile(file), nil
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ func (s *Store) ReadEcShardNeedle(vid needle.VolumeId, n *needle.Needle) (int, e
|
||||
return 0, fmt.Errorf("locate in local ec volume: %v", err)
|
||||
}
|
||||
if size.IsDeleted() {
|
||||
return 0, fmt.Errorf("entry %s is deleted", n.Id)
|
||||
return 0, ErrorDeleted
|
||||
}
|
||||
|
||||
glog.V(3).Infof("read ec volume %d offset %d size %d intervals:%+v", vid, offset.ToAcutalOffset(), size, intervals)
|
||||
@@ -141,7 +141,7 @@ func (s *Store) ReadEcShardNeedle(vid needle.VolumeId, n *needle.Needle) (int, e
|
||||
return 0, fmt.Errorf("ReadEcShardIntervals: %v", err)
|
||||
}
|
||||
if isDeleted {
|
||||
return 0, fmt.Errorf("ec entry %s is deleted", n.Id)
|
||||
return 0, ErrorDeleted
|
||||
}
|
||||
|
||||
err = n.ReadBytes(bytes, offset.ToAcutalOffset(), size, localEcVolume.Version)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
)
|
||||
|
||||
var ErrorNotFound = errors.New("not found")
|
||||
var ErrorDeleted = errors.New("already deleted")
|
||||
|
||||
// isFileUnchanged checks whether this needle to write is same as last one.
|
||||
// It requires serialized access in the same volume.
|
||||
@@ -266,7 +267,7 @@ func (v *Volume) readNeedle(n *needle.Needle, readOption *ReadOption) (int, erro
|
||||
glog.V(3).Infof("reading deleted %s", n.String())
|
||||
readSize = -readSize
|
||||
} else {
|
||||
return -1, errors.New("already deleted")
|
||||
return -1, ErrorDeleted
|
||||
}
|
||||
}
|
||||
if readSize == 0 {
|
||||
|
||||
@@ -76,7 +76,7 @@ func (c *TieredChunkCache) doGetChunk(fileId string, minSize uint64) (data []byt
|
||||
return data
|
||||
}
|
||||
}
|
||||
if minSize <= c.onDiskCacheSizeLimit2 {
|
||||
{
|
||||
data = c.diskCaches[2].getChunk(fid.Key)
|
||||
if len(data) >= int(minSize) {
|
||||
return data
|
||||
@@ -115,7 +115,7 @@ func (c *TieredChunkCache) doSetChunk(fileId string, data []byte) {
|
||||
c.diskCaches[0].setChunk(fid.Key, data)
|
||||
} else if len(data) <= int(c.onDiskCacheSizeLimit1) {
|
||||
c.diskCaches[1].setChunk(fid.Key, data)
|
||||
} else if len(data) <= int(c.onDiskCacheSizeLimit2) {
|
||||
} else {
|
||||
c.diskCaches[2].setChunk(fid.Key, data)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 02)
|
||||
VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 04)
|
||||
COMMIT = ""
|
||||
)
|
||||
|
||||
|
||||
@@ -52,6 +52,32 @@ func (mc *MasterClient) KeepConnectedToMaster() {
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MasterClient) FindLeader(myMasterAddress string) (leader string) {
|
||||
for _, master := range mc.masters {
|
||||
if master == myMasterAddress {
|
||||
continue
|
||||
}
|
||||
if grpcErr := pb.WithMasterClient(master, mc.grpcDialOption, func(client master_pb.SeaweedClient) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Millisecond)
|
||||
defer cancel()
|
||||
resp, err := client.GetMasterConfiguration(ctx, &master_pb.GetMasterConfigurationRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
leader = resp.Leader
|
||||
return nil
|
||||
}); grpcErr != nil {
|
||||
glog.V(0).Infof("connect to %s: %v", master, grpcErr)
|
||||
}
|
||||
if leader != "" {
|
||||
glog.V(0).Infof("existing leader is %s", leader)
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(0).Infof("No existing leader found!")
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *MasterClient) tryAllMasters() {
|
||||
nextHintedLeader := ""
|
||||
for _, master := range mc.masters {
|
||||
@@ -75,7 +101,7 @@ func (mc *MasterClient) tryConnectToMaster(master string) (nextHintedLeader stri
|
||||
|
||||
stream, err := client.KeepConnected(ctx)
|
||||
if err != nil {
|
||||
glog.V(0).Infof("%s masterClient failed to keep connected to %s: %v", mc.clientType, master, err)
|
||||
glog.V(1).Infof("%s masterClient failed to keep connected to %s: %v", mc.clientType, master, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -118,7 +144,7 @@ func (mc *MasterClient) tryConnectToMaster(master string) (nextHintedLeader stri
|
||||
|
||||
})
|
||||
if gprcErr != nil {
|
||||
glog.V(0).Infof("%s masterClient failed to connect with master %v: %v", mc.clientType, master, gprcErr)
|
||||
glog.V(1).Infof("%s masterClient failed to connect with master %v: %v", mc.clientType, master, gprcErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+17
-36
@@ -44,38 +44,36 @@ func (vc *vidMap) getLocationIndex(length int) (int, error) {
|
||||
return int(atomic.AddInt32(&vc.cursor, 1)) % length, nil
|
||||
}
|
||||
|
||||
func (vc *vidMap) LookupVolumeServerUrl(vid string) (serverUrl string, err error) {
|
||||
func (vc *vidMap) LookupVolumeServerUrl(vid string) (serverUrls []string, err error) {
|
||||
id, err := strconv.Atoi(vid)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("Unknown volume id %s", vid)
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vc.GetRandomLocation(uint32(id))
|
||||
locations, found := vc.GetLocations(uint32(id))
|
||||
if !found {
|
||||
return nil, fmt.Errorf("volume %d not found", id)
|
||||
}
|
||||
for _, loc := range locations {
|
||||
serverUrls = append(serverUrls, loc.Url)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (vc *vidMap) LookupFileId(fileId string) (fullUrl string, err error) {
|
||||
func (vc *vidMap) LookupFileId(fileId string) (fullUrls []string, err error) {
|
||||
parts := strings.Split(fileId, ",")
|
||||
if len(parts) != 2 {
|
||||
return "", errors.New("Invalid fileId " + fileId)
|
||||
return nil, errors.New("Invalid fileId " + fileId)
|
||||
}
|
||||
serverUrl, lookupError := vc.LookupVolumeServerUrl(parts[0])
|
||||
serverUrls, lookupError := vc.LookupVolumeServerUrl(parts[0])
|
||||
if lookupError != nil {
|
||||
return "", lookupError
|
||||
return nil, lookupError
|
||||
}
|
||||
return "http://" + serverUrl + "/" + fileId, nil
|
||||
}
|
||||
|
||||
func (vc *vidMap) LookupVolumeServer(fileId string) (volumeServer string, err error) {
|
||||
parts := strings.Split(fileId, ",")
|
||||
if len(parts) != 2 {
|
||||
return "", errors.New("Invalid fileId " + fileId)
|
||||
for _, serverUrl := range serverUrls {
|
||||
fullUrls = append(fullUrls, "http://"+serverUrl+"/"+fileId)
|
||||
}
|
||||
serverUrl, lookupError := vc.LookupVolumeServerUrl(parts[0])
|
||||
if lookupError != nil {
|
||||
return "", lookupError
|
||||
}
|
||||
return serverUrl, nil
|
||||
return
|
||||
}
|
||||
|
||||
func (vc *vidMap) GetVidLocations(vid string) (locations []Location, err error) {
|
||||
@@ -99,23 +97,6 @@ func (vc *vidMap) GetLocations(vid uint32) (locations []Location, found bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (vc *vidMap) GetRandomLocation(vid uint32) (serverUrl string, err error) {
|
||||
vc.RLock()
|
||||
defer vc.RUnlock()
|
||||
|
||||
locations := vc.vid2Locations[vid]
|
||||
if len(locations) == 0 {
|
||||
return "", fmt.Errorf("volume %d not found", vid)
|
||||
}
|
||||
|
||||
index, err := vc.getLocationIndex(len(locations))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("volume %d: %v", vid, err)
|
||||
}
|
||||
|
||||
return locations[index].Url, nil
|
||||
}
|
||||
|
||||
func (vc *vidMap) addLocation(vid uint32, location Location) {
|
||||
vc.Lock()
|
||||
defer vc.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user