mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-01 20:26:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a833021132 | ||
|
|
5e4b5109dd | ||
|
|
15c60cbb26 | ||
|
|
928efc642c | ||
|
|
7256902fb0 | ||
|
|
7fca4324f9 | ||
|
|
94ea3bd3a5 | ||
|
|
5138d3954f | ||
|
|
35ba277a97 | ||
|
|
9c1efdf11b | ||
|
|
8f3a51f2b8 | ||
|
|
6a2a9b67e8 | ||
|
|
694df89331 | ||
|
|
8c3177d835 | ||
|
|
42e5ef4b01 | ||
|
|
9fa7977714 | ||
|
|
ce416d765f | ||
|
|
82c167aaca | ||
|
|
7f90d14f10 | ||
|
|
502554887f | ||
|
|
c3af72d950 | ||
|
|
ee4b208b8a | ||
|
|
430aca474c | ||
|
|
91f26b3b9b | ||
|
|
2fb4c60540 | ||
|
|
24ec17219b | ||
|
|
a331bbb3ae | ||
|
|
5353e38469 | ||
|
|
2396ac234c | ||
|
|
609daaf387 | ||
|
|
9c56b46886 | ||
|
|
d67ccb66c0 | ||
|
|
76430790b9 | ||
|
|
318a3d2efc | ||
|
|
4be51c0701 | ||
|
|
1102ae32c4 | ||
|
|
314dc1c957 | ||
|
|
87d1bfa862 | ||
|
|
6f4aab51f9 | ||
|
|
043c2d7960 | ||
|
|
678b9a60be | ||
|
|
d475c89fcc | ||
|
|
19295600f9 | ||
|
|
990fa69bfe | ||
|
|
9292796ec2 | ||
|
|
c2bf1a88ac | ||
|
|
e9d8201925 | ||
|
|
cf252fc0cd | ||
|
|
9a06c35da4 | ||
|
|
da08402ba2 | ||
|
|
822f1ade9d | ||
|
|
be1062b7fc | ||
|
|
02fdc0a333 | ||
|
|
5f72c388bc | ||
|
|
d38613c49a | ||
|
|
22e167bc8a | ||
|
|
7a96276f34 | ||
|
|
b81956bcb5 | ||
|
|
6b54ff9912 | ||
|
|
f20ec82a28 | ||
|
|
1045ab8b07 | ||
|
|
0488c84bea | ||
|
|
2266b5c528 | ||
|
|
3634811408 | ||
|
|
5e07afb0f0 | ||
|
|
b3f66199db | ||
|
|
2945a64fd6 | ||
|
|
0c64d60fdb | ||
|
|
0aaaa9b470 | ||
|
|
5a761bb7a4 | ||
|
|
ad2a20c8a5 | ||
|
|
612b7975a1 | ||
|
|
3a1d3d3413 | ||
|
|
c41961d5cc | ||
|
|
ff35520a1a | ||
|
|
cc1f3907ff | ||
|
|
62ebb917e0 | ||
|
|
17f7c1c43f | ||
|
|
00707ec00f | ||
|
|
9bcb28a3ea | ||
|
|
2c5eac5705 | ||
|
|
6ca10725b8 | ||
|
|
096e088d7b | ||
|
|
ccbdb38c89 | ||
|
|
80b8692688 | ||
|
|
6897f1bfbc | ||
|
|
f8dbb03bdd | ||
|
|
759482e2ac | ||
|
|
937cfacc01 | ||
|
|
3ecd989500 | ||
|
|
20ef3bb8d4 | ||
|
|
711c3f3939 | ||
|
|
024c186a48 | ||
|
|
84f05787f8 | ||
|
|
f98817cfe6 | ||
|
|
1b8e3da295 | ||
|
|
c78f3dddcb | ||
|
|
1a3f859c58 | ||
|
|
b434f7e4e0 | ||
|
|
d5add83e85 | ||
|
|
52a8f1470e | ||
|
|
93b3adba98 | ||
|
|
fa0c8d5283 | ||
|
|
4c5b752b04 | ||
|
|
96354208c5 | ||
|
|
ca8f793978 | ||
|
|
90ce1eec19 | ||
|
|
f13c082583 | ||
|
|
e439b65e38 | ||
|
|
c7197470fd | ||
|
|
5a3386e39b | ||
|
|
67faa56920 | ||
|
|
7119e7b491 | ||
|
|
de876c795d | ||
|
|
4085b79d38 | ||
|
|
3d5cb7eb86 | ||
|
|
61ef2d8658 | ||
|
|
d7d907be27 | ||
|
|
389426bbb7 | ||
|
|
0fddf68417 | ||
|
|
0f1c08d8ec | ||
|
|
09f49d1c04 | ||
|
|
a4063a5437 | ||
|
|
01dc8a43ba | ||
|
|
16ad74f477 | ||
|
|
f002e668de | ||
|
|
19e52fd414 | ||
|
|
c0bcf6f6e1 | ||
|
|
5d4568b91f | ||
|
|
893cbc8482 | ||
|
|
5ef43b9b09 | ||
|
|
54527f0326 | ||
|
|
b5ceffe188 | ||
|
|
c64bfb0e2e | ||
|
|
34a846009d | ||
|
|
d2ece29fff | ||
|
|
9a50dbcda0 | ||
|
|
e1c7bc66f9 | ||
|
|
4aaa97c789 | ||
|
|
d0826c2f62 | ||
|
|
698f58f7c4 | ||
|
|
fd1d8a2a09 | ||
|
|
ae812c07d6 | ||
|
|
5581d08702 | ||
|
|
f20743bb38 | ||
|
|
8eed763b97 | ||
|
|
f17aa1d06c | ||
|
|
ca73013453 | ||
|
|
e2c7e3fe6d | ||
|
|
819a85f59a | ||
|
|
3c5b0ed8e3 | ||
|
|
9d1d6f7adf | ||
|
|
9e41941ba8 | ||
|
|
d1959de038 | ||
|
|
95982e0856 | ||
|
|
d595b35ca7 | ||
|
|
c1ed647daf | ||
|
|
e57e6d6677 | ||
|
|
9ca1787e7e | ||
|
|
5e62f77681 | ||
|
|
cfc2033bf8 | ||
|
|
8491fbf1c7 | ||
|
|
8b55331247 | ||
|
|
3fb2ed9093 | ||
|
|
c5df2577f5 | ||
|
|
90c5077610 | ||
|
|
5cd9c9e642 | ||
|
|
def6d8e565 | ||
|
|
1c7faf1100 | ||
|
|
2880160113 | ||
|
|
0ef43a23a7 | ||
|
|
629c9962e7 | ||
|
|
361043e6c1 | ||
|
|
cfb9342a15 | ||
|
|
38d516251e | ||
|
|
a4f0bac9b7 | ||
|
|
8a7302ce37 | ||
|
|
2c7148cd55 | ||
|
|
acc0d5cd2c | ||
|
|
dd0171e35c | ||
|
|
394513f598 | ||
|
|
6e12a3a490 | ||
|
|
62ee484d12 | ||
|
|
1efb51ba84 | ||
|
|
f0d3b3bf93 | ||
|
|
d3d3f2fb9b | ||
|
|
e9ccc0a6c5 | ||
|
|
90b117acf1 | ||
|
|
e2e0e30a51 | ||
|
|
40161c9cec | ||
|
|
30efaa66c6 | ||
|
|
07f4703bfc | ||
|
|
0f18592315 | ||
|
|
c7e7431fd3 | ||
|
|
e327385644 | ||
|
|
2b76854641 | ||
|
|
95ecf0c72f | ||
|
|
0764fccde7 | ||
|
|
f579838917 | ||
|
|
d253e0ea6e | ||
|
|
ab57767d4b | ||
|
|
9e670c2e23 | ||
|
|
a08e9d59ec | ||
|
|
b2e50f602f | ||
|
|
bbae13e415 | ||
|
|
eaac1a1393 | ||
|
|
b42c5b71c1 | ||
|
|
1309dfe612 | ||
|
|
e4ee25e6f0 | ||
|
|
247d5eed93 | ||
|
|
e92ce1d517 | ||
|
|
942a30f04c | ||
|
|
a3c432dadc | ||
|
|
e5a3ad372f | ||
|
|
9fa2d20357 | ||
|
|
ebb223c190 | ||
|
|
40b3207f18 | ||
|
|
97a94eddab | ||
|
|
2ce86f308e | ||
|
|
c3f674ac1d | ||
|
|
9c9ba3c209 | ||
|
|
4b5adc60b4 | ||
|
|
039ad101b1 | ||
|
|
5f538c5f9d | ||
|
|
bdd166c031 | ||
|
|
a04d8927a8 | ||
|
|
4250a3a675 | ||
|
|
f4d87f8b9d | ||
|
|
2c3c2c27d7 | ||
|
|
3fdf73e514 | ||
|
|
712563fbdd | ||
|
|
2a5fc7aa4d | ||
|
|
8529d56b59 | ||
|
|
cd2a9865ed | ||
|
|
3433accb1b | ||
|
|
14ddd15508 | ||
|
|
d1a05d261a | ||
|
|
0d8c1f976b | ||
|
|
1fc37a68c4 | ||
|
|
adda3207c4 | ||
|
|
c1cd6dd8d6 | ||
|
|
66ebc7c98f | ||
|
|
adef4ddc87 | ||
|
|
89977123b6 | ||
|
|
54fe0e5059 | ||
|
|
efd4253ab6 | ||
|
|
d2ead72918 | ||
|
|
2e6cf541e7 | ||
|
|
6d2c979999 | ||
|
|
90df52d001 | ||
|
|
da7e5aaa65 | ||
|
|
2ffc87b86e | ||
|
|
7458ff9523 | ||
|
|
7a70caac3c | ||
|
|
561a2ff0bc | ||
|
|
aa020ee7e7 | ||
|
|
da87f6b265 | ||
|
|
0a7c5f85a9 | ||
|
|
515b7632d7 | ||
|
|
0a067944cc | ||
|
|
1b1d182469 | ||
|
|
090f85be4b | ||
|
|
75613b2cc7 | ||
|
|
0e016bc7bd | ||
|
|
8e48a235e2 | ||
|
|
1620de08ae | ||
|
|
a09cd28986 | ||
|
|
75c6edba9e | ||
|
|
9bf6c10505 | ||
|
|
2fea8cfa0f | ||
|
|
53bc1ea25b | ||
|
|
b5e2be635a | ||
|
|
c4a202ec41 | ||
|
|
32955b04b8 | ||
|
|
c3d1b3b5aa | ||
|
|
94e3757c08 | ||
|
|
64c48c9724 | ||
|
|
3be3635799 | ||
|
|
0ca9d89589 | ||
|
|
dc19bc2ace | ||
|
|
385a4dec30 | ||
|
|
da134a2eb7 | ||
|
|
beb3b8ddb0 | ||
|
|
4cf3176cbb | ||
|
|
a1009e8044 | ||
|
|
a0990b929d | ||
|
|
9cd26c6ca5 | ||
|
|
0ed5345954 | ||
|
|
97e3432dfe | ||
|
|
4f31c1bb94 | ||
|
|
6c4f32d173 | ||
|
|
90d785a15f | ||
|
|
fe46411cd4 | ||
|
|
c74bede730 | ||
|
|
adf8cb4000 | ||
|
|
0823bde8d8 | ||
|
|
8ab70ccf76 | ||
|
|
65cc6dc636 | ||
|
|
488c2680e8 | ||
|
|
f5a19a87be | ||
|
|
f30de7f827 | ||
|
|
6b4e9eef57 | ||
|
|
75629954d1 | ||
|
|
af8dffd66f | ||
|
|
60b4dedfb7 | ||
|
|
6912bf94ae | ||
|
|
41c0f3ad24 | ||
|
|
0d5683fb0e | ||
|
|
3269fd7eaf | ||
|
|
e605f1e001 | ||
|
|
23903aa95b | ||
|
|
4e6b316913 | ||
|
|
85411b8cc3 | ||
|
|
252e554e18 | ||
|
|
dbf46b3aa1 | ||
|
|
8c00c46f9b | ||
|
|
47f5ec0cde | ||
|
|
9fc07feb00 | ||
|
|
7af9415696 | ||
|
|
1b4237795a | ||
|
|
160d46dbcc | ||
|
|
8e3b798ea0 | ||
|
|
738c4fd203 | ||
|
|
e2076201d7 | ||
|
|
986cbdf7d9 | ||
|
|
99990a3c91 | ||
|
|
a37f069296 | ||
|
|
b789767cd1 | ||
|
|
8cb67952db | ||
|
|
45f902a9b7 | ||
|
|
4e58a4f24e | ||
|
|
200e56215a | ||
|
|
b0a4da7111 | ||
|
|
0154cca306 | ||
|
|
deb77e3b4a | ||
|
|
fc05963fa3 | ||
|
|
2ac31da89e | ||
|
|
3c7f9633eb | ||
|
|
3d47c38262 | ||
|
|
b7e3ca9172 | ||
|
|
021358749b | ||
|
|
c4459249ef | ||
|
|
38fc89d041 | ||
|
|
23280257df | ||
|
|
813453f3e1 | ||
|
|
16cd6fb278 | ||
|
|
5c465293e9 | ||
|
|
14910d035c | ||
|
|
f930c713fc | ||
|
|
316d1b4e69 | ||
|
|
03637d6f57 | ||
|
|
37075a414d | ||
|
|
ae655033ac | ||
|
|
d986c7196d | ||
|
|
3fedfec1e7 | ||
|
|
c2f18a10cb | ||
|
|
4b0c2a846b | ||
|
|
83078ac6ce | ||
|
|
26731694f8 | ||
|
|
765b3ef1be | ||
|
|
97c942b5ee | ||
|
|
02a160c3fd | ||
|
|
a9c619c270 | ||
|
|
4211601eab | ||
|
|
b52ae9cef8 | ||
|
|
8e78187a97 | ||
|
|
900d22c6ec | ||
|
|
06bb7bf6c0 | ||
|
|
fce8803087 | ||
|
|
606051cc79 | ||
|
|
ac22f1dd26 | ||
|
|
eed87791b7 | ||
|
|
01e2da5782 | ||
|
|
77286f8bea | ||
|
|
57578a6cd1 | ||
|
|
78f3ab439c | ||
|
|
9acda432fe | ||
|
|
ae5eb85a06 | ||
|
|
5931a2f53f | ||
|
|
4c72482536 | ||
|
|
46b91228af | ||
|
|
263eb29e9f | ||
|
|
d171d9f988 | ||
|
|
eadbba5c95 | ||
|
|
cc839f935d | ||
|
|
b219ccfe68 | ||
|
|
a83b8e7b01 | ||
|
|
5fb60b7135 | ||
|
|
4d2855476c | ||
|
|
3857f9c840 | ||
|
|
14699dfcef | ||
|
|
a3d4b50a49 | ||
|
|
003b6245e7 | ||
|
|
f3bb645018 | ||
|
|
dc0bc48257 | ||
|
|
45dba088cd | ||
|
|
a9efaa6385 | ||
|
|
04062c56c7 | ||
|
|
03620776ec | ||
|
|
005a6123e9 | ||
|
|
f4abd01adf | ||
|
|
a9c6be5fc3 | ||
|
|
141ce67c09 | ||
|
|
0e99531dbf | ||
|
|
965413c21b | ||
|
|
96c48bc8a8 | ||
|
|
c57a7839ec | ||
|
|
8438fb0cc3 | ||
|
|
536202e4e0 | ||
|
|
99c4e50d3d | ||
|
|
2c913dde04 | ||
|
|
9ac4935f22 | ||
|
|
85554bea38 | ||
|
|
6d30b21b10 | ||
|
|
3c229eb677 | ||
|
|
c0fb4a3766 | ||
|
|
6783ae5f8f | ||
|
|
983fd90010 | ||
|
|
97f706f35b | ||
|
|
b11449f955 | ||
|
|
0d345ac97d | ||
|
|
cc2bd97ad9 | ||
|
|
0da7ecfd29 | ||
|
|
effa00ed08 | ||
|
|
513bcd6e0d | ||
|
|
1ae108efca | ||
|
|
a102157020 | ||
|
|
4e55baf5b1 | ||
|
|
52c8f2fc9a | ||
|
|
a26f1b2040 | ||
|
|
6206737df2 | ||
|
|
b3adb221b2 | ||
|
|
ec51d77dcf | ||
|
|
eab53ea80d | ||
|
|
d3be2ceedd | ||
|
|
c7ebadc25d | ||
|
|
9aa990f80f | ||
|
|
e8296104fc | ||
|
|
92f906b6fc | ||
|
|
025bd8d447 | ||
|
|
5f3546a688 | ||
|
|
176950ea78 | ||
|
|
7385103057 | ||
|
|
27e73de797 | ||
|
|
da04bb3d1b | ||
|
|
290b5e2cd0 | ||
|
|
781585b195 | ||
|
|
8cb8cd4cc5 | ||
|
|
ec052f75d0 | ||
|
|
7c9e592c2d | ||
|
|
a7258f024e | ||
|
|
4737df597d | ||
|
|
aec071187e | ||
|
|
dc304342b2 | ||
|
|
b04375a3d1 | ||
|
|
9add554feb | ||
|
|
c13db8de80 | ||
|
|
6204dc6e85 | ||
|
|
ef3e068c51 | ||
|
|
5f19e81dab | ||
|
|
ed1ce3f299 | ||
|
|
218561c206 | ||
|
|
c0d279c54e | ||
|
|
83527a8f55 | ||
|
|
fe01059675 | ||
|
|
61d96fde01 | ||
|
|
af658ea970 | ||
|
|
98827d68d9 | ||
|
|
ee2fa14dbe | ||
|
|
2bd6fd3bbe | ||
|
|
71056dae07 | ||
|
|
0ea5c087ce | ||
|
|
95c0de285d | ||
|
|
500bcab953 | ||
|
|
590f02179d | ||
|
|
0a406f652e | ||
|
|
68043cfcac | ||
|
|
0fc9ffc603 | ||
|
|
7cfa616843 | ||
|
|
57c770a7b4 | ||
|
|
442e092995 | ||
|
|
b2a72630da | ||
|
|
824e96ffcc | ||
|
|
3e362451d2 | ||
|
|
0d5355c614 | ||
|
|
7d24980840 | ||
|
|
56d8e5b540 | ||
|
|
b3f22a53b0 | ||
|
|
a9990a1dc6 | ||
|
|
e0d5207ed9 | ||
|
|
a2962604ad | ||
|
|
559dfacdac | ||
|
|
e6333da65a | ||
|
|
7094492428 | ||
|
|
c6a0704019 | ||
|
|
ed3b0f81c6 | ||
|
|
c4f0fd6e1b | ||
|
|
ab966410d2 | ||
|
|
a4dfa00030 | ||
|
|
2251a8e472 | ||
|
|
5a16f17e47 | ||
|
|
e06676f007 | ||
|
|
c32b6f4d9d | ||
|
|
0fbc27b640 | ||
|
|
d7cc0498e0 | ||
|
|
827930e249 | ||
|
|
0880aff224 | ||
|
|
4300ca8142 | ||
|
|
dbba8cb57e | ||
|
|
aa30604cec | ||
|
|
1eec5c8d5d | ||
|
|
fc7baef5bb | ||
|
|
745ee8d8f3 | ||
|
|
a29abd8600 | ||
|
|
8708e7a64d | ||
|
|
c6bd244ebd | ||
|
|
b73ef6aa95 | ||
|
|
dc26012a3b | ||
|
|
81024761cd | ||
|
|
86cdb2a3e5 | ||
|
|
73f934d5de | ||
|
|
5b636b3242 | ||
|
|
15956d8a8e | ||
|
|
a38efe2e7b | ||
|
|
de3bdd0651 | ||
|
|
1dd3a6ac36 | ||
|
|
eee3f00559 | ||
|
|
cc62a2c9ac | ||
|
|
6856b0d57e | ||
|
|
4f676aa7d3 | ||
|
|
be2bf1f102 | ||
|
|
e7e3d8df7c | ||
|
|
de277418bf | ||
|
|
2b6fdebd5e | ||
|
|
5779265992 | ||
|
|
63de3ea518 | ||
|
|
e92780b91e | ||
|
|
546f1bcb90 | ||
|
|
75887ba2a2 | ||
|
|
549422085b | ||
|
|
5fb8530358 | ||
|
|
baced7b94f | ||
|
|
6cd6ff6962 | ||
|
|
2121eba4cf | ||
|
|
eb0b8cd9d8 | ||
|
|
0dafcf1f5a | ||
|
|
06ee199ef3 | ||
|
|
b132cb893f | ||
|
|
0ae4d7bfdf | ||
|
|
8054ef2310 | ||
|
|
e1190b3224 | ||
|
|
c709059b69 | ||
|
|
d29b787220 | ||
|
|
5448781dfc | ||
|
|
0ecff9521d | ||
|
|
031aa70da6 | ||
|
|
c923170d7c | ||
|
|
85e43b678f | ||
|
|
7d5db73d98 | ||
|
|
a9662d44ac | ||
|
|
e0fcab47c0 | ||
|
|
e1423a83dd | ||
|
|
448475e100 | ||
|
|
9b10d1ac33 | ||
|
|
8750cac090 | ||
|
|
ef908e166b | ||
|
|
4609644658 | ||
|
|
df8d976bb0 | ||
|
|
7192a378cc | ||
|
|
004aa9ca5d | ||
|
|
f7b21973ac | ||
|
|
de86945aeb | ||
|
|
306062b4e7 | ||
|
|
05acc33294 | ||
|
|
9708df47d1 | ||
|
|
10f9f1cc26 | ||
|
|
8826601be1 | ||
|
|
be95f68ca7 | ||
|
|
b9ccaabdcc | ||
|
|
20b967e38c | ||
|
|
d2a4ffdd60 | ||
|
|
46303c36bf | ||
|
|
eb32af25d4 | ||
|
|
7ecbb4b3c9 | ||
|
|
9081ff8e65 | ||
|
|
8a7f5eba99 | ||
|
|
9cc6575ab5 | ||
|
|
c057dd1f64 | ||
|
|
19098f2c2d | ||
|
|
6135cbaa02 | ||
|
|
0cdf172694 | ||
|
|
09a7cb8353 | ||
|
|
0ab72a0904 | ||
|
|
737d6ed650 | ||
|
|
02dc51b1ed | ||
|
|
abff1f67a6 | ||
|
|
202964a09b | ||
|
|
e379fc3658 | ||
|
|
11697ca9fd | ||
|
|
08cbc714f0 | ||
|
|
23a0551c26 | ||
|
|
6560ac6466 | ||
|
|
cf0cd12943 | ||
|
|
4c231054fb | ||
|
|
ba64390555 | ||
|
|
0b68b68ec4 | ||
|
|
47047516d6 | ||
|
|
6f8b426f4f | ||
|
|
22a9ea0512 | ||
|
|
c6d9974fe7 | ||
|
|
b917be7955 | ||
|
|
8a55b7053f | ||
|
|
918546bbdb | ||
|
|
6364d59f92 | ||
|
|
fab01f9d8d | ||
|
|
2a10904d03 | ||
|
|
884db215a1 | ||
|
|
c321378976 | ||
|
|
1ad1b8c4f6 | ||
|
|
326fcdd86b | ||
|
|
2dcc178d0a | ||
|
|
5db46bb0dd | ||
|
|
9ff0490dab | ||
|
|
744c5594da | ||
|
|
3e925faddd | ||
|
|
73d924f1ee | ||
|
|
a67eb1afa1 | ||
|
|
7cc07655d4 | ||
|
|
53c3aad875 | ||
|
|
6da87720eb | ||
|
|
24bf142596 | ||
|
|
432c6f8db3 | ||
|
|
06c15ab35c | ||
|
|
72f6b77dea | ||
|
|
8766ca1b95 | ||
|
|
19026ae55d | ||
|
|
843865f2ca | ||
|
|
44921220b0 | ||
|
|
283d584141 | ||
|
|
c693c98c15 | ||
|
|
e71463a9eb | ||
|
|
e219c57849 | ||
|
|
f375b93aef | ||
|
|
596d476e3d | ||
|
|
63b0fb54f7 | ||
|
|
cf7a1c722f | ||
|
|
ab1105c524 | ||
|
|
2c40f56e5a | ||
|
|
6e63e5eaf0 | ||
|
|
62af2d961d | ||
|
|
5c2e409ffe | ||
|
|
a911375798 | ||
|
|
2ff727a32d | ||
|
|
b9a446839a | ||
|
|
411e49f964 | ||
|
|
ac162fc857 | ||
|
|
95bfec4931 | ||
|
|
29a4c3944e | ||
|
|
40334bc28d | ||
|
|
d892cad15d |
@@ -9,6 +9,7 @@ assignees: ''
|
||||
|
||||
Sponsors SeaweedFS via Patreon https://www.patreon.com/seaweedfs
|
||||
Report issues here. Ask questions here https://stackoverflow.com/questions/tagged/seaweedfs
|
||||
Please ask questions in https://github.com/chrislusf/seaweedfs/discussions
|
||||
|
||||
example of a good issue report:
|
||||
https://github.com/chrislusf/seaweedfs/issues/1005
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Cleanup
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Delete old release assets
|
||||
uses: mknejp/delete-release-assets@v1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
tag: dev
|
||||
fail-if-no-assets: false
|
||||
assets: |
|
||||
weed-*
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin, freebsd ]
|
||||
goarch: [amd64, arm]
|
||||
exclude:
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Wait for the deletion
|
||||
uses: jakejarvis/wait-action@master
|
||||
with:
|
||||
time: '30s'
|
||||
|
||||
- name: Set BUILD_TIME env
|
||||
run: echo BUILD_TIME=$(date -u +%Y-%m-%d-%H-%M) >> ${GITHUB_ENV}
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.14
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
build_flags: -tags 5BytesOffset # optional, default is
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed-large-disk
|
||||
asset_name: "weed-large-disk-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
- name: Go Release Binaries
|
||||
uses: wangyoucao577/go-release-action@v1.14
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
release_tag: dev
|
||||
overwrite: true
|
||||
ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}}
|
||||
# Where to run `go build .`
|
||||
project_path: weed
|
||||
binary_name: weed-
|
||||
asset_name: "weed-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
@@ -1,14 +1,17 @@
|
||||
# SeaweedFS
|
||||
|
||||
|
||||
[](https://join.slack.com/t/seaweedfs/shared_invite/enQtMzI4MTMwMjU2MzA3LTEyYzZmZWYzOGQ3MDJlZWMzYmI0OTE4OTJiZjJjODBmMzUxNmYwODg0YjY3MTNlMjBmZDQ1NzQ5NDJhZWI2ZmY)
|
||||
[](https://twitter.com/intent/follow?screen_name=seaweedfs)
|
||||
[](https://travis-ci.org/chrislusf/seaweedfs)
|
||||
[](https://godoc.org/github.com/chrislusf/seaweedfs/weed)
|
||||
[](https://github.com/chrislusf/seaweedfs/wiki)
|
||||
[](https://hub.docker.com/r/chrislusf/seaweedfs/)
|
||||
|
||||
|
||||

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

|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -58,11 +47,12 @@ Your support will be really appreciated by me and other supporters!
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
* [Quick Start](#quick-start)
|
||||
* [Introduction](#introduction)
|
||||
* [Features](#features)
|
||||
* [Additional Features](#additional-features)
|
||||
* [Filer Features](#filer-features)
|
||||
* [Example Usage](#example-usage)
|
||||
* [Example: Using Seaweed Object Store](#example-Using-Seaweed-Object-Store)
|
||||
* [Architecture](#architecture)
|
||||
* [Compared to Other File Systems](#compared-to-other-file-systems)
|
||||
* [Compared to HDFS](#compared-to-hdfs)
|
||||
@@ -75,6 +65,13 @@ Table of Contents
|
||||
* [Benchmark](#Benchmark)
|
||||
* [License](#license)
|
||||
|
||||
|
||||
## Quick Start ##
|
||||
* Download the latest binary from https://github.com/chrislusf/seaweedfs/releases and unzip a single binary file `weed` or `weed.exe`
|
||||
* Run `weed server -dir=/some/data/dir -s3` to start one master, one volume server, one filer, and one S3 gateway.
|
||||
|
||||
Also, to increase capacity, just add more volume servers by running `weed volume -dir="/some/data/dir2" -mserver="<master_host>:9333" -port=8081` locally, or on a different machine, or on thoudsands of machines. That is it!
|
||||
|
||||
## Introduction ##
|
||||
|
||||
SeaweedFS is a simple and highly scalable distributed file system. There are two objectives:
|
||||
@@ -82,15 +79,17 @@ SeaweedFS is a simple and highly scalable distributed file system. There are two
|
||||
1. to store billions of files!
|
||||
2. to serve the files fast!
|
||||
|
||||
SeaweedFS started as an Object Store to handle small files efficiently. Instead of managing all file metadata in a central master, the central master only manages file volumes, and it lets these volume servers manage files and their metadata. This relieves concurrency pressure from the central master and spreads file metadata into volume servers, allowing faster file access (O(1), usually just one disk read operation).
|
||||
SeaweedFS started as an Object Store to handle small files efficiently. Instead of managing all file metadata in a central master, the central master only manages volumes on volume servers, and these volume servers manage files and their metadata. This relieves concurrency pressure from the central master and spreads file metadata into volume servers, allowing faster file access (O(1), usually just one disk read operation).
|
||||
|
||||
SeaweedFS can transparently integrate with the cloud. With hot data on local cluster, and warm data on the cloud with O(1) access time, SeaweedFS can achieve both fast local access time and elastic cloud storage capacity, without any client side changes.
|
||||
SeaweedFS can transparently integrate with the cloud. With hot data on local cluster, and warm data on the cloud with O(1) access time, SeaweedFS can achieve both fast local access time and elastic cloud storage capacity. What's more, the cloud storage access API cost is minimized. Faster and Cheaper than direct cloud storage!
|
||||
|
||||
There is only 40 bytes of disk storage overhead for each file's metadata. It is so simple with O(1) disk reads that you are welcome to challenge the performance with your actual use cases.
|
||||
|
||||
SeaweedFS started by implementing [Facebook's Haystack design paper](http://www.usenix.org/event/osdi10/tech/full_papers/Beaver.pdf). Also, SeaweedFS implements erasure coding with ideas from [f4: Facebook’s Warm BLOB Storage System](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-muralidhar.pdf)
|
||||
|
||||
On top of the object store, optional [Filer] can support directories and POSIX attributes. Filer is a separate linearly-scalable stateless server with customizable metadata stores, e.g., MySql, Postgres, Mongodb, Redis, Cassandra, Elastic Search, LevelDB, MemSql, TiDB, Etcd, CockroachDB, etc.
|
||||
On top of the object store, optional [Filer] can support directories and POSIX attributes. Filer is a separate linearly-scalable stateless server with customizable metadata stores, e.g., MySql, Postgres, Redis, Cassandra, HBase, Mongodb, Elastic Search, LevelDB, RocksDB, MemSql, TiDB, Etcd, CockroachDB, etc.
|
||||
|
||||
For any distributed key value stores, the large values can be offloaded to SeaweedFS. With the fast access speed and linearly scalable capacity, SeaweedFS can work as a distributed [Key-Large-Value store][KeyLargeValueStore].
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
@@ -101,7 +100,7 @@ On top of the object store, optional [Filer] can support directories and POSIX a
|
||||
* Automatic compaction to reclaim disk space after deletion or update.
|
||||
* [Automatic entry TTL expiration][VolumeServerTTL].
|
||||
* Any server with some disk spaces can add to the total storage space.
|
||||
* Adding/Removing servers does **not** cause any data re-balancing.
|
||||
* Adding/Removing servers does **not** cause any data re-balancing unless triggered by admin commands.
|
||||
* Optional picture resizing.
|
||||
* Support ETag, Accept-Range, Last-Modified, etc.
|
||||
* Support in-memory/leveldb/readonly mode tuning for memory/performance balance.
|
||||
@@ -113,16 +112,20 @@ On top of the object store, optional [Filer] can support directories and POSIX a
|
||||
|
||||
## Filer Features ##
|
||||
* [Filer server][Filer] provides "normal" directories and files via http.
|
||||
* [Super Large Files][SuperLargeFiles] stores large or super large files in tens of TB.
|
||||
* [File TTL][FilerTTL] automatically expires file metadata and actual file data.
|
||||
* [Mount filer][Mount] reads and writes files directly as a local directory via FUSE.
|
||||
* [Filer Store Replication][FilerStoreReplication] enables HA for filer meta data stores.
|
||||
* [Active-Active Replication][ActiveActiveAsyncReplication] enables asynchronous one-way or two-way cross cluster continuous replication.
|
||||
* [Amazon S3 compatible API][AmazonS3API] accesses files with S3 tooling.
|
||||
* [Hadoop Compatible File System][Hadoop] accesses files from Hadoop/Spark/Flink/etc or even runs HBase.
|
||||
* [Async Replication To Cloud][BackupToCloud] has extremely fast local access and backups to Amazon S3, Google Cloud Storage, Azure, BackBlaze.
|
||||
* [WebDAV] accesses as a mapped drive on Mac and Windows, or from mobile devices.
|
||||
* [AES256-GCM Encrypted Storage][FilerDataEncryption] safely stores the encrypted data.
|
||||
* [File TTL][FilerTTL] automatically purges file metadata and actual file data.
|
||||
* [Super Large Files][SuperLargeFiles] stores large or super large files in tens of TB.
|
||||
|
||||
## Kubernetes ##
|
||||
* [Kubernetes CSI Driver][SeaweedFsCsiDriver] A Container Storage Interface (CSI) Driver. [](https://hub.docker.com/r/chrislusf/seaweedfs-csi-driver/)
|
||||
* [SeaweedFS Operator](https://github.com/seaweedfs/seaweedfs-operator)
|
||||
|
||||
[Filer]: https://github.com/chrislusf/seaweedfs/wiki/Directories-and-Files
|
||||
[SuperLargeFiles]: https://github.com/chrislusf/seaweedfs/wiki/Data-Structure-for-Large-Files
|
||||
@@ -138,10 +141,12 @@ On top of the object store, optional [Filer] can support directories and POSIX a
|
||||
[VolumeServerTTL]: https://github.com/chrislusf/seaweedfs/wiki/Store-file-with-a-Time-To-Live
|
||||
[SeaweedFsCsiDriver]: https://github.com/seaweedfs/seaweedfs-csi-driver
|
||||
[ActiveActiveAsyncReplication]: https://github.com/chrislusf/seaweedfs/wiki/Filer-Active-Active-cross-cluster-continuous-synchronization
|
||||
[FilerStoreReplication]: https://github.com/chrislusf/seaweedfs/wiki/Filer-Store-Replication
|
||||
[KeyLargeValueStore]: https://github.com/chrislusf/seaweedfs/wiki/Filer-as-a-Key-Large-Value-Store
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
## Example Usage ##
|
||||
## Example: Using Seaweed Object Store ##
|
||||
|
||||
By default, the master node runs on port 9333, and the volume nodes run on port 8080.
|
||||
Let's start one master node, and two volume nodes on port 8080 and 8081. Ideally, they should be started from different machines. We'll use localhost as an example.
|
||||
@@ -339,7 +344,7 @@ Usually hot data are fresh and warm data are old. SeaweedFS puts the newly creat
|
||||
|
||||
With the O(1) access time, the network latency cost is kept at minimum.
|
||||
|
||||
If the hot~warm data is split as 20~80, with 20 servers, you can achieve storage capacity of 100 servers. That's a cost saving of 80%! Or you can repurpose the 80 servers to store new data also, and get 5X storage throughput.
|
||||
If the hot/warm data is split as 20/80, with 20 servers, you can achieve storage capacity of 100 servers. That's a cost saving of 80%! Or you can repurpose the 80 servers to store new data also, and get 5X storage throughput.
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
@@ -369,7 +374,7 @@ The architectures are mostly the same. SeaweedFS aims to store and read files fa
|
||||
|
||||
* SeaweedFS optimizes for small files, ensuring O(1) disk seek operation, and can also handle large files.
|
||||
* SeaweedFS statically assigns a volume id for a file. Locating file content becomes just a lookup of the volume id, which can be easily cached.
|
||||
* SeaweedFS Filer metadata store can be any well-known and proven data stores, e.g., Cassandra, Mongodb, Redis, Elastic Search, MySql, Postgres, MemSql, TiDB, CockroachDB, Etcd etc, and is easy to customized.
|
||||
* SeaweedFS Filer metadata store can be any well-known and proven data stores, e.g., Redis, Cassandra, HBase, Mongodb, Elastic Search, MySql, Postgres, MemSql, TiDB, CockroachDB, Etcd etc, and is easy to customized.
|
||||
* SeaweedFS Volume server also communicates directly with clients via HTTP, supporting range queries, direct uploads, etc.
|
||||
|
||||
| System | File Metadata | File Content Read| POSIX | REST API | Optimized for large number of small files |
|
||||
@@ -407,11 +412,11 @@ SeaweedFS has a centralized master group to look up free volumes, while Ceph use
|
||||
|
||||
Same as SeaweedFS, Ceph is also based on the object store RADOS. Ceph is rather complicated with mixed reviews.
|
||||
|
||||
Ceph uses CRUSH hashing to automatically manage the data placement. SeaweedFS places data by assigned volumes.
|
||||
Ceph uses CRUSH hashing to automatically manage the data placement, which is efficient to locate the data. But the data has to be placed according to the CRUSH algorithm. Any wrong configuration would cause data loss. SeaweedFS places data by assigning them to any writable volumes. If writes to one volume failed, just pick another volume to write. Adding more volumes are also as simple as it can be.
|
||||
|
||||
SeaweedFS is optimized for small files. Small files are stored as one continuous block of content, with at most 8 unused bytes between files. Small file access is O(1) disk read.
|
||||
|
||||
SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Redis, Elastic Search, Cassandra, MemSql, TiDB, CockroachCB, Etcd, to manage file directories. These stores are proven, scalable, and easier to manage.
|
||||
SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Redis, Elastic Search, Cassandra, HBase, MemSql, TiDB, CockroachCB, Etcd, to manage file directories. These stores are proven, scalable, and easier to manage.
|
||||
|
||||
| SeaweedFS | comparable to Ceph | advantage |
|
||||
| ------------- | ------------- | ---------------- |
|
||||
@@ -425,9 +430,10 @@ SeaweedFS Filer uses off-the-shelf stores, such as MySql, Postgres, Mongodb, Red
|
||||
|
||||
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 metadata are in simple files. Each file write will incur extra writes to corresponding meta file.
|
||||
|
||||
MinIO does not have optimization for large number of small files.
|
||||
MinIO does not have optimization for lots of small files. The files are simply stored as is to local disks.
|
||||
Plus the extra meta file and shards for erasure coding, it only amplifies the LOSF problem.
|
||||
|
||||
MinIO has multiple disk IO to read one file. SeaweedFS has O(1) disk reads, even for erasure coded files.
|
||||
|
||||
@@ -439,17 +445,12 @@ MinIO has specific requirements on storage layout. It is not flexible to adjust
|
||||
|
||||
## 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.
|
||||
Other key features include: Erasure Encoding, JWT security.
|
||||
* More tools and documentation, on how to manage and scale the system.
|
||||
* Read and write stream data.
|
||||
* Support structured data.
|
||||
|
||||
This is a super exciting project! And we need helpers and [support](https://www.patreon.com/seaweedfs)!
|
||||
|
||||
BTW, We suggest run the code style check script `util/gostd` before you push your branch to remote, it will make SeaweedFS easy to review, maintain and develop:
|
||||
|
||||
```
|
||||
$ ./util/gostd
|
||||
```
|
||||
|
||||
[Back to TOC](#table-of-contents)
|
||||
|
||||
## Installation Guide ##
|
||||
|
||||
@@ -12,6 +12,7 @@ RUN \
|
||||
SUPERCRONIC=supercronic-linux-$ARCH && \
|
||||
# Install SeaweedFS and Supercronic ( for cron job mode )
|
||||
apk add --no-cache --virtual build-dependencies --update wget curl ca-certificates && \
|
||||
apk add fuse && \
|
||||
wget -P /tmp https://github.com/$(curl -s -L https://github.com/chrislusf/seaweedfs/releases/latest | egrep -o "chrislusf/seaweedfs/releases/download/.*/linux_$ARCH.tar.gz") && \
|
||||
tar -C /usr/bin/ -xzvf /tmp/linux_$ARCH.tar.gz && \
|
||||
curl -fsSLO "$SUPERCRONIC_URL" && \
|
||||
@@ -36,6 +37,8 @@ EXPOSE 19333
|
||||
EXPOSE 9333
|
||||
# s3 server http port
|
||||
EXPOSE 8333
|
||||
# webdav server http port
|
||||
EXPOSE 7333
|
||||
|
||||
RUN mkdir -p /data/filerldb2
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
FROM frolvlad/alpine-glibc as builder
|
||||
RUN apk add git go g++
|
||||
RUN apk add git go g++ fuse
|
||||
RUN mkdir -p /go/src/github.com/chrislusf/
|
||||
RUN git clone https://github.com/chrislusf/seaweedfs /go/src/github.com/chrislusf/seaweedfs
|
||||
ARG BRANCH=${BRANCH:-master}
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs && git checkout $BRANCH
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs/weed \
|
||||
&& export LDFLAGS="-X github.com/chrislusf/seaweedfs/weed/util.COMMIT=$(git rev-parse --short HEAD)" \
|
||||
&& go install -ldflags "${LDFLAGS}"
|
||||
@@ -12,6 +14,7 @@ COPY --from=builder /root/go/bin/weed /usr/bin/
|
||||
RUN mkdir -p /etc/seaweedfs
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/filer.toml /etc/seaweedfs/filer.toml
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/entrypoint.sh /entrypoint.sh
|
||||
RUN apk add fuse # for weed mount
|
||||
|
||||
# volume server gprc port
|
||||
EXPOSE 18080
|
||||
@@ -27,6 +30,8 @@ EXPOSE 19333
|
||||
EXPOSE 9333
|
||||
# s3 server http port
|
||||
EXPOSE 8333
|
||||
# webdav server http port
|
||||
EXPOSE 7333
|
||||
|
||||
RUN mkdir -p /data/filerldb2
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
FROM frolvlad/alpine-glibc as builder
|
||||
RUN apk add git go g++
|
||||
RUN apk add git go g++ fuse
|
||||
RUN mkdir -p /go/src/github.com/chrislusf/
|
||||
RUN git clone https://github.com/chrislusf/seaweedfs /go/src/github.com/chrislusf/seaweedfs
|
||||
ARG BRANCH=${BRANCH:-master}
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs && git checkout $BRANCH
|
||||
RUN cd /go/src/github.com/chrislusf/seaweedfs/weed \
|
||||
&& export LDFLAGS="-X github.com/chrislusf/seaweedfs/weed/util.COMMIT=$(git rev-parse --short HEAD)" \
|
||||
&& go install -tags 5BytesOffset -ldflags "${LDFLAGS}"
|
||||
@@ -12,6 +14,7 @@ COPY --from=builder /root/go/bin/weed /usr/bin/
|
||||
RUN mkdir -p /etc/seaweedfs
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/filer.toml /etc/seaweedfs/filer.toml
|
||||
COPY --from=builder /go/src/github.com/chrislusf/seaweedfs/docker/entrypoint.sh /entrypoint.sh
|
||||
RUN apk add fuse # for weed mount
|
||||
|
||||
# volume server gprc port
|
||||
EXPOSE 18080
|
||||
@@ -27,6 +30,8 @@ EXPOSE 19333
|
||||
EXPOSE 9333
|
||||
# s3 server http port
|
||||
EXPOSE 8333
|
||||
# webdav server http port
|
||||
EXPOSE 7333
|
||||
|
||||
RUN mkdir -p /data/filerldb2
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ COPY ./weed /usr/bin/
|
||||
RUN mkdir -p /etc/seaweedfs
|
||||
COPY ./filer.toml /etc/seaweedfs/filer.toml
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
RUN apk add fuse # for weed mount
|
||||
|
||||
# volume server grpc port
|
||||
EXPOSE 18080
|
||||
@@ -19,6 +20,8 @@ EXPOSE 19333
|
||||
EXPOSE 9333
|
||||
# s3 server http port
|
||||
EXPOSE 8333
|
||||
# webdav server http port
|
||||
EXPOSE 7333
|
||||
|
||||
RUN mkdir -p /data/filerldb2
|
||||
|
||||
|
||||
+20
-2
@@ -10,10 +10,28 @@ build:
|
||||
rm ./weed
|
||||
|
||||
dev: build
|
||||
docker-compose -f local-dev-compose.yml -p seaweedfs up
|
||||
docker-compose -f compose/local-dev-compose.yml -p seaweedfs up
|
||||
|
||||
dev_mount: build
|
||||
docker-compose -f compose/local-mount-compose.yml -p seaweedfs up
|
||||
|
||||
k8s: build
|
||||
docker-compose -f compose/local-k8s-compose.yml -p seaweedfs up
|
||||
|
||||
dev_registry: build
|
||||
docker-compose -f compose/local-registry-compose.yml -p seaweedfs up
|
||||
|
||||
dev_replicate: build
|
||||
docker-compose -f compose/local-replicate-compose.yml -p seaweedfs up
|
||||
|
||||
cluster: build
|
||||
docker-compose -f local-cluster-compose.yml -p seaweedfs up
|
||||
docker-compose -f compose/local-cluster-compose.yml -p seaweedfs up
|
||||
|
||||
2clusters: build
|
||||
docker-compose -f compose/local-clusters-compose.yml -p seaweedfs up
|
||||
|
||||
filer_etcd: build
|
||||
docker stack deploy -c compose/swarm-etcd.yml fs
|
||||
|
||||
clean:
|
||||
rm ./weed
|
||||
|
||||
@@ -19,12 +19,32 @@ services:
|
||||
- 9335:9335
|
||||
- 19335:19335
|
||||
command: "master -ip=master2 -port=9335 -peers=master0:9333,master1:9334,master2:9335 -mdir=/data/m3"
|
||||
volume:
|
||||
volume1:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: 'volume -mserver="master0:9333,master1:9334,master2:9335" -port=8080 -ip=volume -publicUrl=localhost:8080'
|
||||
command: 'volume -mserver="master0:9333,master1:9334,master2:9335" -port=8080 -ip=volume1 -publicUrl=localhost:8080'
|
||||
depends_on:
|
||||
- master0
|
||||
- master1
|
||||
- master2
|
||||
volume2:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8082:8082
|
||||
- 18082:18082
|
||||
command: 'volume -mserver="master0:9333,master1:9334,master2:9335" -port=8082 -ip=volume2 -publicUrl=localhost:8082'
|
||||
depends_on:
|
||||
- master0
|
||||
- master1
|
||||
- master2
|
||||
volume3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8083:8083
|
||||
- 18083:18083
|
||||
command: 'volume -mserver="master0:9333,master1:9334,master2:9335" -port=8083 -ip=volume3 -publicUrl=localhost:8083'
|
||||
depends_on:
|
||||
- master0
|
||||
- master1
|
||||
@@ -39,7 +59,8 @@ services:
|
||||
- master0
|
||||
- master1
|
||||
- master2
|
||||
- volume
|
||||
- volume1
|
||||
- volume2
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
@@ -49,5 +70,6 @@ services:
|
||||
- master0
|
||||
- master1
|
||||
- master2
|
||||
- volume
|
||||
- volume1
|
||||
- volume2
|
||||
- filer
|
||||
@@ -0,0 +1,23 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
server1:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
- 8084:8080
|
||||
- 18084:18080
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: "server -ip=server1 -filer -volume.max=0 -master.volumeSizeLimitMB=1024 -volume.preStopSeconds=1"
|
||||
server2:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9334:9333
|
||||
- 19334:19333
|
||||
- 8085:8080
|
||||
- 18085:18080
|
||||
- 8889:8888
|
||||
- 18889:18888
|
||||
command: "server -ip=server2 -filer -volume.max=0 -master.volumeSizeLimitMB=1024 -volume.preStopSeconds=1"
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume"
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume -preStopSeconds=1"
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
@@ -33,3 +33,14 @@ services:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
mount:
|
||||
image: chrislusf/seaweedfs:local
|
||||
privileged: true
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
mem_limit: 4096m
|
||||
command: '-v=4 mount -filer="filer:8888" -dirAutoCreate -dir=/mnt/seaweedfs -cacheCapacityMB=100 -concurrentWriters=128'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
@@ -0,0 +1,94 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume"
|
||||
depends_on:
|
||||
- master
|
||||
mysql:
|
||||
image: percona/percona-server:5.7
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
- ./seaweedfs.sql:/docker-entrypoint-initdb.d/seaweedfs.sql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=secret
|
||||
- MYSQL_DATABASE=seaweedfs
|
||||
- MYSQL_PASSWORD=secret
|
||||
- MYSQL_USER=seaweedfs
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
environment:
|
||||
- WEED_MYSQL_HOSTNAME=mysql
|
||||
- WEED_MYSQL_PORT=3306
|
||||
- WEED_MYSQL_DATABASE=seaweedfs
|
||||
- WEED_MYSQL_USERNAME=seaweedfs
|
||||
- WEED_MYSQL_PASSWORD=secret
|
||||
- WEED_MYSQL_ENABLED=true
|
||||
- WEED_MYSQL_CONNECTION_MAX_IDLE=5
|
||||
- WEED_MYSQL_CONNECTION_MAX_OPEN=75
|
||||
# "refresh" connection every 10 minutes, eliminating mysql closing "old" connections
|
||||
- WEED_MYSQL_CONNECTION_MAX_LIFETIME_SECONDS=600
|
||||
# enable usage of memsql as filer backend
|
||||
- WEED_MYSQL_INTERPOLATEPARAMS=true
|
||||
- WEED_LEVELDB2_ENABLED=false
|
||||
command: '-v 9 filer -master="master:9333"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- mysql
|
||||
ingress:
|
||||
image: jwilder/nginx-proxy:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./nginx/proxy.conf:/etc/nginx/proxy.conf
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: '-v 9 s3 -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
environment:
|
||||
- VIRTUAL_HOST=ingress
|
||||
- VIRTUAL_PORT=8333
|
||||
registry:
|
||||
image: registry:2
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "0.0.0.0:5001" # seaweedfs s3
|
||||
REGISTRY_LOG_LEVEL: "debug"
|
||||
REGISTRY_STORAGE: "s3"
|
||||
REGISTRY_STORAGE_S3_REGION: "us-east-1"
|
||||
REGISTRY_STORAGE_S3_REGIONENDPOINT: "http://ingress"
|
||||
REGISTRY_STORAGE_S3_BUCKET: "registry"
|
||||
REGISTRY_STORAGE_S3_ACCESSKEY: "some_access_key1"
|
||||
REGISTRY_STORAGE_S3_SECRETKEY: "some_secret_key1"
|
||||
REGISTRY_STORAGE_S3_V4AUTH: "true"
|
||||
REGISTRY_STORAGE_S3_SECURE: "false"
|
||||
REGISTRY_STORAGE_S3_SKIPVERIFY: "true"
|
||||
REGISTRY_STORAGE_S3_ROOTDIRECTORY: "/"
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: "true"
|
||||
REGISTRY_STORAGE_REDIRECT_DISABLE: "true"
|
||||
REGISTRY_VALIDATION_DISABLED: "true"
|
||||
ports:
|
||||
- 5001:5001
|
||||
depends_on:
|
||||
- s3
|
||||
- ingress
|
||||
@@ -0,0 +1,46 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 7455:8080
|
||||
- 9325:9325
|
||||
command: 'volume -mserver="master:9333" -port=8080 -metricsPort=9325 -preStopSeconds=1 -publicUrl=localhost:7455'
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
- 9326:9326
|
||||
command: 'filer -master="master:9333" -metricsPort=9326'
|
||||
tty: true
|
||||
stdin_open: true
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
mount_1:
|
||||
image: chrislusf/seaweedfs:local
|
||||
privileged: true
|
||||
entrypoint: '/bin/sh -c "mkdir -p t1 && mkdir -p cache/t1 && weed -v=4 mount -filer=filer:8888 -cacheDir=./cache/t1 -dir=./t1 -filer.path=/c1" -volumeServerAccess=filerProxy'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
mount_2:
|
||||
image: chrislusf/seaweedfs:local
|
||||
privileged: true
|
||||
entrypoint: '/bin/sh -c "mkdir -p t2 && mkdir -p cache/t2 && weed -v=4 mount -filer=filer:8888 -cacheDir=./cache/t2 -dir=./t2 -filer.path=/c1" -volumeServerAcess=publicUrl'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
- mount_1
|
||||
@@ -0,0 +1,91 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master -volumeSizeLimitMB=1024"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume -max=0 -preStopSeconds=1"
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: 'filer -master="master:9333"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: '-v 9 s3 -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- 9000:9000
|
||||
command: 'minio server /data'
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: "some_access_key1"
|
||||
MINIO_SECRET_KEY: "some_secret_key1"
|
||||
depends_on:
|
||||
- master
|
||||
registry1:
|
||||
image: registry:2
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "0.0.0.0:5001" # seaweedfs s3
|
||||
REGISTRY_LOG_LEVEL: "debug"
|
||||
REGISTRY_STORAGE: "s3"
|
||||
REGISTRY_STORAGE_S3_REGION: "us-east-1"
|
||||
REGISTRY_STORAGE_S3_REGIONENDPOINT: "http://s3:8333"
|
||||
REGISTRY_STORAGE_S3_BUCKET: "registry"
|
||||
REGISTRY_STORAGE_S3_ACCESSKEY: "some_access_key1"
|
||||
REGISTRY_STORAGE_S3_SECRETKEY: "some_secret_key1"
|
||||
REGISTRY_STORAGE_S3_V4AUTH: "true"
|
||||
REGISTRY_STORAGE_S3_SECURE: "false"
|
||||
REGISTRY_STORAGE_S3_SKIPVERIFY: "true"
|
||||
REGISTRY_STORAGE_S3_ROOTDIRECTORY: "/"
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: "true"
|
||||
REGISTRY_STORAGE_REDIRECT_DISABLE: "true"
|
||||
REGISTRY_VALIDATION_DISABLED: "true"
|
||||
ports:
|
||||
- 5001:5001
|
||||
depends_on:
|
||||
- s3
|
||||
- minio
|
||||
registry2:
|
||||
image: registry:2
|
||||
environment:
|
||||
REGISTRY_HTTP_ADDR: "0.0.0.0:5002" # minio
|
||||
REGISTRY_LOG_LEVEL: "debug"
|
||||
REGISTRY_STORAGE: "s3"
|
||||
REGISTRY_STORAGE_S3_REGION: "us-east-1"
|
||||
REGISTRY_STORAGE_S3_REGIONENDPOINT: "http://minio:9000"
|
||||
REGISTRY_STORAGE_S3_BUCKET: "registry"
|
||||
REGISTRY_STORAGE_S3_ACCESSKEY: "some_access_key1"
|
||||
REGISTRY_STORAGE_S3_SECRETKEY: "some_secret_key1"
|
||||
REGISTRY_STORAGE_S3_V4AUTH: "true"
|
||||
REGISTRY_STORAGE_S3_SECURE: "false"
|
||||
REGISTRY_STORAGE_S3_SKIPVERIFY: "true"
|
||||
REGISTRY_STORAGE_S3_ROOTDIRECTORY: "/"
|
||||
REGISTRY_STORAGE_DELETE_ENABLED: "true"
|
||||
REGISTRY_STORAGE_REDIRECT_DISABLE: "true"
|
||||
REGISTRY_VALIDATION_DISABLED: "true"
|
||||
ports:
|
||||
- 5002:5002
|
||||
depends_on:
|
||||
- s3
|
||||
- minio
|
||||
@@ -0,0 +1,61 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 9333:9333
|
||||
- 19333:19333
|
||||
command: "master -ip=master"
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: "volume -mserver=master:9333 -port=8080 -ip=volume -preStopSeconds=1"
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: '-v=9 filer -master="master:9333"'
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./notification.toml:/etc/seaweedfs/notification.toml
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- rabbitmq
|
||||
- replicate
|
||||
environment:
|
||||
RABBIT_SERVER_URL: "amqp://guest:guest@rabbitmq:5672/"
|
||||
replicate:
|
||||
image: chrislusf/seaweedfs:local
|
||||
command: '-v=9 filer.replicate'
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./notification.toml:/etc/seaweedfs/notification.toml
|
||||
- ./replication.toml:/etc/seaweedfs/replication.toml
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
environment:
|
||||
RABBIT_SERVER_URL: "amqp://guest:guest@rabbitmq:5672/"
|
||||
s3:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: 's3 -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.8.10-management-alpine
|
||||
ports:
|
||||
- 5672:5672
|
||||
- 15671:15671
|
||||
- 15672:15672
|
||||
environment:
|
||||
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "-rabbit log_levels [{connection,error},{queue,debug}]"
|
||||
@@ -0,0 +1,84 @@
|
||||
# 2021-01-30 16:25:30
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
|
||||
etcd:
|
||||
image: gasparekatapy/etcd
|
||||
networks:
|
||||
- net
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 3
|
||||
|
||||
master:
|
||||
image: chrislusf/seaweedfs:local
|
||||
environment:
|
||||
WEED_MASTER_FILER_DEFAULT: "filer:8888"
|
||||
WEED_MASTER_SEQUENCER_TYPE: "raft"
|
||||
ports:
|
||||
- "9333:9333"
|
||||
- "19333:19333"
|
||||
networks:
|
||||
- net
|
||||
command:
|
||||
- 'master'
|
||||
- '-resumeState=true'
|
||||
- '-ip=master'
|
||||
- '-port=9333'
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
|
||||
filer:
|
||||
image: chrislusf/seaweedfs:local
|
||||
environment:
|
||||
WEED_LEVELDB2_ENABLED: "false"
|
||||
WEED_ETCD_ENABLED: "true"
|
||||
WEED_ETCD_SERVERS: "etcd:2379"
|
||||
ports:
|
||||
- target: 8888
|
||||
published: 8888
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 18888
|
||||
published: 18888
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- net
|
||||
command:
|
||||
- 'filer'
|
||||
- '-ip=filer'
|
||||
- '-port=8888'
|
||||
- '-port.readonly=28888'
|
||||
- '-master=master:9333'
|
||||
- '-disableDirListing=true'
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
|
||||
volume:
|
||||
image: chrislusf/seaweedfs:local
|
||||
ports:
|
||||
- target: 8080
|
||||
published: 8080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 18080
|
||||
published: 18080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- net
|
||||
command:
|
||||
- 'volume'
|
||||
- '-mserver=master:9333'
|
||||
- '-port=8080'
|
||||
deploy:
|
||||
mode: global
|
||||
|
||||
###########################################################################
|
||||
|
||||
networks:
|
||||
net:
|
||||
@@ -0,0 +1,30 @@
|
||||
# HTTP 1.1 support
|
||||
proxy_http_version 1.1;
|
||||
#proxy_buffering off;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||
|
||||
# Mitigate httpoxy attack (see README for details)
|
||||
proxy_set_header Proxy "";
|
||||
|
||||
# aws default max_concurrent_requests 10
|
||||
# aws default multipart_threshold 8MB
|
||||
proxy_buffering on; # GET buffering or “X-Accel-Buffering” enables or disables buffering of a response;
|
||||
proxy_buffers 64 1m; # buffers used for reading a response from the proxied server, for a single connection
|
||||
proxy_buffer_size 8k; # maximum size of the data that nginx can receive from the server at a time is set
|
||||
proxy_busy_buffers_size 2m;
|
||||
|
||||
proxy_request_buffering on; # PUT buffering
|
||||
client_body_buffer_size 64m; # buffer size for reading client request body
|
||||
client_max_body_size 64m;
|
||||
|
||||
proxy_next_upstream error timeout non_idempotent http_500; # PUT request should be passed to the next server:
|
||||
proxy_connect_timeout 200ms;
|
||||
proxy_read_timeout 3s; #timeout is set only between two successive read operations
|
||||
proxy_send_timeout 3s; #timeout is set only between two successive write operations
|
||||
@@ -0,0 +1,17 @@
|
||||
[notification.log]
|
||||
# this is only for debugging perpose and does not work with "weed filer.replicate"
|
||||
enabled = false
|
||||
|
||||
|
||||
[notification.gocdk_pub_sub]
|
||||
# The Go Cloud Development Kit (https://gocloud.dev).
|
||||
# PubSub API (https://godoc.org/gocloud.dev/pubsub).
|
||||
# Supports AWS SNS/SQS, Azure Service Bus, Google PubSub, NATS and RabbitMQ.
|
||||
enabled = true
|
||||
# This URL will Dial the RabbitMQ server at the URL in the environment
|
||||
# variable RABBIT_SERVER_URL and open the exchange "myexchange".
|
||||
# The exchange must have already been created by some other means, like
|
||||
# the RabbitMQ management plugin. Сreate myexchange of type fanout and myqueue then
|
||||
# create binding myexchange => myqueue
|
||||
topic_url = "rabbit://swexchange"
|
||||
sub_url = "rabbit://swqueue"
|
||||
@@ -0,0 +1,13 @@
|
||||
global:
|
||||
scrape_interval: 30s
|
||||
scrape_timeout: 10s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: services
|
||||
metrics_path: /metrics
|
||||
static_configs:
|
||||
- targets:
|
||||
- 'prometheus:9090'
|
||||
- 'volume:9325'
|
||||
- 'filer:9326'
|
||||
- 's3:9327'
|
||||
@@ -0,0 +1,11 @@
|
||||
[source.filer]
|
||||
enabled = true
|
||||
grpcAddress = "filer:18888"
|
||||
# all files under this directory tree are replicated.
|
||||
# this is not a directory on your hard drive, but on your filer.
|
||||
# i.e., all files with this "prefix" are sent to notification message queue.
|
||||
directory = "/buckets"
|
||||
|
||||
[sink.local_incremental]
|
||||
enabled = true
|
||||
directory = "/data"
|
||||
@@ -12,7 +12,8 @@ services:
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 18080:18080
|
||||
command: 'volume -mserver="master:9333" -port=8080'
|
||||
- 9325:9325
|
||||
command: 'volume -mserver="master:9333" -port=8080 -metricsPort=9325'
|
||||
depends_on:
|
||||
- master
|
||||
filer:
|
||||
@@ -20,7 +21,8 @@ services:
|
||||
ports:
|
||||
- 8888:8888
|
||||
- 18888:18888
|
||||
command: 'filer -master="master:9333"'
|
||||
- 9326:9326
|
||||
command: 'filer -master="master:9333" -metricsPort=9326'
|
||||
tty: true
|
||||
stdin_open: true
|
||||
depends_on:
|
||||
@@ -40,8 +42,27 @@ services:
|
||||
image: chrislusf/seaweedfs # use a remote image
|
||||
ports:
|
||||
- 8333:8333
|
||||
command: 's3 -filer="filer:8888"'
|
||||
- 9327:9327
|
||||
command: 's3 -filer="filer:8888" -metricsPort=9327'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
webdav:
|
||||
image: chrislusf/seaweedfs # use a remote image
|
||||
ports:
|
||||
- 7333:7333
|
||||
command: 'webdav -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.21.0
|
||||
ports:
|
||||
- 9000:9090
|
||||
volumes:
|
||||
- ./prometheus:/etc/prometheus
|
||||
command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml
|
||||
depends_on:
|
||||
- s3
|
||||
|
||||
@@ -33,3 +33,12 @@ services:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
webdav:
|
||||
image: chrislusf/seaweedfs:dev # use a remote dev image
|
||||
ports:
|
||||
- 7333:7333
|
||||
command: 'webdav -filer="filer:8888"'
|
||||
depends_on:
|
||||
- master
|
||||
- volume
|
||||
- filer
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE DATABASE IF NOT EXISTS seaweedfs;
|
||||
CREATE USER IF NOT EXISTS 'seaweedfs'@'%' IDENTIFIED BY 'secret';
|
||||
GRANT ALL PRIVILEGES ON seaweedfs_fast.* TO 'seaweedfs'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
USE seaweedfs;
|
||||
CREATE TABLE IF NOT EXISTS filemeta (
|
||||
dirhash BIGINT COMMENT 'first 64 bits of MD5 hash value of directory field',
|
||||
name VARCHAR(1000) COMMENT 'directory or file name',
|
||||
directory TEXT COMMENT 'full path to parent directory',
|
||||
meta LONGBLOB,
|
||||
PRIMARY KEY (dirhash, name)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
@@ -3,15 +3,18 @@ module github.com/chrislusf/seaweedfs
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.44.3
|
||||
cloud.google.com/go v0.58.0
|
||||
cloud.google.com/go/pubsub v1.3.1
|
||||
cloud.google.com/go/storage v1.9.0
|
||||
github.com/Azure/azure-amqp-common-go/v2 v2.1.0 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.2 // indirect
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0
|
||||
github.com/Azure/azure-storage-blob-go v0.9.0
|
||||
github.com/OneOfOne/xxhash v1.2.2
|
||||
github.com/Shopify/sarama v1.23.1
|
||||
github.com/aws/aws-sdk-go v1.33.5
|
||||
github.com/aws/aws-sdk-go v1.34.30
|
||||
github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/chrislusf/raft v1.0.2-0.20201002174524-b13c3bfdb011
|
||||
github.com/chrislusf/raft v1.0.4
|
||||
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
|
||||
@@ -22,12 +25,14 @@ require (
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4
|
||||
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
|
||||
github.com/fclairamb/ftpserverlib v0.8.0
|
||||
github.com/frankban/quicktest v1.7.2 // indirect
|
||||
github.com/go-redis/redis v6.15.7+incompatible
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.4.4
|
||||
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/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/btree v1.0.0
|
||||
github.com/google/uuid v1.1.1
|
||||
@@ -37,54 +42,54 @@ require (
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/karlseguin/ccache v2.0.3+incompatible
|
||||
github.com/karlseguin/expect v1.0.1 // indirect
|
||||
github.com/klauspost/compress v1.10.9
|
||||
github.com/karlseguin/ccache/v2 v2.0.7
|
||||
github.com/klauspost/compress v1.10.9 // indirect
|
||||
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||
github.com/klauspost/crc32 v1.2.0
|
||||
github.com/klauspost/reedsolomon v1.9.2
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lunixbochs/vtclean v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190805055040-f9202b1cfdeb // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.0.4 // indirect
|
||||
github.com/olivere/elastic/v7 v7.0.19
|
||||
github.com/onsi/ginkgo v1.10.1 // indirect
|
||||
github.com/onsi/gomega v1.7.0 // indirect
|
||||
github.com/peterh/liner v1.1.0
|
||||
github.com/pierrec/lz4 v2.2.7+incompatible // indirect
|
||||
github.com/prometheus/client_golang v1.1.0
|
||||
github.com/prometheus/procfs v0.0.4 // indirect
|
||||
github.com/prometheus/client_golang v1.3.0
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
|
||||
github.com/seaweedfs/fuse v1.0.7
|
||||
github.com/seaweedfs/fuse v1.1.1
|
||||
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
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 // indirect
|
||||
github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c
|
||||
github.com/tidwall/gjson v1.3.2
|
||||
github.com/tidwall/match v1.0.1
|
||||
github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/viant/assertly v0.5.4 // indirect
|
||||
github.com/viant/ptrie v0.3.0
|
||||
github.com/viant/toolbox v0.33.2 // indirect
|
||||
github.com/willf/bitset v1.1.10 // indirect
|
||||
github.com/willf/bloom v2.0.3+incompatible
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect
|
||||
go.etcd.io/etcd v3.3.15+incompatible
|
||||
go.mongodb.org/mongo-driver v1.3.2
|
||||
gocloud.dev v0.16.0
|
||||
gocloud.dev/pubsub/natspubsub v0.16.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.16.0
|
||||
gocloud.dev v0.20.0
|
||||
gocloud.dev/pubsub/natspubsub v0.20.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.20.0
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
|
||||
golang.org/x/sync v0.0.0-20200930132711-30421366ff76 // indirect
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5
|
||||
google.golang.org/api v0.9.0
|
||||
google.golang.org/appengine v1.6.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20200608174601-1b747fd94509
|
||||
google.golang.org/api v0.26.0
|
||||
google.golang.org/grpc v1.29.1
|
||||
google.golang.org/protobuf v1.24.0
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
version: 2.06
|
||||
appVersion: "2.24"
|
||||
version: 2.24
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,12 +52,12 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- $registryName := default .Values.image.registry .Values.global.localRegistry | toString -}}
|
||||
{{- $repositoryName := .Values.image.repository | toString -}}
|
||||
{{- $name := .Values.global.imageName | toString -}}
|
||||
{{- $tag := .Values.global.imageTag | toString -}}
|
||||
{{- $tag := .Chart.AppVersion | toString -}}
|
||||
{{- printf "%s%s%s:%s" $registryName $repositoryName $name $tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Return the proper postgresqlSchema image */}}
|
||||
{{/* Return the proper dbSchema image */}}
|
||||
{{- define "filer.dbSchema.image" -}}
|
||||
{{- if .Values.filer.dbSchema.imageOverride -}}
|
||||
{{- $imageOverride := .Values.filer.dbSchema.imageOverride -}}
|
||||
@@ -80,7 +80,7 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- $registryName := default .Values.image.registry .Values.global.localRegistry | toString -}}
|
||||
{{- $repositoryName := .Values.image.repository | toString -}}
|
||||
{{- $name := .Values.global.imageName | toString -}}
|
||||
{{- $tag := .Values.global.imageTag | toString -}}
|
||||
{{- $tag := .Chart.AppVersion | toString -}}
|
||||
{{- printf "%s%s%s:%s" $registryName $repositoryName $name $tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -94,7 +94,7 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- $registryName := default .Values.image.registry .Values.global.localRegistry | toString -}}
|
||||
{{- $repositoryName := .Values.image.repository | toString -}}
|
||||
{{- $name := .Values.global.imageName | toString -}}
|
||||
{{- $tag := .Values.global.imageTag | toString -}}
|
||||
{{- $tag := .Chart.AppVersion | toString -}}
|
||||
{{- printf "%s%s%s:%s" $registryName $repositoryName $name $tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -108,7 +108,7 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- $registryName := default .Values.image.registry .Values.global.localRegistry | toString -}}
|
||||
{{- $repositoryName := .Values.image.repository | toString -}}
|
||||
{{- $name := .Values.global.imageName | toString -}}
|
||||
{{- $tag := .Values.global.imageTag | toString -}}
|
||||
{{- $tag := .Chart.AppVersion | toString -}}
|
||||
{{- printf "%s%s%s:%s" $registryName $repositoryName $name $tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -122,7 +122,7 @@ Inject extra environment vars in the format key:value, if populated
|
||||
{{- $registryName := default .Values.image.registry .Values.global.localRegistry | toString -}}
|
||||
{{- $repositoryName := .Values.image.repository | toString -}}
|
||||
{{- $name := .Values.global.imageName | toString -}}
|
||||
{{- $tag := .Values.global.imageTag | toString -}}
|
||||
{{- $tag := .Chart.AppVersion | toString -}}
|
||||
{{- printf "%s%s%s:%s" $registryName $repositoryName $name $tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -6,6 +6,7 @@ metadata:
|
||||
name: {{ include "seaweedfs.fullname" . }}-cronjob
|
||||
spec:
|
||||
schedule: "{{ .Values.cronjob.schedule }}"
|
||||
startingDeadlineSeconds: 200
|
||||
concurrencyPolicy: Forbid
|
||||
failedJobsHistoryLimit: 2
|
||||
successfulJobsHistoryLimit: 2
|
||||
@@ -51,4 +52,4 @@ spec:
|
||||
-filer {{ template "seaweedfs.name" . }}-filer.{{ .Release.Namespace }}.svc:{{ .Values.filer.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ template "seaweedfs.name" . }}-filer-client
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: filer
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
monitoring: "true"
|
||||
{{- end }}
|
||||
spec:
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: "swfs-filer"
|
||||
port: {{ .Values.filer.port }}
|
||||
targetPort: {{ .Values.filer.port }}
|
||||
protocol: TCP
|
||||
- name: "swfs-filer-grpc"
|
||||
port: {{ .Values.filer.grpcPort }}
|
||||
targetPort: {{ .Values.filer.grpcPort }}
|
||||
protocol: TCP
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
- name: "metrics"
|
||||
port: {{ .Values.filer.metricsPort }}
|
||||
targetPort: {{ .Values.filer.metricsPort }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: filer
|
||||
@@ -1,6 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
name: {{ template "seaweedfs.name" . }}-filer
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
@@ -8,6 +10,7 @@ metadata:
|
||||
component: filer
|
||||
spec:
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: "swfs-filer"
|
||||
port: {{ .Values.filer.port }}
|
||||
@@ -17,12 +20,6 @@ spec:
|
||||
port: {{ .Values.filer.grpcPort }}
|
||||
targetPort: {{ .Values.filer.grpcPort }}
|
||||
protocol: TCP
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
- name: "swfs-filer-metrics"
|
||||
port: {{ .Values.filer.metricsPort }}
|
||||
targetPort: {{ .Values.filer.metricsPort }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: filer
|
||||
component: filer
|
||||
|
||||
@@ -87,6 +87,12 @@ spec:
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.global.extraEnvironmentVars }}
|
||||
{{- range $key, $value := .Values.global.extraEnvironmentVars }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
@@ -101,7 +107,7 @@ spec:
|
||||
-port={{ .Values.filer.port }} \
|
||||
{{- if .Values.filer.metricsPort }}
|
||||
-metricsPort {{ .Values.filer.metricsPort }} \
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
{{- if .Values.filer.redirectOnRead }}
|
||||
-redirectOnRead \
|
||||
{{- end }}
|
||||
@@ -128,7 +134,7 @@ spec:
|
||||
{{- end }}
|
||||
-ip=${POD_IP} \
|
||||
{{- if gt (.Values.filer.replicas | int) 1 }}
|
||||
-peers={{ range $index := until (.Values.filer.replicas | int) }}${SEAWEEDFS_FULLNAME}-filer-{{ $index }}.${SEAWEEDFS_FULLNAME}-filer:{{ $.Values.filer.port }}{{ if lt $index (sub ($.Values.filer.replicas | int) 1) }},{{ end }}{{ end }}
|
||||
-peers=$(echo -n "{{ range $index := until (.Values.filer.replicas | int) }}${SEAWEEDFS_FULLNAME}-filer-{{ $index }}.${SEAWEEDFS_FULLNAME}-filer:{{ $.Values.filer.port }}{{ if lt $index (sub ($.Values.filer.replicas | int) 1) }},{{ end }}{{ end }}" | sed "s/$HOSTNAME.${SEAWEEDFS_FULLNAME}-filer:{{ $.Values.filer.port }}//" | sed 's/,$//; 's/^,//'; s/,,/,/;' ) \
|
||||
{{- end }}
|
||||
-master={{ range $index := until (.Values.master.replicas | int) }}${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master:{{ $.Values.master.port }}{{ if lt $index (sub ($.Values.master.replicas | int) 1) }},{{ end }}{{ end }}
|
||||
{{- if or (.Values.global.enableSecurity) (.Values.filer.extraVolumeMounts) }}
|
||||
@@ -172,7 +178,7 @@ spec:
|
||||
periodSeconds: 15
|
||||
successThreshold: 1
|
||||
failureThreshold: 100
|
||||
timeoutSeconds: 3
|
||||
timeoutSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
@@ -182,7 +188,7 @@ spec:
|
||||
periodSeconds: 30
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
timeoutSeconds: 3
|
||||
timeoutSeconds: 10
|
||||
{{- if .Values.filer.resources }}
|
||||
resources:
|
||||
{{ tpl .Values.filer.resources . | nindent 12 | trim }}
|
||||
|
||||
@@ -10,6 +10,7 @@ metadata:
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
spec:
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: "swfs-master"
|
||||
port: {{ .Values.master.port }}
|
||||
@@ -21,4 +22,4 @@ spec:
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: master
|
||||
component: master
|
||||
|
||||
@@ -76,6 +76,12 @@ spec:
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.global.extraEnvironmentVars }}
|
||||
{{- range $key, $value := .Values.global.extraEnvironmentVars }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
@@ -157,7 +163,7 @@ spec:
|
||||
periodSeconds: 45
|
||||
successThreshold: 2
|
||||
failureThreshold: 100
|
||||
timeoutSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /cluster/status
|
||||
@@ -167,7 +173,7 @@ spec:
|
||||
periodSeconds: 30
|
||||
successThreshold: 1
|
||||
failureThreshold: 4
|
||||
timeoutSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
{{- if .Values.master.resources }}
|
||||
resources:
|
||||
{{ tpl .Values.master.resources . | nindent 12 | trim }}
|
||||
|
||||
@@ -59,11 +59,17 @@ spec:
|
||||
fieldPath: metadata.namespace
|
||||
- name: SEAWEEDFS_FULLNAME
|
||||
value: "{{ template "seaweedfs.name" . }}"
|
||||
{{- if .Values.global.extraEnvironmentVars }}
|
||||
{{- range $key, $value := .Values.global.extraEnvironmentVars }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
exec /usr/bin/weed \
|
||||
exec /usr/bin/weed -logdir=/logs \
|
||||
{{- if .Values.s3.loggingOverrideLevel }}
|
||||
-v={{ .Values.s3.loggingOverrideLevel }} \
|
||||
{{- else }}
|
||||
@@ -73,7 +79,7 @@ spec:
|
||||
-port={{ .Values.s3.port }} \
|
||||
{{- if .Values.s3.metricsPort }}
|
||||
-metricsPort {{ .Values.s3.metricsPort }} \
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
{{- if .Values.global.enableSecurity }}
|
||||
-cert.file=/usr/local/share/ca-certificates/client/tls.crt \
|
||||
-key.file=/usr/local/share/ca-certificates/client/tls.key \
|
||||
@@ -81,9 +87,13 @@ spec:
|
||||
{{- if .Values.s3.domainName }}
|
||||
-domainName={{ .Values.s3.domainName }} \
|
||||
{{- end }}
|
||||
-filer={{ template "seaweedfs.name" . }}-filer:{{ .Values.filer.port }}
|
||||
{{- if or (.Values.global.enableSecurity) (.Values.s3.extraVolumeMounts) }}
|
||||
{{- if .Values.s3.allowEmptyFolder }}
|
||||
-allowEmptyFolder={{ .Values.s3.allowEmptyFolder }} \
|
||||
{{- end }}
|
||||
-filer={{ template "seaweedfs.name" . }}-filer-client:{{ .Values.filer.port }}
|
||||
volumeMounts:
|
||||
- name: logs
|
||||
mountPath: "/logs/"
|
||||
{{- if .Values.global.enableSecurity }}
|
||||
- name: security-config
|
||||
readOnly: true
|
||||
@@ -106,7 +116,6 @@ spec:
|
||||
mountPath: /usr/local/share/ca-certificates/client/
|
||||
{{- end }}
|
||||
{{ tpl .Values.s3.extraVolumeMounts . | nindent 12 | trim }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.s3.port }}
|
||||
name: swfs-s3
|
||||
@@ -119,7 +128,7 @@ spec:
|
||||
periodSeconds: 15
|
||||
successThreshold: 1
|
||||
failureThreshold: 100
|
||||
timeoutSeconds: 3
|
||||
timeoutSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
@@ -129,12 +138,18 @@ spec:
|
||||
periodSeconds: 60
|
||||
successThreshold: 1
|
||||
failureThreshold: 20
|
||||
timeoutSeconds: 3
|
||||
timeoutSeconds: 10
|
||||
{{- if .Values.s3.resources }}
|
||||
resources:
|
||||
{{ tpl .Values.s3.resources . | nindent 12 | trim }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- if eq .Values.s3.logs.type "hostPath" }}
|
||||
- name: logs
|
||||
hostPath:
|
||||
path: /storage/logs/seaweedfs/s3
|
||||
type: DirectoryOrCreate
|
||||
{{- end }}
|
||||
{{- if .Values.global.enableSecurity }}
|
||||
- name: security-config
|
||||
configMap:
|
||||
|
||||
@@ -14,10 +14,10 @@ spec:
|
||||
protocol: TCP
|
||||
{{- if .Values.s3.metricsPort }}
|
||||
- name: "swfs-s3-metrics"
|
||||
port: {{ .Values.filer.s3 }}
|
||||
port: {{ .Values.s3.metricsPort }}
|
||||
targetPort: {{ .Values.s3.metricsPort }}
|
||||
protocol: TCP
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: s3
|
||||
@@ -15,4 +15,4 @@ spec:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: s3
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- if .Values.global.monitoring.enabled }}
|
||||
{{- $files := .Files.Glob "dashboards/*.json" }}
|
||||
{{- if $files }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMapList
|
||||
items:
|
||||
{{- range $path, $fileContents := $files }}
|
||||
{{- $dashboardName := regexReplaceAll "(^.*/)(.*)\\.json$" $path "${2}" }}
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ printf "%s" $dashboardName | lower | replace "_" "-" }}
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
{{ $dashboardName }}.json: {{ $.Files.Get $path | toJson }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ spec:
|
||||
port: {{ .Values.volume.metricsPort }}
|
||||
targetPort: {{ .Values.volume.metricsPort }}
|
||||
protocol: TCP
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: volume
|
||||
@@ -15,4 +15,4 @@ spec:
|
||||
selector:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: volume
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
@@ -64,6 +64,12 @@ spec:
|
||||
fieldPath: status.hostIP
|
||||
- name: SEAWEEDFS_FULLNAME
|
||||
value: "{{ template "seaweedfs.name" . }}"
|
||||
{{- if .Values.global.extraEnvironmentVars }}
|
||||
{{- range $key, $value := .Values.global.extraEnvironmentVars }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
@@ -78,8 +84,11 @@ spec:
|
||||
-port={{ .Values.volume.port }} \
|
||||
{{- if .Values.volume.metricsPort }}
|
||||
-metricsPort {{ .Values.volume.metricsPort }} \
|
||||
{{- end }}}
|
||||
{{- end }}
|
||||
-dir={{ .Values.volume.dir }} \
|
||||
{{- if .Values.volume.dir_idx }}
|
||||
-dir.idx={{ .Values.volume.dir_idx }} \
|
||||
{{- end }}
|
||||
-max={{ .Values.volume.maxVolumes }} \
|
||||
{{- if .Values.volume.rack }}
|
||||
-rack={{ .Values.volume.rack }} \
|
||||
@@ -149,7 +158,7 @@ spec:
|
||||
periodSeconds: 90
|
||||
successThreshold: 1
|
||||
failureThreshold: 100
|
||||
timeoutSeconds: 5
|
||||
timeoutSeconds: 30
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /status
|
||||
@@ -159,7 +168,7 @@ spec:
|
||||
periodSeconds: 90
|
||||
successThreshold: 1
|
||||
failureThreshold: 4
|
||||
timeoutSeconds: 5
|
||||
timeoutSeconds: 30
|
||||
{{- if .Values.volume.resources }}
|
||||
resources:
|
||||
{{ tpl .Values.volume.resources . | nindent 12 | trim }}
|
||||
|
||||
@@ -4,7 +4,7 @@ global:
|
||||
registry: ""
|
||||
repository: ""
|
||||
imageName: chrislusf/seaweedfs
|
||||
imageTag: "2.06"
|
||||
# imageTag: "2.24" - started using {.Chart.appVersion}
|
||||
imagePullPolicy: IfNotPresent
|
||||
imagePullSecrets: imagepullsecret
|
||||
restartPolicy: Always
|
||||
@@ -21,6 +21,10 @@ global:
|
||||
# Y number of replica in other racks in the same data center
|
||||
# Z number of replica in other servers in the same rack
|
||||
replicationPlacment: "001"
|
||||
extraEnvironmentVars:
|
||||
WEED_CLUSTER_DEFAULT: "sw"
|
||||
WEED_CLUSTER_SW_MASTER: "seaweedfs-master:9333"
|
||||
WEED_CLUSTER_SW_FILER: "seaweedfs-filer-client:8888"
|
||||
|
||||
image:
|
||||
registry: ""
|
||||
@@ -140,6 +144,8 @@ volume:
|
||||
|
||||
# Directories to store data files. dir[,dir]... (default "/tmp")
|
||||
dir: "/data"
|
||||
# Directories to store index files. dir[,dir]... (default "/tmp")
|
||||
dir_idx: null
|
||||
|
||||
# Maximum numbers of volumes, count[,count]...
|
||||
# If set to zero on non-windows OS, the limit will be auto configured. (default "7")
|
||||
@@ -292,8 +298,10 @@ filer:
|
||||
WEED_MYSQL_HOSTNAME: "mysql-db-host"
|
||||
WEED_MYSQL_PORT: "3306"
|
||||
WEED_MYSQL_DATABASE: "sw_database"
|
||||
WEED_MYSQL_CONNECTION_MAX_IDLE: "10"
|
||||
WEED_MYSQL_CONNECTION_MAX_OPEN: "150"
|
||||
WEED_MYSQL_CONNECTION_MAX_IDLE: "5"
|
||||
WEED_MYSQL_CONNECTION_MAX_OPEN: "75"
|
||||
# "refresh" connection every 10 minutes, eliminating mysql closing "old" connections
|
||||
WEED_MYSQL_CONNECTION_MAX_LIFETIME_SECONDS: "600"
|
||||
# enable usage of memsql as filer backend
|
||||
WEED_MYSQL_INTERPOLATEPARAMS: "true"
|
||||
WEED_LEVELDB2_ENABLED: "false"
|
||||
@@ -302,10 +310,6 @@ filer:
|
||||
WEED_FILER_OPTIONS_RECURSIVE_DELETE: "false"
|
||||
# directories under this folder will be automatically creating a separate bucket
|
||||
WEED_FILER_BUCKETS_FOLDER: "/buckets"
|
||||
# directories under this folder will be store message queue data
|
||||
WEED_FILER_QUEUES_FOLDER: "/queues"
|
||||
# WEED_FILER_OPTIONS_BUCKETS_FSYNC a list of buckets names with all write requests fsync=true
|
||||
WEED_FILER_OPTIONS_BUCKETS_FSYNC: []
|
||||
|
||||
s3:
|
||||
enabled: true
|
||||
@@ -317,6 +321,8 @@ s3:
|
||||
port: 8333
|
||||
metricsPort: 9327
|
||||
loggingOverrideLevel: null
|
||||
#allow empty folders
|
||||
allowEmptyFolder: true
|
||||
|
||||
# Suffix of the host name, {bucket}.{domainName}
|
||||
domainName: ""
|
||||
@@ -347,6 +353,11 @@ s3:
|
||||
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
||||
priorityClassName: ""
|
||||
|
||||
logs:
|
||||
type: "hostPath"
|
||||
size: ""
|
||||
storageClass: ""
|
||||
|
||||
cronjob:
|
||||
enabled: false
|
||||
schedule: "*/7 * * * *"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.6.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
@@ -68,6 +68,11 @@
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<distributionManagement>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.6.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.6.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
@@ -68,6 +68,11 @@
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -15,7 +15,6 @@ public class ChunkCache {
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.weakValues()
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class FileChunkManifest {
|
||||
byte[] chunkData = SeaweedRead.chunkCache.getChunk(chunkView.fileId);
|
||||
if (chunkData == null) {
|
||||
LOG.debug("doFetchFullChunkData:{}", chunkView);
|
||||
chunkData = SeaweedRead.doFetchFullChunkData(chunkView, locations);
|
||||
chunkData = SeaweedRead.doFetchFullChunkData(filerGrpcClient, chunkView, locations);
|
||||
}
|
||||
if (chunk.getIsChunkManifest()){
|
||||
LOG.debug("chunk {} size {}", chunkView.fileId, chunkData.length);
|
||||
@@ -86,7 +86,7 @@ public class FileChunkManifest {
|
||||
}
|
||||
|
||||
public static List<FilerProto.FileChunk> maybeManifestize(
|
||||
final FilerGrpcClient filerGrpcClient, List<FilerProto.FileChunk> inputChunks) throws IOException {
|
||||
final FilerGrpcClient filerGrpcClient, List<FilerProto.FileChunk> inputChunks, String parentDirectory) throws IOException {
|
||||
// the return variable
|
||||
List<FilerProto.FileChunk> chunks = new ArrayList<>();
|
||||
|
||||
@@ -101,7 +101,7 @@ public class FileChunkManifest {
|
||||
|
||||
int remaining = dataChunks.size();
|
||||
for (int i = 0; i + mergeFactor < dataChunks.size(); i += mergeFactor) {
|
||||
FilerProto.FileChunk chunk = mergeIntoManifest(filerGrpcClient, dataChunks.subList(i, i + mergeFactor));
|
||||
FilerProto.FileChunk chunk = mergeIntoManifest(filerGrpcClient, dataChunks.subList(i, i + mergeFactor), parentDirectory);
|
||||
chunks.add(chunk);
|
||||
remaining -= mergeFactor;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public class FileChunkManifest {
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private static FilerProto.FileChunk mergeIntoManifest(final FilerGrpcClient filerGrpcClient, List<FilerProto.FileChunk> dataChunks) throws IOException {
|
||||
private static FilerProto.FileChunk mergeIntoManifest(final FilerGrpcClient filerGrpcClient, List<FilerProto.FileChunk> dataChunks, String parentDirectory) throws IOException {
|
||||
// create and serialize the manifest
|
||||
dataChunks = FilerClient.beforeEntrySerialization(dataChunks);
|
||||
FilerProto.FileChunkManifest.Builder m = FilerProto.FileChunkManifest.newBuilder().addAllChunks(dataChunks);
|
||||
@@ -130,7 +130,7 @@ public class FileChunkManifest {
|
||||
filerGrpcClient.getReplication(),
|
||||
filerGrpcClient,
|
||||
minOffset,
|
||||
data, 0, data.length);
|
||||
data, 0, data.length, parentDirectory);
|
||||
manifestChunk.setIsChunkManifest(true);
|
||||
manifestChunk.setSize(maxOffset - minOffset);
|
||||
return manifestChunk.build();
|
||||
|
||||
@@ -275,9 +275,9 @@ public class FilerClient {
|
||||
try {
|
||||
FilerProto.CreateEntryResponse createEntryResponse =
|
||||
filerGrpcClient.getBlockingStub().createEntry(FilerProto.CreateEntryRequest.newBuilder()
|
||||
.setDirectory(parent)
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
.setDirectory(parent)
|
||||
.setEntry(entry)
|
||||
.build());
|
||||
if (Strings.isNullOrEmpty(createEntryResponse.getError())) {
|
||||
return true;
|
||||
}
|
||||
@@ -333,4 +333,13 @@ public class FilerClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Iterator<FilerProto.SubscribeMetadataResponse> watch(String prefix, String clientName, long sinceNs) {
|
||||
return filerGrpcClient.getBlockingStub().subscribeMetadata(FilerProto.SubscribeMetadataRequest.newBuilder()
|
||||
.setPathPrefix(prefix)
|
||||
.setClientName(clientName)
|
||||
.setSinceNs(sinceNs)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FilerGrpcClient {
|
||||
@@ -26,6 +26,9 @@ public class FilerGrpcClient {
|
||||
}
|
||||
}
|
||||
|
||||
public final int VOLUME_SERVER_ACCESS_DIRECT = 0;
|
||||
public final int VOLUME_SERVER_ACCESS_PUBLIC_URL = 1;
|
||||
public final int VOLUME_SERVER_ACCESS_FILER_PROXY = 2;
|
||||
public final Map<String, FilerProto.Locations> vidLocations = new HashMap<>();
|
||||
private final ManagedChannel channel;
|
||||
private final SeaweedFilerGrpc.SeaweedFilerBlockingStub blockingStub;
|
||||
@@ -34,6 +37,8 @@ public class FilerGrpcClient {
|
||||
private boolean cipher = false;
|
||||
private String collection = "";
|
||||
private String replication = "";
|
||||
private int volumeServerAccess = VOLUME_SERVER_ACCESS_DIRECT;
|
||||
private String filerAddress;
|
||||
|
||||
public FilerGrpcClient(String host, int grpcPort) {
|
||||
this(host, grpcPort, sslContext);
|
||||
@@ -49,6 +54,8 @@ public class FilerGrpcClient {
|
||||
.negotiationType(NegotiationType.TLS)
|
||||
.sslContext(sslContext));
|
||||
|
||||
filerAddress = String.format("%s:%d", host, grpcPort - 10000);
|
||||
|
||||
FilerProto.GetFilerConfigurationResponse filerConfigurationResponse =
|
||||
this.getBlockingStub().getFilerConfiguration(
|
||||
FilerProto.GetFilerConfigurationRequest.newBuilder().build());
|
||||
@@ -58,7 +65,7 @@ public class FilerGrpcClient {
|
||||
|
||||
}
|
||||
|
||||
public FilerGrpcClient(ManagedChannelBuilder<?> channelBuilder) {
|
||||
private FilerGrpcClient(ManagedChannelBuilder<?> channelBuilder) {
|
||||
channel = channelBuilder.build();
|
||||
blockingStub = SeaweedFilerGrpc.newBlockingStub(channel);
|
||||
asyncStub = SeaweedFilerGrpc.newStub(channel);
|
||||
@@ -93,4 +100,39 @@ public class FilerGrpcClient {
|
||||
return futureStub;
|
||||
}
|
||||
|
||||
public void setAccessVolumeServerDirectly() {
|
||||
this.volumeServerAccess = VOLUME_SERVER_ACCESS_DIRECT;
|
||||
}
|
||||
|
||||
public boolean isAccessVolumeServerDirectly() {
|
||||
return this.volumeServerAccess == VOLUME_SERVER_ACCESS_DIRECT;
|
||||
}
|
||||
|
||||
public void setAccessVolumeServerByPublicUrl() {
|
||||
this.volumeServerAccess = VOLUME_SERVER_ACCESS_PUBLIC_URL;
|
||||
}
|
||||
|
||||
public boolean isAccessVolumeServerByPublicUrl() {
|
||||
return this.volumeServerAccess == VOLUME_SERVER_ACCESS_PUBLIC_URL;
|
||||
}
|
||||
|
||||
public void setAccessVolumeServerByFilerProxy() {
|
||||
this.volumeServerAccess = VOLUME_SERVER_ACCESS_FILER_PROXY;
|
||||
}
|
||||
|
||||
public boolean isAccessVolumeServerByFilerProxy() {
|
||||
return this.volumeServerAccess == VOLUME_SERVER_ACCESS_FILER_PROXY;
|
||||
}
|
||||
|
||||
public String getChunkUrl(String chunkId, String url, String publicUrl) {
|
||||
switch (this.volumeServerAccess) {
|
||||
case VOLUME_SERVER_ACCESS_PUBLIC_URL:
|
||||
return String.format("http://%s/%s", publicUrl, chunkId);
|
||||
case VOLUME_SERVER_ACCESS_FILER_PROXY:
|
||||
return String.format("http://%s/?proxyChunkId=%s", this.filerAddress, chunkId);
|
||||
default:
|
||||
return String.format("http://%s/%s", url, chunkId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+56
-87
@@ -1,26 +1,22 @@
|
||||
package seaweed.hdfs;
|
||||
package seaweedfs.client;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedRead;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public class SeaweedInputStream extends FSInputStream {
|
||||
public class SeaweedInputStream extends InputStream {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedInputStream.class);
|
||||
private static final IOException EXCEPTION_STREAM_IS_CLOSED = new IOException("Stream is closed!");
|
||||
|
||||
private final FilerGrpcClient filerGrpcClient;
|
||||
private final Statistics statistics;
|
||||
private final String path;
|
||||
private final FilerProto.Entry entry;
|
||||
private final List<SeaweedRead.VisibleInterval> visibleIntervalList;
|
||||
@@ -32,11 +28,26 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
|
||||
public SeaweedInputStream(
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String fullpath) throws IOException {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.path = fullpath;
|
||||
FilerClient filerClient = new FilerClient(filerGrpcClient);
|
||||
this.entry = filerClient.lookupEntry(
|
||||
SeaweedOutputStream.getParentDirectory(fullpath),
|
||||
SeaweedOutputStream.getFileName(fullpath));
|
||||
this.contentLength = SeaweedRead.fileSize(entry);
|
||||
|
||||
this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(filerGrpcClient, entry.getChunksList());
|
||||
|
||||
LOG.debug("new path:{} entry:{} visibleIntervalList:{}", path, entry, visibleIntervalList);
|
||||
|
||||
}
|
||||
|
||||
public SeaweedInputStream(
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final String path,
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.statistics = statistics;
|
||||
this.path = path;
|
||||
this.entry = entry;
|
||||
this.contentLength = SeaweedRead.fileSize(entry);
|
||||
@@ -63,14 +74,8 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
if (b == null) {
|
||||
throw new IllegalArgumentException("null byte array passed in to read() method");
|
||||
}
|
||||
@@ -84,53 +89,62 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
|
||||
}
|
||||
|
||||
long bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, b, off, len, SeaweedRead.fileSize(entry));
|
||||
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
|
||||
return read(buf);
|
||||
|
||||
}
|
||||
|
||||
// implement ByteBufferReadable
|
||||
public synchronized int read(ByteBuffer buf) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
|
||||
long bytesRead = 0;
|
||||
int len = buf.remaining();
|
||||
int start = (int) this.position;
|
||||
if (start+len <= entry.getContent().size()) {
|
||||
entry.getContent().substring(start, start+len).copyTo(buf);
|
||||
} else {
|
||||
bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, buf, SeaweedRead.fileSize(entry));
|
||||
}
|
||||
|
||||
if (bytesRead > Integer.MAX_VALUE) {
|
||||
throw new IOException("Unexpected Content-Length");
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
this.position += bytesRead;
|
||||
if (statistics != null) {
|
||||
statistics.incrementBytesRead(bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) bytesRead;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to given position in stream.
|
||||
*
|
||||
* @param n position to seek to
|
||||
* @throws IOException if there is an error
|
||||
* @throws EOFException if attempting to seek past end of file
|
||||
*/
|
||||
@Override
|
||||
public synchronized void seek(long n) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
throw EXCEPTION_STREAM_IS_CLOSED;
|
||||
}
|
||||
if (n < 0) {
|
||||
throw new EOFException(FSExceptionMessages.NEGATIVE_SEEK);
|
||||
throw new EOFException("Cannot seek to a negative offset");
|
||||
}
|
||||
if (n > contentLength) {
|
||||
throw new EOFException(FSExceptionMessages.CANNOT_SEEK_PAST_EOF);
|
||||
throw new EOFException("Attempted to seek or read past the end of the file");
|
||||
}
|
||||
|
||||
this.position = n;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long skip(long n) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
throw EXCEPTION_STREAM_IS_CLOSED;
|
||||
}
|
||||
if (this.position == contentLength) {
|
||||
if (n > 0) {
|
||||
throw new EOFException(FSExceptionMessages.CANNOT_SEEK_PAST_EOF);
|
||||
throw new EOFException("Attempted to seek or read past the end of the file");
|
||||
}
|
||||
}
|
||||
long newPos = this.position + n;
|
||||
@@ -158,10 +172,9 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
@Override
|
||||
public synchronized int available() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(
|
||||
FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
throw EXCEPTION_STREAM_IS_CLOSED;
|
||||
}
|
||||
final long remaining = this.contentLength - this.getPos();
|
||||
final long remaining = this.contentLength - this.position;
|
||||
return remaining <= Integer.MAX_VALUE
|
||||
? (int) remaining : Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -176,65 +189,21 @@ public class SeaweedInputStream extends FSInputStream {
|
||||
*/
|
||||
public long length() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
throw EXCEPTION_STREAM_IS_CLOSED;
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current offset from the start of the file
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPos() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
throw EXCEPTION_STREAM_IS_CLOSED;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks a different copy of the data. Returns true if
|
||||
* found a new source, false otherwise.
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public boolean seekToNewSource(long l) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*
|
||||
* @param readlimit ignored
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* gets whether mark and reset are supported by {@code ADLFileInputStream}. Always returns false.
|
||||
*
|
||||
* @return always {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+60
-24
@@ -1,16 +1,9 @@
|
||||
package seaweed.hdfs;
|
||||
package seaweedfs.client;
|
||||
|
||||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seaweedfs.client.ByteBufferPool;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedWrite;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
@@ -18,21 +11,19 @@ import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static seaweed.hdfs.SeaweedFileSystemStore.getParentDirectory;
|
||||
|
||||
public class SeaweedOutputStream extends OutputStream {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedOutputStream.class);
|
||||
|
||||
protected final boolean supportFlush = true;
|
||||
private final FilerGrpcClient filerGrpcClient;
|
||||
private final Path path;
|
||||
private final String path;
|
||||
private final int bufferSize;
|
||||
private final int maxConcurrentRequestCount;
|
||||
private final ThreadPoolExecutor threadExecutor;
|
||||
private final ExecutorCompletionService<Void> completionService;
|
||||
private final FilerProto.Entry.Builder entry;
|
||||
private final boolean supportFlush = false; // true;
|
||||
private final ConcurrentLinkedDeque<WriteOperation> writeOperations;
|
||||
private final boolean shouldSaveMetadata = false;
|
||||
private FilerProto.Entry.Builder entry;
|
||||
private long position;
|
||||
private boolean closed;
|
||||
private volatile IOException lastError;
|
||||
@@ -42,7 +33,15 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
private long outputIndex;
|
||||
private String replication = "000";
|
||||
|
||||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final Path path, FilerProto.Entry.Builder entry,
|
||||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final String fullpath) {
|
||||
this(filerGrpcClient, fullpath, "000");
|
||||
}
|
||||
|
||||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final String fullpath, final String replication) {
|
||||
this(filerGrpcClient, fullpath, null, 0, 8 * 1024 * 1024, "000");
|
||||
}
|
||||
|
||||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final String path, FilerProto.Entry.Builder entry,
|
||||
final long position, final int bufferSize, final String replication) {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.replication = replication;
|
||||
@@ -67,7 +66,45 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
this.completionService = new ExecutorCompletionService<>(this.threadExecutor);
|
||||
|
||||
this.entry = entry;
|
||||
if (this.entry == null) {
|
||||
long now = System.currentTimeMillis() / 1000L;
|
||||
|
||||
this.entry = FilerProto.Entry.newBuilder()
|
||||
.setName(getFileName(path))
|
||||
.setIsDirectory(false)
|
||||
.setAttributes(FilerProto.FuseAttributes.newBuilder()
|
||||
.setFileMode(0755)
|
||||
.setReplication(replication)
|
||||
.setCrtime(now)
|
||||
.setMtime(now)
|
||||
.clearGroupName()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String getParentDirectory(String path) {
|
||||
int protoIndex = path.indexOf("://");
|
||||
if (protoIndex >= 0) {
|
||||
int pathStart = path.indexOf("/", protoIndex+3);
|
||||
path = path.substring(pathStart);
|
||||
}
|
||||
if (path.equals("/")) {
|
||||
return path;
|
||||
}
|
||||
int lastSlashIndex = path.lastIndexOf("/");
|
||||
if (lastSlashIndex == 0) {
|
||||
return "/";
|
||||
}
|
||||
return path.substring(0, lastSlashIndex);
|
||||
}
|
||||
|
||||
public static String getFileName(String path) {
|
||||
if (path.indexOf("/") < 0) {
|
||||
return path;
|
||||
}
|
||||
int lastSlashIndex = path.lastIndexOf("/");
|
||||
return path.substring(lastSlashIndex + 1);
|
||||
}
|
||||
|
||||
private synchronized void flushWrittenBytesToServiceInternal(final long offset) throws IOException {
|
||||
@@ -89,7 +126,9 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
throws IOException {
|
||||
maybeThrowLastError();
|
||||
|
||||
Preconditions.checkArgument(data != null, "null data");
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (off < 0 || length < 0 || length > data.length - off) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
@@ -152,7 +191,7 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
flushInternal();
|
||||
threadExecutor.shutdown();
|
||||
} finally {
|
||||
lastError = new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
lastError = new IOException("Stream is closed!");
|
||||
ByteBufferPool.release(buffer);
|
||||
buffer = null;
|
||||
outputIndex = 0;
|
||||
@@ -162,6 +201,7 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
threadExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private synchronized void writeCurrentBufferToService() throws IOException {
|
||||
@@ -185,7 +225,7 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
}
|
||||
final Future<Void> job = completionService.submit(() -> {
|
||||
// System.out.println(path + " is going to save [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")");
|
||||
SeaweedWrite.writeData(entry, replication, filerGrpcClient, writePosition, bufferToWrite.array(), bufferToWrite.position(), bufferToWrite.limit());
|
||||
SeaweedWrite.writeData(entry, replication, filerGrpcClient, writePosition, bufferToWrite.array(), bufferToWrite.position(), bufferToWrite.limit(), path);
|
||||
// System.out.println(path + " saved [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")");
|
||||
ByteBufferPool.release(bufferToWrite);
|
||||
return null;
|
||||
@@ -239,13 +279,13 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void flushInternal() throws IOException {
|
||||
protected synchronized void flushInternal() throws IOException {
|
||||
maybeThrowLastError();
|
||||
writeCurrentBufferToService();
|
||||
flushWrittenBytesToService();
|
||||
}
|
||||
|
||||
private synchronized void flushInternalAsync() throws IOException {
|
||||
protected synchronized void flushInternalAsync() throws IOException {
|
||||
maybeThrowLastError();
|
||||
writeCurrentBufferToService();
|
||||
flushWrittenBytesToServiceAsync();
|
||||
@@ -278,10 +318,6 @@ public class SeaweedOutputStream extends OutputStream {
|
||||
private final long length;
|
||||
|
||||
WriteOperation(final Future<Void> task, final long startOffset, final long length) {
|
||||
Preconditions.checkNotNull(task, "task");
|
||||
Preconditions.checkArgument(startOffset >= 0, "startOffset");
|
||||
Preconditions.checkArgument(length >= 0, "length");
|
||||
|
||||
this.task = task;
|
||||
this.startOffset = startOffset;
|
||||
this.length = length;
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
public class SeaweedRead {
|
||||
@@ -19,24 +20,36 @@ public class SeaweedRead {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedRead.class);
|
||||
|
||||
static ChunkCache chunkCache = new ChunkCache(4);
|
||||
static VolumeIdCache volumeIdCache = new VolumeIdCache(4 * 1024);
|
||||
|
||||
// returns bytesRead
|
||||
public static long read(FilerGrpcClient filerGrpcClient, List<VisibleInterval> visibleIntervals,
|
||||
final long position, final byte[] buffer, final int bufferOffset,
|
||||
final int bufferLength, final long fileSize) throws IOException {
|
||||
final long position, final ByteBuffer buf, final long fileSize) throws IOException {
|
||||
|
||||
List<ChunkView> chunkViews = viewFromVisibles(visibleIntervals, position, bufferLength);
|
||||
List<ChunkView> chunkViews = viewFromVisibles(visibleIntervals, position, buf.remaining());
|
||||
|
||||
Map<String, FilerProto.Locations> knownLocations = new HashMap<>();
|
||||
|
||||
FilerProto.LookupVolumeRequest.Builder lookupRequest = FilerProto.LookupVolumeRequest.newBuilder();
|
||||
for (ChunkView chunkView : chunkViews) {
|
||||
String vid = parseVolumeId(chunkView.fileId);
|
||||
lookupRequest.addVolumeIds(vid);
|
||||
FilerProto.Locations locations = volumeIdCache.getLocations(vid);
|
||||
if (locations == null) {
|
||||
lookupRequest.addVolumeIds(vid);
|
||||
} else {
|
||||
knownLocations.put(vid, locations);
|
||||
}
|
||||
}
|
||||
|
||||
FilerProto.LookupVolumeResponse lookupResponse = filerGrpcClient
|
||||
.getBlockingStub().lookupVolume(lookupRequest.build());
|
||||
|
||||
Map<String, FilerProto.Locations> vid2Locations = lookupResponse.getLocationsMapMap();
|
||||
if (lookupRequest.getVolumeIdsCount() > 0) {
|
||||
FilerProto.LookupVolumeResponse lookupResponse = filerGrpcClient
|
||||
.getBlockingStub().lookupVolume(lookupRequest.build());
|
||||
Map<String, FilerProto.Locations> vid2Locations = lookupResponse.getLocationsMapMap();
|
||||
for (Map.Entry<String, FilerProto.Locations> entry : vid2Locations.entrySet()) {
|
||||
volumeIdCache.setLocations(entry.getKey(), entry.getValue());
|
||||
knownLocations.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
//TODO parallel this
|
||||
long readCount = 0;
|
||||
@@ -46,18 +59,19 @@ public class SeaweedRead {
|
||||
if (startOffset < chunkView.logicOffset) {
|
||||
long gap = chunkView.logicOffset - startOffset;
|
||||
LOG.debug("zero [{},{})", startOffset, startOffset + gap);
|
||||
buf.position(buf.position()+ (int)gap);
|
||||
readCount += gap;
|
||||
startOffset += gap;
|
||||
}
|
||||
|
||||
FilerProto.Locations locations = vid2Locations.get(parseVolumeId(chunkView.fileId));
|
||||
FilerProto.Locations locations = knownLocations.get(parseVolumeId(chunkView.fileId));
|
||||
if (locations == null || locations.getLocationsCount() == 0) {
|
||||
LOG.error("failed to locate {}", chunkView.fileId);
|
||||
// log here!
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = readChunkView(startOffset, buffer, bufferOffset + readCount, chunkView, locations);
|
||||
int len = readChunkView(filerGrpcClient, startOffset, buf, chunkView, locations);
|
||||
|
||||
LOG.debug("read [{},{}) {} size {}", startOffset, startOffset + len, chunkView.fileId, chunkView.size);
|
||||
|
||||
@@ -66,11 +80,12 @@ public class SeaweedRead {
|
||||
|
||||
}
|
||||
|
||||
long limit = Math.min(bufferOffset + bufferLength, fileSize);
|
||||
long limit = Math.min(buf.limit(), fileSize);
|
||||
|
||||
if (startOffset < limit) {
|
||||
long gap = limit - startOffset;
|
||||
LOG.debug("zero2 [{},{})", startOffset, startOffset + gap);
|
||||
buf.position(buf.position()+ (int)gap);
|
||||
readCount += gap;
|
||||
startOffset += gap;
|
||||
}
|
||||
@@ -78,30 +93,30 @@ public class SeaweedRead {
|
||||
return readCount;
|
||||
}
|
||||
|
||||
private static int readChunkView(long startOffset, byte[] buffer, long bufOffset, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
private static int readChunkView(FilerGrpcClient filerGrpcClient, long startOffset, ByteBuffer buf, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
|
||||
byte[] chunkData = chunkCache.getChunk(chunkView.fileId);
|
||||
|
||||
if (chunkData == null) {
|
||||
chunkData = doFetchFullChunkData(chunkView, locations);
|
||||
chunkData = doFetchFullChunkData(filerGrpcClient, chunkView, locations);
|
||||
chunkCache.setChunk(chunkView.fileId, chunkData);
|
||||
}
|
||||
|
||||
int len = (int) chunkView.size;
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} chunkView[{};{}) buf[{},{})/{} startOffset:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, chunkView.logicOffset, chunkView.logicOffset + chunkView.size, bufOffset, bufOffset + len, buffer.length, startOffset);
|
||||
System.arraycopy(chunkData, (int) (startOffset - chunkView.logicOffset + chunkView.offset), buffer, (int) bufOffset, len);
|
||||
LOG.debug("readChunkView fid:{} chunkData.length:{} chunkView.offset:{} chunkView[{};{}) startOffset:{}",
|
||||
chunkView.fileId, chunkData.length, chunkView.offset, chunkView.logicOffset, chunkView.logicOffset + chunkView.size, startOffset);
|
||||
buf.put(chunkData, (int) (startOffset - chunkView.logicOffset + chunkView.offset), len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
public static byte[] doFetchFullChunkData(ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
public static byte[] doFetchFullChunkData(FilerGrpcClient filerGrpcClient, ChunkView chunkView, FilerProto.Locations locations) throws IOException {
|
||||
|
||||
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);
|
||||
String url = filerGrpcClient.getChunkUrl(chunkView.fileId, location.getUrl(), location.getPublicUrl());
|
||||
try {
|
||||
data = doFetchOneFullChunkData(chunkView, url);
|
||||
lastException = null;
|
||||
@@ -130,7 +145,7 @@ public class SeaweedRead {
|
||||
|
||||
}
|
||||
|
||||
public static byte[] doFetchOneFullChunkData(ChunkView chunkView, String url) throws IOException {
|
||||
private static byte[] doFetchOneFullChunkData(ChunkView chunkView, String url) throws IOException {
|
||||
|
||||
HttpGet request = new HttpGet(url);
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ public class SeaweedWrite {
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final long offset,
|
||||
final byte[] bytes,
|
||||
final long bytesOffset, final long bytesLength) throws IOException {
|
||||
final long bytesOffset, final long bytesLength,
|
||||
final String path) throws IOException {
|
||||
FilerProto.FileChunk.Builder chunkBuilder = writeChunk(
|
||||
replication, filerGrpcClient, offset, bytes, bytesOffset, bytesLength);
|
||||
replication, filerGrpcClient, offset, bytes, bytesOffset, bytesLength, path);
|
||||
synchronized (entry) {
|
||||
entry.addChunks(chunkBuilder);
|
||||
}
|
||||
@@ -39,18 +40,20 @@ public class SeaweedWrite {
|
||||
final long offset,
|
||||
final byte[] bytes,
|
||||
final long bytesOffset,
|
||||
final long bytesLength) throws IOException {
|
||||
final long bytesLength,
|
||||
final String path) throws IOException {
|
||||
FilerProto.AssignVolumeResponse response = filerGrpcClient.getBlockingStub().assignVolume(
|
||||
FilerProto.AssignVolumeRequest.newBuilder()
|
||||
.setCollection(filerGrpcClient.getCollection())
|
||||
.setReplication(replication == null ? filerGrpcClient.getReplication() : replication)
|
||||
.setDataCenter("")
|
||||
.setTtlSec(0)
|
||||
.setPath(path)
|
||||
.build());
|
||||
String fileId = response.getFileId();
|
||||
String url = response.getUrl();
|
||||
String auth = response.getAuth();
|
||||
String targetUrl = String.format("http://%s/%s", url, fileId);
|
||||
|
||||
String targetUrl = filerGrpcClient.getChunkUrl(fileId, response.getUrl(), response.getPublicUrl());
|
||||
|
||||
ByteString cipherKeyString = com.google.protobuf.ByteString.EMPTY;
|
||||
byte[] cipherKey = null;
|
||||
@@ -77,7 +80,7 @@ public class SeaweedWrite {
|
||||
final FilerProto.Entry.Builder entry) throws IOException {
|
||||
|
||||
synchronized (entry) {
|
||||
List<FilerProto.FileChunk> chunks = FileChunkManifest.maybeManifestize(filerGrpcClient, entry.getChunksList());
|
||||
List<FilerProto.FileChunk> chunks = FileChunkManifest.maybeManifestize(filerGrpcClient, entry.getChunksList(), parentDirectory);
|
||||
entry.clearChunks();
|
||||
entry.addAllChunks(chunks);
|
||||
filerGrpcClient.getBlockingStub().createEntry(
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package seaweedfs.client;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VolumeIdCache {
|
||||
|
||||
private Cache<String, FilerProto.Locations> cache = null;
|
||||
|
||||
public VolumeIdCache(int maxEntries) {
|
||||
if (maxEntries == 0) {
|
||||
return;
|
||||
}
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(maxEntries)
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public FilerProto.Locations getLocations(String volumeId) {
|
||||
if (this.cache == null) {
|
||||
return null;
|
||||
}
|
||||
return this.cache.getIfPresent(volumeId);
|
||||
}
|
||||
|
||||
public void setLocations(String volumeId, FilerProto.Locations locations) {
|
||||
if (this.cache == null) {
|
||||
return;
|
||||
}
|
||||
this.cache.put(volumeId, locations);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -100,6 +100,7 @@ message Entry {
|
||||
map<string, bytes> extended = 5;
|
||||
bytes hard_link_id = 7;
|
||||
int32 hard_link_counter = 8; // only exists in hard link meta data
|
||||
bytes content = 9; // if not empty, the file content
|
||||
}
|
||||
|
||||
message FullEntry {
|
||||
@@ -217,7 +218,7 @@ message AssignVolumeRequest {
|
||||
string replication = 3;
|
||||
int32 ttl_sec = 4;
|
||||
string data_center = 5;
|
||||
string parent_path = 6;
|
||||
string path = 6;
|
||||
string rack = 7;
|
||||
}
|
||||
|
||||
@@ -348,3 +349,23 @@ message KvPutRequest {
|
||||
message KvPutResponse {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
// path-based configurations
|
||||
message FilerConf {
|
||||
int32 version = 1;
|
||||
message PathConf {
|
||||
string location_prefix = 1;
|
||||
string collection = 2;
|
||||
string replication = 3;
|
||||
string ttl = 4;
|
||||
enum DiskType {
|
||||
NONE = 0;
|
||||
HDD = 1;
|
||||
SSD = 2;
|
||||
}
|
||||
DiskType disk_type = 5;
|
||||
bool fsync = 6;
|
||||
uint32 volume_growth_count = 7;
|
||||
}
|
||||
repeated PathConf locations = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>unzip</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-client</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chrislusf</groupId>
|
||||
<artifactId>seaweedfs-hadoop2-client</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hadoop</groupId>
|
||||
<artifactId>hadoop-common</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.SeaweedInputStream;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ExampleReadFile {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
FilerGrpcClient filerGrpcClient = new FilerGrpcClient("localhost", 18888);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
parseZip("/Users/chris/tmp/test.zip");
|
||||
|
||||
long startTime2 = System.currentTimeMillis();
|
||||
|
||||
long localProcessTime = startTime2 - startTime;
|
||||
|
||||
SeaweedInputStream seaweedInputStream = new SeaweedInputStream(
|
||||
filerGrpcClient, "/test.zip");
|
||||
parseZip(seaweedInputStream);
|
||||
|
||||
long swProcessTime = System.currentTimeMillis() - startTime2;
|
||||
|
||||
System.out.println("Local time: " + localProcessTime);
|
||||
System.out.println("SeaweedFS time: " + swProcessTime);
|
||||
|
||||
}
|
||||
|
||||
public static void parseZip(String filename) throws IOException {
|
||||
FileInputStream fileInputStream = new FileInputStream(filename);
|
||||
parseZip(fileInputStream);
|
||||
}
|
||||
|
||||
public static void parseZip(InputStream is) throws IOException {
|
||||
ZipInputStream zin = new ZipInputStream(is);
|
||||
ZipEntry ze;
|
||||
while ((ze = zin.getNextEntry()) != null) {
|
||||
System.out.println(ze.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import seaweedfs.client.FilerClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class ExampleWatchFileChanges {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
FilerClient filerClient = new FilerClient("localhost", 18888);
|
||||
|
||||
long sinceNs = (System.currentTimeMillis() - 3600 * 1000) * 1000000L;
|
||||
|
||||
Iterator<FilerProto.SubscribeMetadataResponse> watch = filerClient.watch(
|
||||
"/buckets",
|
||||
"exampleClientName",
|
||||
sinceNs
|
||||
);
|
||||
|
||||
System.out.println("Connected to filer, subscribing from " + new Date());
|
||||
|
||||
while (watch.hasNext()) {
|
||||
FilerProto.SubscribeMetadataResponse event = watch.next();
|
||||
FilerProto.EventNotification notification = event.getEventNotification();
|
||||
if (!event.getDirectory().equals(notification.getNewParentPath())) {
|
||||
// move an entry to a new directory, possibly with a new name
|
||||
if (notification.hasOldEntry() && notification.hasNewEntry()) {
|
||||
System.out.println("moved " + event.getDirectory() + "/" + notification.getOldEntry().getName() + " to " + notification.getNewParentPath() + "/" + notification.getNewEntry().getName());
|
||||
} else {
|
||||
System.out.println("this should not happen.");
|
||||
}
|
||||
} else if (notification.hasNewEntry() && !notification.hasOldEntry()) {
|
||||
System.out.println("created entry " + event.getDirectory() + "/" + notification.getNewEntry().getName());
|
||||
} else if (!notification.hasNewEntry() && notification.hasOldEntry()) {
|
||||
System.out.println("deleted entry " + event.getDirectory() + "/" + notification.getOldEntry().getName());
|
||||
} else if (notification.hasNewEntry() && notification.hasOldEntry()) {
|
||||
System.out.println("updated entry " + event.getDirectory() + "/" + notification.getNewEntry().getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.SeaweedInputStream;
|
||||
import seaweedfs.client.SeaweedOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ExampleWriteFile {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
FilerGrpcClient filerGrpcClient = new FilerGrpcClient("localhost", 18888);
|
||||
|
||||
SeaweedInputStream seaweedInputStream = new SeaweedInputStream(
|
||||
filerGrpcClient, "/test.zip");
|
||||
unZipFiles(filerGrpcClient, seaweedInputStream);
|
||||
|
||||
}
|
||||
|
||||
public static void unZipFiles(FilerGrpcClient filerGrpcClient, InputStream is) throws IOException {
|
||||
ZipInputStream zin = new ZipInputStream(is);
|
||||
ZipEntry ze;
|
||||
while ((ze = zin.getNextEntry()) != null) {
|
||||
|
||||
String filename = ze.getName();
|
||||
if (filename.indexOf("/") >= 0) {
|
||||
filename = filename.substring(filename.lastIndexOf("/") + 1);
|
||||
}
|
||||
if (filename.length()==0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SeaweedOutputStream seaweedOutputStream = new SeaweedOutputStream(filerGrpcClient, "/test/"+filename);
|
||||
byte[] bytesIn = new byte[16 * 1024];
|
||||
int read = 0;
|
||||
while ((read = zin.read(bytesIn))!=-1) {
|
||||
seaweedOutputStream.write(bytesIn,0,read);
|
||||
}
|
||||
seaweedOutputStream.close();
|
||||
|
||||
System.out.println(ze.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.seaweedfs.examples;
|
||||
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.fs.FileSystem;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.hadoop.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class HdfsCopyFile {
|
||||
public static void main(String[] args) throws IOException {
|
||||
Configuration configuration = new Configuration();
|
||||
|
||||
configuration.set("fs.defaultFS", "seaweedfs://localhost:8888");
|
||||
configuration.set("fs.seaweedfs.impl", "seaweed.hdfs.SeaweedFileSystem");
|
||||
|
||||
FileSystem fs = FileSystem.get(configuration);
|
||||
String source = "/Users/chris/tmp/test.zip";
|
||||
String destination = "/buckets/spark/test01.zip";
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(source));
|
||||
|
||||
OutputStream out = fs.create(new Path(destination));
|
||||
IOUtils.copyBytes(in, out, 4096, true);
|
||||
}
|
||||
}
|
||||
@@ -301,7 +301,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.1</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.6.0</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.1</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.6.0</seaweedfs.client.version>
|
||||
<hadoop.version>2.9.2</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
import org.apache.hadoop.fs.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BufferedByteBufferReadableInputStream extends BufferedFSInputStream implements ByteBufferReadable {
|
||||
|
||||
public BufferedByteBufferReadableInputStream(FSInputStream in, int size) {
|
||||
super(in, size);
|
||||
if (!(in instanceof Seekable) || !(in instanceof PositionedReadable)) {
|
||||
throw new IllegalArgumentException("In is not an instance of Seekable or PositionedReadable");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer buf) throws IOException {
|
||||
if (this.in instanceof ByteBufferReadable) {
|
||||
return ((ByteBufferReadable)this.in).read(buf);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Byte-buffer read unsupported by input stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
public static final String FS_SEAWEED_FILER_PORT = "fs.seaweed.filer.port";
|
||||
public static final int FS_SEAWEED_DEFAULT_PORT = 8888;
|
||||
public static final String FS_SEAWEED_BUFFER_SIZE = "fs.seaweed.buffer.size";
|
||||
public static final String FS_SEAWEED_REPLICATION = "fs.seaweed.replication";
|
||||
public static final String FS_SEAWEED_VOLUME_SERVER_ACCESS = "fs.seaweed.volume.server.access";
|
||||
public static final int FS_SEAWEED_DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystem.class);
|
||||
@@ -65,6 +67,12 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.seaweedFileSystemStore.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSDataInputStream open(Path path, int bufferSize) throws IOException {
|
||||
|
||||
@@ -75,7 +83,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
return new FSDataInputStream(new BufferedByteBufferReadableInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
return null;
|
||||
@@ -91,7 +99,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
path = qualify(path);
|
||||
|
||||
try {
|
||||
String replicaPlacement = String.format("%03d", replication - 1);
|
||||
String replicaPlacement = this.getConf().get(FS_SEAWEED_REPLICATION, String.format("%03d", replication - 1));
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
OutputStream outputStream = seaweedFileSystemStore.createFile(path, overwrite, permission, seaweedBufferSize, replicaPlacement);
|
||||
return new FSDataOutputStream(outputStream, statistics);
|
||||
|
||||
@@ -18,8 +18,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static seaweed.hdfs.SeaweedFileSystem.FS_SEAWEED_BUFFER_SIZE;
|
||||
import static seaweed.hdfs.SeaweedFileSystem.FS_SEAWEED_DEFAULT_BUFFER_SIZE;
|
||||
import static seaweed.hdfs.SeaweedFileSystem.*;
|
||||
|
||||
public class SeaweedFileSystemStore {
|
||||
|
||||
@@ -34,6 +33,21 @@ public class SeaweedFileSystemStore {
|
||||
filerGrpcClient = new FilerGrpcClient(host, grpcPort);
|
||||
filerClient = new FilerClient(filerGrpcClient);
|
||||
this.conf = conf;
|
||||
String volumeServerAccessMode = this.conf.get(FS_SEAWEED_VOLUME_SERVER_ACCESS, "direct");
|
||||
if (volumeServerAccessMode.equals("publicUrl")) {
|
||||
filerGrpcClient.setAccessVolumeServerByPublicUrl();
|
||||
} else if (volumeServerAccessMode.equals("filerProxy")) {
|
||||
filerGrpcClient.setAccessVolumeServerByFilerProxy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.filerGrpcClient.shutdown();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getParentDirectory(Path path) {
|
||||
@@ -185,6 +199,7 @@ public class SeaweedFileSystemStore {
|
||||
if (existingEntry != null) {
|
||||
entry = FilerProto.Entry.newBuilder();
|
||||
entry.mergeFrom(existingEntry);
|
||||
entry.clearContent();
|
||||
entry.getAttributesBuilder().setMtime(now);
|
||||
LOG.debug("createFile merged entry path:{} entry:{} from:{}", path, entry, existingEntry);
|
||||
writePosition = SeaweedRead.fileSize(existingEntry);
|
||||
@@ -207,7 +222,7 @@ public class SeaweedFileSystemStore {
|
||||
SeaweedWrite.writeMeta(filerGrpcClient, getParentDirectory(path), entry);
|
||||
}
|
||||
|
||||
return new SeaweedOutputStream(filerGrpcClient, path, entry, writePosition, bufferSize, replication);
|
||||
return new SeaweedHadoopOutputStream(filerGrpcClient, path.toString(), entry, writePosition, bufferSize, replication);
|
||||
|
||||
}
|
||||
|
||||
@@ -221,7 +236,7 @@ public class SeaweedFileSystemStore {
|
||||
throw new FileNotFoundException("read non-exist file " + path);
|
||||
}
|
||||
|
||||
return new SeaweedInputStream(filerGrpcClient,
|
||||
return new SeaweedHadoopInputStream(filerGrpcClient,
|
||||
statistics,
|
||||
path.toUri().getPath(),
|
||||
entry);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.ByteBufferReadable;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedInputStream;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SeaweedHadoopInputStream extends FSInputStream implements ByteBufferReadable {
|
||||
|
||||
private final SeaweedInputStream seaweedInputStream;
|
||||
private final Statistics statistics;
|
||||
|
||||
public SeaweedHadoopInputStream(
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String path,
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.seaweedInputStream = new SeaweedInputStream(filerGrpcClient, path, entry);
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return seaweedInputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
return seaweedInputStream.read(b, off, len);
|
||||
}
|
||||
|
||||
// implement ByteBufferReadable
|
||||
@Override
|
||||
public synchronized int read(ByteBuffer buf) throws IOException {
|
||||
int bytesRead = seaweedInputStream.read(buf);
|
||||
|
||||
if (bytesRead > 0) {
|
||||
if (statistics != null) {
|
||||
statistics.incrementBytesRead(bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to given position in stream.
|
||||
*
|
||||
* @param n position to seek to
|
||||
* @throws IOException if there is an error
|
||||
* @throws EOFException if attempting to seek past end of file
|
||||
*/
|
||||
@Override
|
||||
public synchronized void seek(long n) throws IOException {
|
||||
seaweedInputStream.seek(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long skip(long n) throws IOException {
|
||||
return seaweedInputStream.skip(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the remaining available bytes
|
||||
* if the size is less than or equal to {@link Integer#MAX_VALUE},
|
||||
* otherwise, return {@link Integer#MAX_VALUE}.
|
||||
* <p>
|
||||
* This is to match the behavior of DFSInputStream.available(),
|
||||
* which some clients may rely on (HBase write-ahead log reading in
|
||||
* particular).
|
||||
*/
|
||||
@Override
|
||||
public synchronized int available() throws IOException {
|
||||
return seaweedInputStream.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the file that this stream refers to. Note that the length returned is the length
|
||||
* as of the time the Stream was opened. Specifically, if there have been subsequent appends to the file,
|
||||
* they wont be reflected in the returned length.
|
||||
*
|
||||
* @return length of the file.
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
public long length() throws IOException {
|
||||
return seaweedInputStream.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current offset from the start of the file
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPos() throws IOException {
|
||||
return seaweedInputStream.getPos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks a different copy of the data. Returns true if
|
||||
* found a new source, false otherwise.
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public boolean seekToNewSource(long l) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
seaweedInputStream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*
|
||||
* @param readlimit ignored
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* gets whether mark and reset are supported by {@code ADLFileInputStream}. Always returns false.
|
||||
*
|
||||
* @return always {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream
|
||||
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedOutputStream;
|
||||
|
||||
public class SeaweedHadoopOutputStream extends SeaweedOutputStream {
|
||||
|
||||
public SeaweedHadoopOutputStream(FilerGrpcClient filerGrpcClient, final String path, FilerProto.Entry.Builder entry,
|
||||
final long position, final int bufferSize, final String replication) {
|
||||
super(filerGrpcClient, path.toString(), entry, position, bufferSize, replication);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedRead;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class SeaweedInputStream extends FSInputStream {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedInputStream.class);
|
||||
|
||||
private final FilerGrpcClient filerGrpcClient;
|
||||
private final Statistics statistics;
|
||||
private final String path;
|
||||
private final FilerProto.Entry entry;
|
||||
private final List<SeaweedRead.VisibleInterval> visibleIntervalList;
|
||||
private final long contentLength;
|
||||
|
||||
private long position = 0; // cursor of the file
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
public SeaweedInputStream(
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String path,
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.statistics = statistics;
|
||||
this.path = path;
|
||||
this.entry = entry;
|
||||
this.contentLength = SeaweedRead.fileSize(entry);
|
||||
|
||||
this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(filerGrpcClient, entry.getChunksList());
|
||||
|
||||
LOG.debug("new path:{} entry:{} visibleIntervalList:{}", path, entry, visibleIntervalList);
|
||||
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
int numberOfBytesRead = read(b, 0, 1);
|
||||
if (numberOfBytesRead < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return (b[0] & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("attempting to read from negative offset");
|
||||
}
|
||||
if (position >= contentLength) {
|
||||
return -1; // Hadoop prefers -1 to EOFException
|
||||
}
|
||||
if (b == null) {
|
||||
throw new IllegalArgumentException("null byte array passed in to read() method");
|
||||
}
|
||||
if (off >= b.length) {
|
||||
throw new IllegalArgumentException("offset greater than length of array");
|
||||
}
|
||||
if (len < 0) {
|
||||
throw new IllegalArgumentException("requested read length is less than zero");
|
||||
}
|
||||
if (len > (b.length - off)) {
|
||||
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
|
||||
}
|
||||
|
||||
long bytesRead = SeaweedRead.read(this.filerGrpcClient, this.visibleIntervalList, this.position, b, off, len, SeaweedRead.fileSize(entry));
|
||||
if (bytesRead > Integer.MAX_VALUE) {
|
||||
throw new IOException("Unexpected Content-Length");
|
||||
}
|
||||
|
||||
if (bytesRead > 0) {
|
||||
this.position += bytesRead;
|
||||
if (statistics != null) {
|
||||
statistics.incrementBytesRead(bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) bytesRead;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to given position in stream.
|
||||
*
|
||||
* @param n position to seek to
|
||||
* @throws IOException if there is an error
|
||||
* @throws EOFException if attempting to seek past end of file
|
||||
*/
|
||||
@Override
|
||||
public synchronized void seek(long n) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
}
|
||||
if (n < 0) {
|
||||
throw new EOFException(FSExceptionMessages.NEGATIVE_SEEK);
|
||||
}
|
||||
if (n > contentLength) {
|
||||
throw new EOFException(FSExceptionMessages.CANNOT_SEEK_PAST_EOF);
|
||||
}
|
||||
|
||||
this.position = n;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long skip(long n) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
}
|
||||
if (this.position == contentLength) {
|
||||
if (n > 0) {
|
||||
throw new EOFException(FSExceptionMessages.CANNOT_SEEK_PAST_EOF);
|
||||
}
|
||||
}
|
||||
long newPos = this.position + n;
|
||||
if (newPos < 0) {
|
||||
newPos = 0;
|
||||
n = newPos - this.position;
|
||||
}
|
||||
if (newPos > contentLength) {
|
||||
newPos = contentLength;
|
||||
n = newPos - this.position;
|
||||
}
|
||||
seek(newPos);
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the remaining available bytes
|
||||
* if the size is less than or equal to {@link Integer#MAX_VALUE},
|
||||
* otherwise, return {@link Integer#MAX_VALUE}.
|
||||
* <p>
|
||||
* This is to match the behavior of DFSInputStream.available(),
|
||||
* which some clients may rely on (HBase write-ahead log reading in
|
||||
* particular).
|
||||
*/
|
||||
@Override
|
||||
public synchronized int available() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(
|
||||
FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
}
|
||||
final long remaining = this.contentLength - this.getPos();
|
||||
return remaining <= Integer.MAX_VALUE
|
||||
? (int) remaining : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the file that this stream refers to. Note that the length returned is the length
|
||||
* as of the time the Stream was opened. Specifically, if there have been subsequent appends to the file,
|
||||
* they wont be reflected in the returned length.
|
||||
*
|
||||
* @return length of the file.
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
public long length() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current offset from the start of the file
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPos() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks a different copy of the data. Returns true if
|
||||
* found a new source, false otherwise.
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public boolean seekToNewSource(long l) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*
|
||||
* @param readlimit ignored
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* gets whether mark and reset are supported by {@code ADLFileInputStream}. Always returns false.
|
||||
*
|
||||
* @return always {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -309,7 +309,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.1</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.6.0</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<seaweedfs.client.version>1.5.1</seaweedfs.client.version>
|
||||
<seaweedfs.client.version>1.6.0</seaweedfs.client.version>
|
||||
<hadoop.version>3.1.1</hadoop.version>
|
||||
</properties>
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
import org.apache.hadoop.fs.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BufferedByteBufferReadableInputStream extends BufferedFSInputStream implements ByteBufferReadable {
|
||||
|
||||
public BufferedByteBufferReadableInputStream(FSInputStream in, int size) {
|
||||
super(in, size);
|
||||
if (!(in instanceof Seekable) || !(in instanceof PositionedReadable)) {
|
||||
throw new IllegalArgumentException("In is not an instance of Seekable or PositionedReadable");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer buf) throws IOException {
|
||||
if (this.in instanceof ByteBufferReadable) {
|
||||
return ((ByteBufferReadable)this.in).read(buf);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Byte-buffer read unsupported by input stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
public static final String FS_SEAWEED_FILER_PORT = "fs.seaweed.filer.port";
|
||||
public static final int FS_SEAWEED_DEFAULT_PORT = 8888;
|
||||
public static final String FS_SEAWEED_BUFFER_SIZE = "fs.seaweed.buffer.size";
|
||||
public static final String FS_SEAWEED_REPLICATION = "fs.seaweed.replication";
|
||||
public static final String FS_SEAWEED_VOLUME_SERVER_ACCESS = "fs.seaweed.volume.server.access";
|
||||
public static final int FS_SEAWEED_DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystem.class);
|
||||
@@ -65,6 +67,12 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.seaweedFileSystemStore.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSDataInputStream open(Path path, int bufferSize) throws IOException {
|
||||
|
||||
@@ -75,7 +83,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
try {
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
FSInputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics);
|
||||
return new FSDataInputStream(new BufferedFSInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
return new FSDataInputStream(new BufferedByteBufferReadableInputStream(inputStream, 4 * seaweedBufferSize));
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("open path: {} bufferSize:{}", path, bufferSize, ex);
|
||||
return null;
|
||||
@@ -91,7 +99,7 @@ public class SeaweedFileSystem extends FileSystem {
|
||||
path = qualify(path);
|
||||
|
||||
try {
|
||||
String replicaPlacement = String.format("%03d", replication - 1);
|
||||
String replicaPlacement = this.getConf().get(FS_SEAWEED_REPLICATION, String.format("%03d", replication - 1));
|
||||
int seaweedBufferSize = this.getConf().getInt(FS_SEAWEED_BUFFER_SIZE, FS_SEAWEED_DEFAULT_BUFFER_SIZE);
|
||||
OutputStream outputStream = seaweedFileSystemStore.createFile(path, overwrite, permission, seaweedBufferSize, replicaPlacement);
|
||||
return new FSDataOutputStream(outputStream, statistics);
|
||||
|
||||
@@ -18,8 +18,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static seaweed.hdfs.SeaweedFileSystem.FS_SEAWEED_BUFFER_SIZE;
|
||||
import static seaweed.hdfs.SeaweedFileSystem.FS_SEAWEED_DEFAULT_BUFFER_SIZE;
|
||||
import static seaweed.hdfs.SeaweedFileSystem.*;
|
||||
|
||||
public class SeaweedFileSystemStore {
|
||||
|
||||
@@ -34,6 +33,21 @@ public class SeaweedFileSystemStore {
|
||||
filerGrpcClient = new FilerGrpcClient(host, grpcPort);
|
||||
filerClient = new FilerClient(filerGrpcClient);
|
||||
this.conf = conf;
|
||||
String volumeServerAccessMode = this.conf.get(FS_SEAWEED_VOLUME_SERVER_ACCESS, "direct");
|
||||
if (volumeServerAccessMode.equals("publicUrl")) {
|
||||
filerGrpcClient.setAccessVolumeServerByPublicUrl();
|
||||
} else if (volumeServerAccessMode.equals("filerProxy")) {
|
||||
filerGrpcClient.setAccessVolumeServerByFilerProxy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.filerGrpcClient.shutdown();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getParentDirectory(Path path) {
|
||||
@@ -185,6 +199,7 @@ public class SeaweedFileSystemStore {
|
||||
if (existingEntry != null) {
|
||||
entry = FilerProto.Entry.newBuilder();
|
||||
entry.mergeFrom(existingEntry);
|
||||
entry.clearContent();
|
||||
entry.getAttributesBuilder().setMtime(now);
|
||||
LOG.debug("createFile merged entry path:{} entry:{} from:{}", path, entry, existingEntry);
|
||||
writePosition = SeaweedRead.fileSize(existingEntry);
|
||||
@@ -207,7 +222,7 @@ public class SeaweedFileSystemStore {
|
||||
SeaweedWrite.writeMeta(filerGrpcClient, getParentDirectory(path), entry);
|
||||
}
|
||||
|
||||
return new SeaweedOutputStream(filerGrpcClient, path, entry, writePosition, bufferSize, replication);
|
||||
return new SeaweedHadoopOutputStream(filerGrpcClient, path.toString(), entry, writePosition, bufferSize, replication);
|
||||
|
||||
}
|
||||
|
||||
@@ -221,7 +236,7 @@ public class SeaweedFileSystemStore {
|
||||
throw new FileNotFoundException("read non-exist file " + path);
|
||||
}
|
||||
|
||||
return new SeaweedInputStream(filerGrpcClient,
|
||||
return new SeaweedHadoopInputStream(filerGrpcClient,
|
||||
statistics,
|
||||
path.toUri().getPath(),
|
||||
entry);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
|
||||
|
||||
import org.apache.hadoop.fs.ByteBufferReadable;
|
||||
import org.apache.hadoop.fs.FSInputStream;
|
||||
import org.apache.hadoop.fs.FileSystem.Statistics;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedInputStream;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SeaweedHadoopInputStream extends FSInputStream implements ByteBufferReadable {
|
||||
|
||||
private final SeaweedInputStream seaweedInputStream;
|
||||
private final Statistics statistics;
|
||||
|
||||
public SeaweedHadoopInputStream(
|
||||
final FilerGrpcClient filerGrpcClient,
|
||||
final Statistics statistics,
|
||||
final String path,
|
||||
final FilerProto.Entry entry) throws IOException {
|
||||
this.seaweedInputStream = new SeaweedInputStream(filerGrpcClient, path, entry);
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return seaweedInputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
return seaweedInputStream.read(b, off, len);
|
||||
}
|
||||
|
||||
// implement ByteBufferReadable
|
||||
@Override
|
||||
public synchronized int read(ByteBuffer buf) throws IOException {
|
||||
int bytesRead = seaweedInputStream.read(buf);
|
||||
|
||||
if (bytesRead > 0) {
|
||||
if (statistics != null) {
|
||||
statistics.incrementBytesRead(bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to given position in stream.
|
||||
*
|
||||
* @param n position to seek to
|
||||
* @throws IOException if there is an error
|
||||
* @throws EOFException if attempting to seek past end of file
|
||||
*/
|
||||
@Override
|
||||
public synchronized void seek(long n) throws IOException {
|
||||
seaweedInputStream.seek(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long skip(long n) throws IOException {
|
||||
return seaweedInputStream.skip(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the remaining available bytes
|
||||
* if the size is less than or equal to {@link Integer#MAX_VALUE},
|
||||
* otherwise, return {@link Integer#MAX_VALUE}.
|
||||
* <p>
|
||||
* This is to match the behavior of DFSInputStream.available(),
|
||||
* which some clients may rely on (HBase write-ahead log reading in
|
||||
* particular).
|
||||
*/
|
||||
@Override
|
||||
public synchronized int available() throws IOException {
|
||||
return seaweedInputStream.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the file that this stream refers to. Note that the length returned is the length
|
||||
* as of the time the Stream was opened. Specifically, if there have been subsequent appends to the file,
|
||||
* they wont be reflected in the returned length.
|
||||
*
|
||||
* @return length of the file.
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
public long length() throws IOException {
|
||||
return seaweedInputStream.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current offset from the start of the file
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPos() throws IOException {
|
||||
return seaweedInputStream.getPos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks a different copy of the data. Returns true if
|
||||
* found a new source, false otherwise.
|
||||
*
|
||||
* @throws IOException throws {@link IOException} if there is an error
|
||||
*/
|
||||
@Override
|
||||
public boolean seekToNewSource(long l) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
seaweedInputStream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*
|
||||
* @param readlimit ignored
|
||||
*/
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported by this stream. Throws {@link UnsupportedOperationException}
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new UnsupportedOperationException("mark()/reset() not supported on this stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* gets whether mark and reset are supported by {@code ADLFileInputStream}. Always returns false.
|
||||
*
|
||||
* @return always {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream
|
||||
|
||||
import org.apache.hadoop.fs.StreamCapabilities;
|
||||
import org.apache.hadoop.fs.Syncable;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SeaweedHadoopOutputStream extends SeaweedOutputStream implements Syncable, StreamCapabilities {
|
||||
|
||||
public SeaweedHadoopOutputStream(FilerGrpcClient filerGrpcClient, final String path, FilerProto.Entry.Builder entry,
|
||||
final long position, final int bufferSize, final String replication) {
|
||||
super(filerGrpcClient, path, entry, position, bufferSize, replication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to posix fsync, flush out the data in client's user buffer
|
||||
* all the way to the disk device (but the disk may have it in its cache).
|
||||
*
|
||||
* @throws IOException if error occurs
|
||||
*/
|
||||
@Override
|
||||
public void hsync() throws IOException {
|
||||
if (supportFlush) {
|
||||
flushInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush out the data in client's user buffer. After the return of
|
||||
* this call, new readers will see the data.
|
||||
*
|
||||
* @throws IOException if any error occurs
|
||||
*/
|
||||
@Override
|
||||
public void hflush() throws IOException {
|
||||
if (supportFlush) {
|
||||
flushInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the stream for a specific capability.
|
||||
*
|
||||
* @param capability string to query the stream support for.
|
||||
* @return true for hsync and hflush.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasCapability(String capability) {
|
||||
switch (capability.toLowerCase(Locale.ENGLISH)) {
|
||||
case StreamCapabilities.HSYNC:
|
||||
case StreamCapabilities.HFLUSH:
|
||||
return supportFlush;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
package seaweed.hdfs;
|
||||
|
||||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.hadoop.fs.FSExceptionMessages;
|
||||
import org.apache.hadoop.fs.Path;
|
||||
import org.apache.hadoop.fs.StreamCapabilities;
|
||||
import org.apache.hadoop.fs.Syncable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seaweedfs.client.ByteBufferPool;
|
||||
import seaweedfs.client.FilerGrpcClient;
|
||||
import seaweedfs.client.FilerProto;
|
||||
import seaweedfs.client.SeaweedWrite;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static seaweed.hdfs.SeaweedFileSystemStore.getParentDirectory;
|
||||
|
||||
public class SeaweedOutputStream extends OutputStream implements Syncable, StreamCapabilities {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedOutputStream.class);
|
||||
|
||||
private final FilerGrpcClient filerGrpcClient;
|
||||
private final Path path;
|
||||
private final int bufferSize;
|
||||
private final int maxConcurrentRequestCount;
|
||||
private final ThreadPoolExecutor threadExecutor;
|
||||
private final ExecutorCompletionService<Void> completionService;
|
||||
private final FilerProto.Entry.Builder entry;
|
||||
private final boolean supportFlush = false; // true;
|
||||
private final ConcurrentLinkedDeque<WriteOperation> writeOperations;
|
||||
private long position;
|
||||
private boolean closed;
|
||||
private volatile IOException lastError;
|
||||
private long lastFlushOffset;
|
||||
private long lastTotalAppendOffset = 0;
|
||||
private ByteBuffer buffer;
|
||||
private long outputIndex;
|
||||
private String replication = "000";
|
||||
|
||||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final Path path, FilerProto.Entry.Builder entry,
|
||||
final long position, final int bufferSize, final String replication) {
|
||||
this.filerGrpcClient = filerGrpcClient;
|
||||
this.replication = replication;
|
||||
this.path = path;
|
||||
this.position = position;
|
||||
this.closed = false;
|
||||
this.lastError = null;
|
||||
this.lastFlushOffset = 0;
|
||||
this.bufferSize = bufferSize;
|
||||
this.buffer = ByteBufferPool.request(bufferSize);
|
||||
this.outputIndex = 0;
|
||||
this.writeOperations = new ConcurrentLinkedDeque<>();
|
||||
|
||||
this.maxConcurrentRequestCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
this.threadExecutor
|
||||
= new ThreadPoolExecutor(maxConcurrentRequestCount,
|
||||
maxConcurrentRequestCount,
|
||||
120L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
this.completionService = new ExecutorCompletionService<>(this.threadExecutor);
|
||||
|
||||
this.entry = entry;
|
||||
|
||||
}
|
||||
|
||||
private synchronized void flushWrittenBytesToServiceInternal(final long offset) throws IOException {
|
||||
try {
|
||||
SeaweedWrite.writeMeta(filerGrpcClient, getParentDirectory(path), entry);
|
||||
} catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
this.lastFlushOffset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int byteVal) throws IOException {
|
||||
write(new byte[]{(byte) (byteVal & 0xFF)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(final byte[] data, final int off, final int length)
|
||||
throws IOException {
|
||||
maybeThrowLastError();
|
||||
|
||||
Preconditions.checkArgument(data != null, "null data");
|
||||
|
||||
if (off < 0 || length < 0 || length > data.length - off) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
// System.out.println(path + " write [" + (outputIndex + off) + "," + ((outputIndex + off) + length) + ")");
|
||||
|
||||
int currentOffset = off;
|
||||
int writableBytes = bufferSize - buffer.position();
|
||||
int numberOfBytesToWrite = length;
|
||||
|
||||
while (numberOfBytesToWrite > 0) {
|
||||
|
||||
if (numberOfBytesToWrite < writableBytes) {
|
||||
buffer.put(data, currentOffset, numberOfBytesToWrite);
|
||||
outputIndex += numberOfBytesToWrite;
|
||||
break;
|
||||
}
|
||||
|
||||
// System.out.println(path + " [" + (outputIndex + currentOffset) + "," + ((outputIndex + currentOffset) + writableBytes) + ") " + buffer.capacity());
|
||||
buffer.put(data, currentOffset, writableBytes);
|
||||
outputIndex += writableBytes;
|
||||
currentOffset += writableBytes;
|
||||
writeCurrentBufferToService();
|
||||
numberOfBytesToWrite = numberOfBytesToWrite - writableBytes;
|
||||
writableBytes = bufferSize - buffer.position();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes this output stream and forces any buffered output bytes to be
|
||||
* written out. If any data remains in the payload it is committed to the
|
||||
* service. Data is queued for writing and forced out to the service
|
||||
* before the call returns.
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (supportFlush) {
|
||||
flushInternalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to posix fsync, flush out the data in client's user buffer
|
||||
* all the way to the disk device (but the disk may have it in its cache).
|
||||
*
|
||||
* @throws IOException if error occurs
|
||||
*/
|
||||
@Override
|
||||
public void hsync() throws IOException {
|
||||
if (supportFlush) {
|
||||
flushInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush out the data in client's user buffer. After the return of
|
||||
* this call, new readers will see the data.
|
||||
*
|
||||
* @throws IOException if any error occurs
|
||||
*/
|
||||
@Override
|
||||
public void hflush() throws IOException {
|
||||
if (supportFlush) {
|
||||
flushInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the stream for a specific capability.
|
||||
*
|
||||
* @param capability string to query the stream support for.
|
||||
* @return true for hsync and hflush.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasCapability(String capability) {
|
||||
switch (capability.toLowerCase(Locale.ENGLISH)) {
|
||||
case StreamCapabilities.HSYNC:
|
||||
case StreamCapabilities.HFLUSH:
|
||||
return supportFlush;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all data in the output stream to be written to Azure storage.
|
||||
* Wait to return until this is complete. Close the access to the stream and
|
||||
* shutdown the upload thread pool.
|
||||
* If the blob was created, its lease will be released.
|
||||
* Any error encountered caught in threads and stored will be rethrown here
|
||||
* after cleanup.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("close path: {}", path);
|
||||
try {
|
||||
flushInternal();
|
||||
threadExecutor.shutdown();
|
||||
} finally {
|
||||
lastError = new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
|
||||
ByteBufferPool.release(buffer);
|
||||
buffer = null;
|
||||
outputIndex = 0;
|
||||
closed = true;
|
||||
writeOperations.clear();
|
||||
if (!threadExecutor.isShutdown()) {
|
||||
threadExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void writeCurrentBufferToService() throws IOException {
|
||||
if (buffer.position() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
position += submitWriteBufferToService(buffer, position);
|
||||
|
||||
buffer = ByteBufferPool.request(bufferSize);
|
||||
|
||||
}
|
||||
|
||||
private synchronized int submitWriteBufferToService(final ByteBuffer bufferToWrite, final long writePosition) throws IOException {
|
||||
|
||||
bufferToWrite.flip();
|
||||
int bytesLength = bufferToWrite.limit() - bufferToWrite.position();
|
||||
|
||||
if (threadExecutor.getQueue().size() >= maxConcurrentRequestCount) {
|
||||
waitForTaskToComplete();
|
||||
}
|
||||
final Future<Void> job = completionService.submit(() -> {
|
||||
// System.out.println(path + " is going to save [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")");
|
||||
SeaweedWrite.writeData(entry, replication, filerGrpcClient, writePosition, bufferToWrite.array(), bufferToWrite.position(), bufferToWrite.limit());
|
||||
// System.out.println(path + " saved [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")");
|
||||
ByteBufferPool.release(bufferToWrite);
|
||||
return null;
|
||||
});
|
||||
|
||||
writeOperations.add(new WriteOperation(job, writePosition, bytesLength));
|
||||
|
||||
// Try to shrink the queue
|
||||
shrinkWriteOperationQueue();
|
||||
|
||||
return bytesLength;
|
||||
|
||||
}
|
||||
|
||||
private void waitForTaskToComplete() throws IOException {
|
||||
boolean completed;
|
||||
for (completed = false; completionService.poll() != null; completed = true) {
|
||||
// keep polling until there is no data
|
||||
}
|
||||
|
||||
if (!completed) {
|
||||
try {
|
||||
completionService.take();
|
||||
} catch (InterruptedException e) {
|
||||
lastError = (IOException) new InterruptedIOException(e.toString()).initCause(e);
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeThrowLastError() throws IOException {
|
||||
if (lastError != null) {
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to remove the completed write operations from the beginning of write
|
||||
* operation FIFO queue.
|
||||
*/
|
||||
private synchronized void shrinkWriteOperationQueue() throws IOException {
|
||||
try {
|
||||
while (writeOperations.peek() != null && writeOperations.peek().task.isDone()) {
|
||||
writeOperations.peek().task.get();
|
||||
lastTotalAppendOffset += writeOperations.peek().length;
|
||||
writeOperations.remove();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
lastError = new IOException(e);
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void flushInternal() throws IOException {
|
||||
maybeThrowLastError();
|
||||
writeCurrentBufferToService();
|
||||
flushWrittenBytesToService();
|
||||
}
|
||||
|
||||
private synchronized void flushInternalAsync() throws IOException {
|
||||
maybeThrowLastError();
|
||||
writeCurrentBufferToService();
|
||||
flushWrittenBytesToServiceAsync();
|
||||
}
|
||||
|
||||
private synchronized void flushWrittenBytesToService() throws IOException {
|
||||
for (WriteOperation writeOperation : writeOperations) {
|
||||
try {
|
||||
writeOperation.task.get();
|
||||
} catch (Exception ex) {
|
||||
lastError = new IOException(ex);
|
||||
throw lastError;
|
||||
}
|
||||
}
|
||||
LOG.debug("flushWrittenBytesToService: {} position:{}", path, position);
|
||||
flushWrittenBytesToServiceInternal(position);
|
||||
}
|
||||
|
||||
private synchronized void flushWrittenBytesToServiceAsync() throws IOException {
|
||||
shrinkWriteOperationQueue();
|
||||
|
||||
if (this.lastTotalAppendOffset > this.lastFlushOffset) {
|
||||
this.flushWrittenBytesToServiceInternal(this.lastTotalAppendOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WriteOperation {
|
||||
private final Future<Void> task;
|
||||
private final long startOffset;
|
||||
private final long length;
|
||||
|
||||
WriteOperation(final Future<Void> task, final long startOffset, final long length) {
|
||||
Preconditions.checkNotNull(task, "task");
|
||||
Preconditions.checkArgument(startOffset >= 0, "startOffset");
|
||||
Preconditions.checkArgument(length >= 0, "length");
|
||||
|
||||
this.task = task;
|
||||
this.startOffset = startOffset;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -386,7 +386,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_filer_request_total[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_filer_request_total[1m])",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "{{type}}",
|
||||
@@ -761,7 +761,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_s3_request_total[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_s3_request_total[1m])",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "{{type}}",
|
||||
@@ -1853,4 +1853,4 @@
|
||||
"timezone": "browser",
|
||||
"title": "SeaweedFS",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\",type=~\"$method\"}[1m]) * 5) by (code)",
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\",type=~\"$method\"}[1m])) by (code)",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -210,7 +210,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m]) * 5) by (type)",
|
||||
"expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m])) by (type)",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -1014,7 +1014,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "rate(SeaweedFS_filer_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m]) * 5",
|
||||
"expr": "rate(SeaweedFS_filer_request_total{namespace=\"$namespace\",service=~\"$service-api\"}[1m])",
|
||||
"format": "time_series",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
@@ -2359,4 +2359,4 @@
|
||||
"timezone": "browser",
|
||||
"title": "SeaweedFS",
|
||||
"version": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ func getVolumeFiles(v uint32, addr string) (map[types.NeedleId]needleState, int6
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
if actual := offset.ToAcutalOffset(); actual > maxOffset {
|
||||
if actual := offset.ToActualOffset(); actual > maxOffset {
|
||||
maxOffset = actual
|
||||
}
|
||||
return nil
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
hsbench -a accesstoken -s secret -z 4K -d 10 -t 10 -b 10 -u http://localhost:8333 -m "cxipgdx" -bp "hsbench-"
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
minSize = flag.Int("minSize", 1024, "min file size")
|
||||
maxSize = flag.Int("maxSize", 3*1024*1024, "max file size")
|
||||
fileCount = flag.Int("n", 1, "number of files to write")
|
||||
blockSize = flag.Int("blockSizeKB", 4, "write block size")
|
||||
toDir = flag.String("dir", ".", "destination directory")
|
||||
)
|
||||
|
||||
func check(e error) {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
block := make([]byte, *blockSize*1024)
|
||||
|
||||
for i := 0; i < *fileCount; i++ {
|
||||
|
||||
f, err := os.Create(fmt.Sprintf("%s/file%05d", *toDir, i))
|
||||
check(err)
|
||||
|
||||
fileSize := *minSize + rand.Intn(*maxSize-*minSize)
|
||||
startTime := time.Now()
|
||||
|
||||
fmt.Printf("write %s %d bytes: ", f.Name(), fileSize)
|
||||
|
||||
for x := 0; x < fileSize; {
|
||||
rand.Read(block)
|
||||
_, err = f.Write(block)
|
||||
check(err)
|
||||
x += len(block)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
check(err)
|
||||
|
||||
fmt.Printf("%.02f MB/sec\n", float64(fileSize)*float64(time.Second)/float64(time.Now().Sub(startTime)*1024*1024))
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -20,7 +20,7 @@ debug_mount:
|
||||
|
||||
debug_server:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- server -dir=/Volumes/mobile_disk/99 -filer -volume.port=8343 -s3 -volume.max=0
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- server -dir=/Volumes/mobile_disk/99 -filer -volume.port=8343 -s3 -volume.max=0 -master.volumeSizeLimitMB=1024 -volume.preStopSeconds=1
|
||||
|
||||
debug_volume:
|
||||
go build -gcflags="all=-N -l"
|
||||
@@ -29,3 +29,7 @@ debug_volume:
|
||||
debug_webdav:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- -v=4 webdav
|
||||
|
||||
debug_s3:
|
||||
go build -gcflags="all=-N -l"
|
||||
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- -v=4 s3
|
||||
|
||||
@@ -112,7 +112,7 @@ func runBackup(cmd *Command, args []string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
v, err := storage.NewVolume(util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*s.dir), util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating or reading from volume %d: %v\n", vid, err)
|
||||
return true
|
||||
@@ -137,7 +137,7 @@ func runBackup(cmd *Command, args []string) bool {
|
||||
// remove the old data
|
||||
v.Destroy()
|
||||
// recreate an empty volume
|
||||
v, err = storage.NewVolume(util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
v, err = storage.NewVolume(util.ResolvePath(*s.dir), util.ResolvePath(*s.dir), *s.collection, vid, storage.NeedleMapInMemory, replication, ttl, 0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating or reading from volume %d: %v\n", vid, err)
|
||||
return true
|
||||
|
||||
@@ -125,7 +125,7 @@ func runBenchmark(cmd *Command, args []string) bool {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
b.masterClient = wdclient.NewMasterClient(b.grpcDialOption, "client", "", 0, strings.Split(*b.masters, ","))
|
||||
b.masterClient = wdclient.NewMasterClient(b.grpcDialOption, "client", "", 0, "", strings.Split(*b.masters, ","))
|
||||
go b.masterClient.KeepConnectedToMaster()
|
||||
b.masterClient.WaitUntilConnected()
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ var Commands = []*Command{
|
||||
cmdDownload,
|
||||
cmdExport,
|
||||
cmdFiler,
|
||||
cmdFilerCat,
|
||||
cmdFilerMetaTail,
|
||||
cmdFilerReplicate,
|
||||
cmdFilerSynchronize,
|
||||
cmdFix,
|
||||
@@ -25,7 +27,6 @@ var Commands = []*Command{
|
||||
cmdScaffold,
|
||||
cmdServer,
|
||||
cmdShell,
|
||||
cmdWatch,
|
||||
cmdUpload,
|
||||
cmdVersion,
|
||||
cmdVolume,
|
||||
|
||||
@@ -41,8 +41,7 @@ func runCompact(cmd *Command, args []string) bool {
|
||||
preallocate := *compactVolumePreallocate * (1 << 20)
|
||||
|
||||
vid := needle.VolumeId(*compactVolumeId)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*compactVolumePath), *compactVolumeCollection, vid,
|
||||
storage.NeedleMapInMemory, nil, nil, preallocate, 0)
|
||||
v, err := storage.NewVolume(util.ResolvePath(*compactVolumePath), util.ResolvePath(*compactVolumePath), *compactVolumeCollection, vid, storage.NeedleMapInMemory, nil, nil, preallocate, 0)
|
||||
if err != nil {
|
||||
glog.Fatalf("Load Volume [ERROR] %s\n", err)
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func (scanner *VolumeFileScanner4Export) VisitNeedle(n *needle.Needle, offset in
|
||||
nv, ok := needleMap.Get(n.Id)
|
||||
glog.V(3).Infof("key %d offset %d size %d disk_size %d compressed %v ok %v nv %+v",
|
||||
n.Id, offset, n.Size, n.DiskSize(scanner.version), n.IsCompressed(), ok, nv)
|
||||
if *showDeleted && n.Size > 0 || ok && nv.Size.IsValid() && nv.Offset.ToAcutalOffset() == offset {
|
||||
if *showDeleted && n.Size > 0 || ok && nv.Size.IsValid() && nv.Offset.ToActualOffset() == offset {
|
||||
if newerThanUnix >= 0 && n.HasLastModifiedDate() && n.LastModified < uint64(newerThanUnix) {
|
||||
glog.V(3).Infof("Skipping this file, as it's old enough: LastModified %d vs %d",
|
||||
n.LastModified, newerThanUnix)
|
||||
|
||||
+33
-11
@@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,9 +20,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
f FilerOptions
|
||||
filerStartS3 *bool
|
||||
filerS3Options S3Options
|
||||
f FilerOptions
|
||||
filerStartS3 *bool
|
||||
filerS3Options S3Options
|
||||
filerStartWebDav *bool
|
||||
filerWebDavOptions WebDavOption
|
||||
)
|
||||
|
||||
type FilerOptions struct {
|
||||
@@ -42,8 +45,7 @@ type FilerOptions struct {
|
||||
cipher *bool
|
||||
peers *string
|
||||
metricsHttpPort *int
|
||||
|
||||
// default leveldb directory, used in "weed server" mode
|
||||
saveToFilerLimit *int
|
||||
defaultLevelDbDirectory *string
|
||||
}
|
||||
|
||||
@@ -59,12 +61,14 @@ func init() {
|
||||
f.disableDirListing = cmdFiler.Flag.Bool("disableDirListing", false, "turn off directory listing")
|
||||
f.maxMB = cmdFiler.Flag.Int("maxMB", 32, "split files larger than the limit")
|
||||
f.dirListingLimit = cmdFiler.Flag.Int("dirListLimit", 100000, "limit sub dir listing size")
|
||||
f.dataCenter = cmdFiler.Flag.String("dataCenter", "", "prefer to write to volumes in this data center")
|
||||
f.dataCenter = cmdFiler.Flag.String("dataCenter", "", "prefer to read and write to volumes in this data center")
|
||||
f.rack = cmdFiler.Flag.String("rack", "", "prefer to write to volumes in this rack")
|
||||
f.disableHttp = cmdFiler.Flag.Bool("disableHttp", false, "disable http request, only gRpc operations are allowed")
|
||||
f.cipher = cmdFiler.Flag.Bool("encryptVolumeData", false, "encrypt data on volume servers")
|
||||
f.peers = cmdFiler.Flag.String("peers", "", "all filers sharing the same filer store in comma separated ip:port list")
|
||||
f.metricsHttpPort = cmdFiler.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
f.saveToFilerLimit = cmdFiler.Flag.Int("saveToFilerLimit", 0, "files smaller than this limit will be saved in filer store")
|
||||
f.defaultLevelDbDirectory = cmdFiler.Flag.String("defaultStoreDir", ".", "if filer.toml is empty, use an embedded filer store in the directory")
|
||||
|
||||
// start s3 on filer
|
||||
filerStartS3 = cmdFiler.Flag.Bool("s3", false, "whether to start S3 gateway")
|
||||
@@ -73,6 +77,16 @@ func init() {
|
||||
filerS3Options.tlsPrivateKey = cmdFiler.Flag.String("s3.key.file", "", "path to the TLS private key file")
|
||||
filerS3Options.tlsCertificate = cmdFiler.Flag.String("s3.cert.file", "", "path to the TLS certificate file")
|
||||
filerS3Options.config = cmdFiler.Flag.String("s3.config", "", "path to the config file")
|
||||
filerS3Options.allowEmptyFolder = cmdFiler.Flag.Bool("s3.allowEmptyFolder", false, "allow empty folders")
|
||||
|
||||
// start webdav on filer
|
||||
filerStartWebDav = cmdFiler.Flag.Bool("webdav", false, "whether to start webdav gateway")
|
||||
filerWebDavOptions.port = cmdFiler.Flag.Int("webdav.port", 7333, "webdav server http listen port")
|
||||
filerWebDavOptions.collection = cmdFiler.Flag.String("webdav.collection", "", "collection to create the files")
|
||||
filerWebDavOptions.tlsPrivateKey = cmdFiler.Flag.String("webdav.key.file", "", "path to the TLS private key file")
|
||||
filerWebDavOptions.tlsCertificate = cmdFiler.Flag.String("webdav.cert.file", "", "path to the TLS certificate file")
|
||||
filerWebDavOptions.cacheDir = cmdFiler.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks")
|
||||
filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB")
|
||||
}
|
||||
|
||||
var cmdFiler = &Command{
|
||||
@@ -89,7 +103,8 @@ var cmdFiler = &Command{
|
||||
//return a json format subdirectory and files listing
|
||||
GET /path/to/
|
||||
|
||||
The configuration file "filer.toml" is read from ".", "$HOME/.seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
The configuration file "filer.toml" is read from ".", "$HOME/.seaweedfs/", "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
If the "filer.toml" is not found, an embedded filer store will be craeted under "-defaultStoreDir".
|
||||
|
||||
The example filer.toml configuration file can be generated by "weed scaffold -config=filer"
|
||||
|
||||
@@ -111,6 +126,15 @@ func runFiler(cmd *Command, args []string) bool {
|
||||
}()
|
||||
}
|
||||
|
||||
if *filerStartWebDav {
|
||||
filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port)
|
||||
filerWebDavOptions.filer = &filerAddress
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
filerWebDavOptions.startWebDav()
|
||||
}()
|
||||
}
|
||||
|
||||
f.startFiler()
|
||||
|
||||
return true
|
||||
@@ -125,10 +149,7 @@ func (fo *FilerOptions) startFiler() {
|
||||
publicVolumeMux = http.NewServeMux()
|
||||
}
|
||||
|
||||
defaultLevelDbDirectory := "./filerldb2"
|
||||
if fo.defaultLevelDbDirectory != nil {
|
||||
defaultLevelDbDirectory = util.ResolvePath(*fo.defaultLevelDbDirectory + "/filerldb2")
|
||||
}
|
||||
defaultLevelDbDirectory := util.ResolvePath(*fo.defaultLevelDbDirectory + "/filerldb2")
|
||||
|
||||
var peers []string
|
||||
if *fo.peers != "" {
|
||||
@@ -149,6 +170,7 @@ func (fo *FilerOptions) startFiler() {
|
||||
Host: *fo.ip,
|
||||
Port: uint32(*fo.port),
|
||||
Cipher: *fo.cipher,
|
||||
SaveToFilerLimit: *fo.saveToFilerLimit,
|
||||
Filers: peers,
|
||||
})
|
||||
if nfs_err != nil {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||
"google.golang.org/grpc"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/security"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
var (
|
||||
filerCat FilerCatOptions
|
||||
)
|
||||
|
||||
type FilerCatOptions struct {
|
||||
grpcDialOption grpc.DialOption
|
||||
filerAddress string
|
||||
filerClient filer_pb.SeaweedFilerClient
|
||||
output *string
|
||||
}
|
||||
|
||||
func (fco *FilerCatOptions) GetLookupFileIdFunction() wdclient.LookupFileIdFunctionType {
|
||||
return func(fileId string) (targetUrls []string, err error) {
|
||||
vid := filer.VolumeId(fileId)
|
||||
resp, err := fco.filerClient.LookupVolume(context.Background(), &filer_pb.LookupVolumeRequest{
|
||||
VolumeIds: []string{vid},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locations := resp.LocationsMap[vid]
|
||||
for _, loc := range locations.Locations {
|
||||
targetUrls = append(targetUrls, fmt.Sprintf("http://%s/%s", loc.Url, fileId))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdFilerCat.Run = runFilerCat // break init cycle
|
||||
filerCat.output = cmdFilerCat.Flag.String("o", "", "write to file instead of stdout")
|
||||
}
|
||||
|
||||
var cmdFilerCat = &Command{
|
||||
UsageLine: "filer.cat [-o <file>] http://localhost:8888/path/to/file",
|
||||
Short: "copy one file to local",
|
||||
Long: `read one file to stdout or write to a file
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
func runFilerCat(cmd *Command, args []string) bool {
|
||||
|
||||
util.LoadConfiguration("security", false)
|
||||
|
||||
if len(args) == 0 {
|
||||
return false
|
||||
}
|
||||
filerSource := args[len(args)-1]
|
||||
|
||||
filerUrl, err := url.Parse(filerSource)
|
||||
if err != nil {
|
||||
fmt.Printf("The last argument should be a URL on filer: %v\n", err)
|
||||
return false
|
||||
}
|
||||
urlPath := filerUrl.Path
|
||||
if strings.HasSuffix(urlPath, "/") {
|
||||
fmt.Printf("The last argument should be a file: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
filerCat.filerAddress = filerUrl.Host
|
||||
filerCat.grpcDialOption = security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||
|
||||
dir, name := util.FullPath(urlPath).DirAndName()
|
||||
|
||||
writer := os.Stdout
|
||||
if *filerCat.output != "" {
|
||||
|
||||
fmt.Printf("saving %s to %s\n", filerSource, *filerCat.output)
|
||||
|
||||
f, err := os.OpenFile(*filerCat.output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
fmt.Printf("open file %s: %v\n", *filerCat.output, err)
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
writer = f
|
||||
}
|
||||
|
||||
pb.WithFilerClient(filerCat.filerAddress, filerCat.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.LookupDirectoryEntryRequest{
|
||||
Name: name,
|
||||
Directory: dir,
|
||||
}
|
||||
respLookupEntry, err := filer_pb.LookupEntry(client, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filerCat.filerClient = client
|
||||
|
||||
return filer.StreamContent(&filerCat, writer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64)
|
||||
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func runCopy(cmd *Command, args []string) bool {
|
||||
}
|
||||
urlPath := filerUrl.Path
|
||||
if !strings.HasSuffix(urlPath, "/") {
|
||||
fmt.Printf("The last argument should be a folder and end with \"/\": %v\n", err)
|
||||
fmt.Printf("The last argument should be a folder and end with \"/\"\n")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -116,13 +116,13 @@ func runCopy(cmd *Command, args []string) bool {
|
||||
fmt.Printf("read from filer %s: %v\n", filerGrpcAddress, err)
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(urlPath, dirBuckets) {
|
||||
if strings.HasPrefix(urlPath, dirBuckets+"/") {
|
||||
restPath := urlPath[len(dirBuckets)+1:]
|
||||
if strings.Index(restPath, "/") > 0 {
|
||||
expectedBucket := restPath[:strings.Index(restPath, "/")]
|
||||
if *copy.collection == "" {
|
||||
*copy.collection = expectedBucket
|
||||
} else {
|
||||
} else if *copy.collection != expectedBucket {
|
||||
fmt.Printf("destination %s uses collection \"%s\": unexpected collection \"%v\"\n", urlPath, expectedBucket, *copy.collection)
|
||||
return true
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func runCopy(cmd *Command, args []string) bool {
|
||||
defer close(fileCopyTaskChan)
|
||||
for _, fileOrDir := range fileOrDirs {
|
||||
if err := genFileCopyTask(fileOrDir, urlPath, fileCopyTaskChan); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gen file list error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "genFileCopyTask : %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi
|
||||
|
||||
fi, err := os.Stat(fileOrDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get stat for file %s: %v\n", fileOrDir, err)
|
||||
fmt.Fprintf(os.Stderr, "Error: read file %s: %v\n", fileOrDir, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err
|
||||
Replication: *worker.options.replication,
|
||||
Collection: *worker.options.collection,
|
||||
TtlSec: worker.options.ttlSec,
|
||||
ParentPath: task.destinationUrlPath,
|
||||
Path: task.destinationUrlPath,
|
||||
}
|
||||
|
||||
assignResult, assignError = client.AssignVolume(context.Background(), request)
|
||||
@@ -405,7 +405,7 @@ func (worker *FileCopyWorker) uploadFileInChunks(task FileCopyTask, f *os.File,
|
||||
Replication: *worker.options.replication,
|
||||
Collection: *worker.options.collection,
|
||||
TtlSec: worker.options.ttlSec,
|
||||
ParentPath: task.destinationUrlPath,
|
||||
Path: task.destinationUrlPath + fileName,
|
||||
}
|
||||
|
||||
assignResult, assignError = client.AssignVolume(context.Background(), request)
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/olivere/elastic/v7"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/security"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdFilerMetaTail.Run = runFilerMetaTail // break init cycle
|
||||
}
|
||||
|
||||
var cmdFilerMetaTail = &Command{
|
||||
UsageLine: "filer.meta.tail [-filer=localhost:8888] [-target=/]",
|
||||
Short: "see recent changes on a filer",
|
||||
Long: `See recent changes on a filer.
|
||||
|
||||
weed filer.meta.tail -timeAgo=30h | grep truncate
|
||||
weed filer.meta.tail -timeAgo=30h | jq .
|
||||
weed filer.meta.tail -timeAgo=30h | jq .eventNotification.newEntry.name
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
var (
|
||||
tailFiler = cmdFilerMetaTail.Flag.String("filer", "localhost:8888", "filer hostname:port")
|
||||
tailTarget = cmdFilerMetaTail.Flag.String("pathPrefix", "/", "path to a folder or file, or common prefix for the folders or files on filer")
|
||||
tailStart = cmdFilerMetaTail.Flag.Duration("timeAgo", 0, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
tailPattern = cmdFilerMetaTail.Flag.String("pattern", "", "full path or just filename pattern, ex: \"/home/?opher\", \"*.pdf\", see https://golang.org/pkg/path/filepath/#Match ")
|
||||
esServers = cmdFilerMetaTail.Flag.String("es", "", "comma-separated elastic servers http://<host:port>")
|
||||
esIndex = cmdFilerMetaTail.Flag.String("es.index", "seaweedfs", "ES index name")
|
||||
)
|
||||
|
||||
func runFilerMetaTail(cmd *Command, args []string) bool {
|
||||
|
||||
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||
|
||||
var filterFunc func(dir, fname string) bool
|
||||
if *tailPattern != "" {
|
||||
if strings.Contains(*tailPattern, "/") {
|
||||
println("watch path pattern", *tailPattern)
|
||||
filterFunc = func(dir, fname string) bool {
|
||||
matched, err := filepath.Match(*tailPattern, dir+"/"+fname)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
return matched
|
||||
}
|
||||
} else {
|
||||
println("watch file pattern", *tailPattern)
|
||||
filterFunc = func(dir, fname string) bool {
|
||||
matched, err := filepath.Match(*tailPattern, fname)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
return matched
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldPrint := func(resp *filer_pb.SubscribeMetadataResponse) bool {
|
||||
if filterFunc == nil {
|
||||
return true
|
||||
}
|
||||
if resp.EventNotification.OldEntry == nil && resp.EventNotification.NewEntry == nil {
|
||||
return false
|
||||
}
|
||||
if resp.EventNotification.OldEntry != nil && filterFunc(resp.Directory, resp.EventNotification.OldEntry.Name) {
|
||||
return true
|
||||
}
|
||||
if resp.EventNotification.NewEntry != nil && filterFunc(resp.EventNotification.NewParentPath, resp.EventNotification.NewEntry.Name) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
jsonpbMarshaler := jsonpb.Marshaler{
|
||||
EmitDefaults: false,
|
||||
}
|
||||
eachEntryFunc := func(resp *filer_pb.SubscribeMetadataResponse) error {
|
||||
jsonpbMarshaler.Marshal(os.Stdout, resp)
|
||||
fmt.Fprintln(os.Stdout)
|
||||
return nil
|
||||
}
|
||||
if *esServers != "" {
|
||||
var err error
|
||||
eachEntryFunc, err = sendToElasticSearchFunc(*esServers, *esIndex)
|
||||
if err != nil {
|
||||
fmt.Printf("create elastic search client to %s: %+v\n", *esServers, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
tailErr := pb.WithFilerClient(*tailFiler, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
stream, err := client.SubscribeMetadata(ctx, &filer_pb.SubscribeMetadataRequest{
|
||||
ClientName: "tail",
|
||||
PathPrefix: *tailTarget,
|
||||
SinceNs: time.Now().Add(-*tailStart).UnixNano(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen: %v", err)
|
||||
}
|
||||
|
||||
for {
|
||||
resp, listenErr := stream.Recv()
|
||||
if listenErr == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if listenErr != nil {
|
||||
return listenErr
|
||||
}
|
||||
if !shouldPrint(resp) {
|
||||
continue
|
||||
}
|
||||
if err = eachEntryFunc(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
if tailErr != nil {
|
||||
fmt.Printf("tail %s: %v\n", *tailFiler, tailErr)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type EsDocument struct {
|
||||
Dir string `json:"dir,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
IsDirectory bool `json:"isDir,omitempty"`
|
||||
Size uint64 `json:"size,omitempty"`
|
||||
Uid uint32 `json:"uid,omitempty"`
|
||||
Gid uint32 `json:"gid,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
Collection string `json:"collection,omitempty"`
|
||||
Crtime int64 `json:"crtime,omitempty"`
|
||||
Mtime int64 `json:"mtime,omitempty"`
|
||||
Mime string `json:"mime,omitempty"`
|
||||
}
|
||||
|
||||
func toEsEntry(event *filer_pb.EventNotification) (*EsDocument, string) {
|
||||
entry := event.NewEntry
|
||||
dir, name := event.NewParentPath, entry.Name
|
||||
id := util.Md5String([]byte(util.NewFullPath(dir, name)))
|
||||
esEntry := &EsDocument{
|
||||
Dir: dir,
|
||||
Name: name,
|
||||
IsDirectory: entry.IsDirectory,
|
||||
Size: entry.Attributes.FileSize,
|
||||
Uid: entry.Attributes.Uid,
|
||||
Gid: entry.Attributes.Gid,
|
||||
UserName: entry.Attributes.UserName,
|
||||
Collection: entry.Attributes.Collection,
|
||||
Crtime: entry.Attributes.Crtime,
|
||||
Mtime: entry.Attributes.Mtime,
|
||||
Mime: entry.Attributes.Mime,
|
||||
}
|
||||
return esEntry, id
|
||||
}
|
||||
|
||||
func sendToElasticSearchFunc(servers string, esIndex string) (func(resp *filer_pb.SubscribeMetadataResponse) error, error) {
|
||||
options := []elastic.ClientOptionFunc{}
|
||||
options = append(options, elastic.SetURL(strings.Split(servers, ",")...))
|
||||
options = append(options, elastic.SetSniff(false))
|
||||
options = append(options, elastic.SetHealthcheck(false))
|
||||
client, err := elastic.NewClient(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(resp *filer_pb.SubscribeMetadataResponse) error {
|
||||
event := resp.EventNotification
|
||||
if event.OldEntry != nil &&
|
||||
(event.NewEntry == nil || resp.Directory != event.NewParentPath || event.OldEntry.Name != event.NewEntry.Name) {
|
||||
// delete or not update the same file
|
||||
dir, name := resp.Directory, event.OldEntry.Name
|
||||
id := util.Md5String([]byte(util.NewFullPath(dir, name)))
|
||||
println("delete", id)
|
||||
_, err := client.Delete().Index(esIndex).Id(id).Do(context.Background())
|
||||
return err
|
||||
}
|
||||
if event.NewEntry != nil {
|
||||
// add a new file or update the same file
|
||||
esEntry, id := toEsEntry(event)
|
||||
value, err := jsoniter.Marshal(esEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
println(string(value))
|
||||
_, err = client.Index().Index(esIndex).Id(id).BodyJson(string(value)).Do(context.Background())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/b2sink"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/filersink"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/gcssink"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/localsink"
|
||||
_ "github.com/chrislusf/seaweedfs/weed/replication/sink/s3sink"
|
||||
"github.com/chrislusf/seaweedfs/weed/replication/sub"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -98,13 +98,19 @@ func runFilerReplicate(cmd *Command, args []string) bool {
|
||||
replicator := replication.NewReplicator(config, "source.filer.", dataSink)
|
||||
|
||||
for {
|
||||
key, m, err := notificationInput.ReceiveMessage()
|
||||
key, m, onSuccessFn, onFailureFn, err := notificationInput.ReceiveMessage()
|
||||
if err != nil {
|
||||
glog.Errorf("receive %s: %+v", key, err)
|
||||
if onFailureFn != nil {
|
||||
onFailureFn()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if key == "" {
|
||||
// long poll received no messages
|
||||
if onSuccessFn != nil {
|
||||
onSuccessFn()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if m.OldEntry != nil && m.NewEntry == nil {
|
||||
@@ -116,14 +122,20 @@ func runFilerReplicate(cmd *Command, args []string) bool {
|
||||
}
|
||||
if err = replicator.Replicate(context.Background(), key, m); err != nil {
|
||||
glog.Errorf("replicate %s: %+v", key, err)
|
||||
if onFailureFn != nil {
|
||||
onFailureFn()
|
||||
}
|
||||
} else {
|
||||
glog.V(1).Infof("replicated %s", key)
|
||||
if onSuccessFn != nil {
|
||||
onSuccessFn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func validateOneEnabledInput(config *viper.Viper) {
|
||||
func validateOneEnabledInput(config *util.ViperProxy) {
|
||||
enabledInput := ""
|
||||
for _, input := range sub.NotificationInputs {
|
||||
if config.GetBool("notification." + input.GetName() + ".enabled") {
|
||||
|
||||
+17
-11
@@ -33,6 +33,8 @@ type SyncOptions struct {
|
||||
bTtlSec *int
|
||||
aDebug *bool
|
||||
bDebug *bool
|
||||
aProxyByFiler *bool
|
||||
bProxyByFiler *bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -43,7 +45,7 @@ var (
|
||||
|
||||
func init() {
|
||||
cmdFilerSynchronize.Run = runFilerSynchronize // break init cycle
|
||||
syncOptions.isActivePassive = cmdFilerSynchronize.Flag.Bool("isActivePassive", false, "one directional follow if true")
|
||||
syncOptions.isActivePassive = cmdFilerSynchronize.Flag.Bool("isActivePassive", false, "one directional follow from A to B if true")
|
||||
syncOptions.filerA = cmdFilerSynchronize.Flag.String("a", "", "filer A in one SeaweedFS cluster")
|
||||
syncOptions.filerB = cmdFilerSynchronize.Flag.String("b", "", "filer B in the other SeaweedFS cluster")
|
||||
syncOptions.aPath = cmdFilerSynchronize.Flag.String("a.path", "/", "directory to sync on filer A")
|
||||
@@ -54,6 +56,8 @@ func init() {
|
||||
syncOptions.bCollection = cmdFilerSynchronize.Flag.String("b.collection", "", "collection on filer B")
|
||||
syncOptions.aTtlSec = cmdFilerSynchronize.Flag.Int("a.ttlSec", 0, "ttl in seconds on filer A")
|
||||
syncOptions.bTtlSec = cmdFilerSynchronize.Flag.Int("b.ttlSec", 0, "ttl in seconds on filer B")
|
||||
syncOptions.aProxyByFiler = cmdFilerSynchronize.Flag.Bool("a.filerProxy", false, "read and write file chunks by filer A instead of volume servers")
|
||||
syncOptions.bProxyByFiler = cmdFilerSynchronize.Flag.Bool("b.filerProxy", false, "read and write file chunks by filer B instead of volume servers")
|
||||
syncOptions.aDebug = cmdFilerSynchronize.Flag.Bool("a.debug", false, "debug mode to print out filer A received files")
|
||||
syncOptions.bDebug = cmdFilerSynchronize.Flag.Bool("b.debug", false, "debug mode to print out filer B received files")
|
||||
syncCpuProfile = cmdFilerSynchronize.Flag.String("cpuprofile", "", "cpu profile output file")
|
||||
@@ -62,8 +66,8 @@ func init() {
|
||||
|
||||
var cmdFilerSynchronize = &Command{
|
||||
UsageLine: "filer.sync -a=<oneFilerHost>:<oneFilerPort> -b=<otherFilerHost>:<otherFilerPort>",
|
||||
Short: "continuously synchronize between two active-active or active-passive SeaweedFS clusters",
|
||||
Long: `continuously synchronize file changes between two active-active or active-passive filers
|
||||
Short: "resumeable continuous synchronization between two active-active or active-passive SeaweedFS clusters",
|
||||
Long: `resumeable continuous synchronization for file changes between two active-active or active-passive filers
|
||||
|
||||
filer.sync listens on filer notifications. If any file is updated, it will fetch the updated content,
|
||||
and write to the other destination. Different from filer.replicate:
|
||||
@@ -86,8 +90,9 @@ func runFilerSynchronize(cmd *Command, args []string) bool {
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := doSubscribeFilerMetaChanges(grpcDialOption, *syncOptions.filerA, *syncOptions.aPath, *syncOptions.filerB,
|
||||
*syncOptions.bPath, *syncOptions.bReplication, *syncOptions.bCollection, *syncOptions.bTtlSec, *syncOptions.bDebug)
|
||||
err := doSubscribeFilerMetaChanges(grpcDialOption, *syncOptions.filerA, *syncOptions.aPath, *syncOptions.aProxyByFiler,
|
||||
*syncOptions.filerB, *syncOptions.bPath, *syncOptions.bReplication, *syncOptions.bCollection, *syncOptions.bTtlSec, *syncOptions.bProxyByFiler,
|
||||
*syncOptions.bDebug)
|
||||
if err != nil {
|
||||
glog.Errorf("sync from %s to %s: %v", *syncOptions.filerA, *syncOptions.filerB, err)
|
||||
time.Sleep(1747 * time.Millisecond)
|
||||
@@ -98,8 +103,9 @@ func runFilerSynchronize(cmd *Command, args []string) bool {
|
||||
if !*syncOptions.isActivePassive {
|
||||
go func() {
|
||||
for {
|
||||
err := doSubscribeFilerMetaChanges(grpcDialOption, *syncOptions.filerB, *syncOptions.bPath, *syncOptions.filerA,
|
||||
*syncOptions.aPath, *syncOptions.aReplication, *syncOptions.aCollection, *syncOptions.aTtlSec, *syncOptions.aDebug)
|
||||
err := doSubscribeFilerMetaChanges(grpcDialOption, *syncOptions.filerB, *syncOptions.bPath, *syncOptions.bProxyByFiler,
|
||||
*syncOptions.filerA, *syncOptions.aPath, *syncOptions.aReplication, *syncOptions.aCollection, *syncOptions.aTtlSec, *syncOptions.aProxyByFiler,
|
||||
*syncOptions.aDebug)
|
||||
if err != nil {
|
||||
glog.Errorf("sync from %s to %s: %v", *syncOptions.filerB, *syncOptions.filerA, err)
|
||||
time.Sleep(2147 * time.Millisecond)
|
||||
@@ -113,8 +119,8 @@ func runFilerSynchronize(cmd *Command, args []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func doSubscribeFilerMetaChanges(grpcDialOption grpc.DialOption, sourceFiler, sourcePath, targetFiler, targetPath string,
|
||||
replicationStr, collection string, ttlSec int, debug bool) error {
|
||||
func doSubscribeFilerMetaChanges(grpcDialOption grpc.DialOption, sourceFiler, sourcePath string, sourceReadChunkFromFiler bool, targetFiler, targetPath string,
|
||||
replicationStr, collection string, ttlSec int, sinkWriteChunkByFiler, debug bool) error {
|
||||
|
||||
// read source filer signature
|
||||
sourceFilerSignature, sourceErr := replication.ReadFilerSignature(grpcDialOption, sourceFiler)
|
||||
@@ -138,9 +144,9 @@ func doSubscribeFilerMetaChanges(grpcDialOption grpc.DialOption, sourceFiler, so
|
||||
|
||||
// create filer sink
|
||||
filerSource := &source.FilerSource{}
|
||||
filerSource.DoInitialize(pb.ServerToGrpcAddress(sourceFiler), sourcePath)
|
||||
filerSource.DoInitialize(sourceFiler, pb.ServerToGrpcAddress(sourceFiler), sourcePath, sourceReadChunkFromFiler)
|
||||
filerSink := &filersink.FilerSink{}
|
||||
filerSink.DoInitialize(pb.ServerToGrpcAddress(targetFiler), targetPath, replicationStr, collection, ttlSec, grpcDialOption)
|
||||
filerSink.DoInitialize(targetFiler, pb.ServerToGrpcAddress(targetFiler), targetPath, replicationStr, collection, ttlSec, grpcDialOption, sinkWriteChunkByFiler)
|
||||
filerSink.SetSourceFiler(filerSource)
|
||||
|
||||
processEventFn := func(resp *filer_pb.SubscribeMetadataResponse) error {
|
||||
|
||||
@@ -69,7 +69,7 @@ var cmdMaster = &Command{
|
||||
Short: "start a master server",
|
||||
Long: `start a master server to provide volume=>location mapping service and sequence number of file ids
|
||||
|
||||
The configuration file "security.toml" is read from ".", "$HOME/.seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
The configuration file "security.toml" is read from ".", "$HOME/.seaweedfs/", "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order.
|
||||
|
||||
The example security.toml configuration file can be generated by "weed scaffold -config=security"
|
||||
|
||||
|
||||
+20
-18
@@ -6,23 +6,24 @@ import (
|
||||
)
|
||||
|
||||
type MountOptions struct {
|
||||
filer *string
|
||||
filerMountRootPath *string
|
||||
dir *string
|
||||
dirAutoCreate *bool
|
||||
collection *string
|
||||
replication *string
|
||||
ttlSec *int
|
||||
chunkSizeLimitMB *int
|
||||
cacheDir *string
|
||||
cacheSizeMB *int64
|
||||
dataCenter *string
|
||||
allowOthers *bool
|
||||
umaskString *string
|
||||
nonempty *bool
|
||||
outsideContainerClusterMode *bool
|
||||
uidMap *string
|
||||
gidMap *string
|
||||
filer *string
|
||||
filerMountRootPath *string
|
||||
dir *string
|
||||
dirAutoCreate *bool
|
||||
collection *string
|
||||
replication *string
|
||||
ttlSec *int
|
||||
chunkSizeLimitMB *int
|
||||
concurrentWriters *int
|
||||
cacheDir *string
|
||||
cacheSizeMB *int64
|
||||
dataCenter *string
|
||||
allowOthers *bool
|
||||
umaskString *string
|
||||
nonempty *bool
|
||||
volumeServerAccess *string
|
||||
uidMap *string
|
||||
gidMap *string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -42,13 +43,14 @@ func init() {
|
||||
mountOptions.replication = cmdMount.Flag.String("replication", "", "replication(e.g. 000, 001) to create to files. If empty, let filer decide.")
|
||||
mountOptions.ttlSec = cmdMount.Flag.Int("ttl", 0, "file ttl in seconds")
|
||||
mountOptions.chunkSizeLimitMB = cmdMount.Flag.Int("chunkSizeLimitMB", 2, "local write buffer size, also chunk large files")
|
||||
mountOptions.concurrentWriters = cmdMount.Flag.Int("concurrentWriters", 128, "limit concurrent goroutine writers if not 0")
|
||||
mountOptions.cacheDir = cmdMount.Flag.String("cacheDir", os.TempDir(), "local cache directory for file chunks and meta data")
|
||||
mountOptions.cacheSizeMB = cmdMount.Flag.Int64("cacheCapacityMB", 1000, "local file chunk cache capacity in MB (0 will disable cache)")
|
||||
mountOptions.dataCenter = cmdMount.Flag.String("dataCenter", "", "prefer to write to the data center")
|
||||
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")
|
||||
mountOptions.outsideContainerClusterMode = cmdMount.Flag.Bool("outsideContainerClusterMode", false, "allows other users to access volume servers with publicUrl")
|
||||
mountOptions.volumeServerAccess = cmdMount.Flag.String("volumeServerAccess", "direct", "access volume servers by [direct|publicUrl|filerProxy]")
|
||||
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>")
|
||||
|
||||
|
||||
+34
-28
@@ -5,8 +5,6 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||
"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -15,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache"
|
||||
|
||||
"github.com/seaweedfs/fuse"
|
||||
"github.com/seaweedfs/fuse/fs"
|
||||
|
||||
@@ -33,7 +33,7 @@ func runMount(cmd *Command, args []string) bool {
|
||||
if *mountReadRetryTime < time.Second {
|
||||
*mountReadRetryTime = time.Second
|
||||
}
|
||||
filer.ReadWaitTime = *mountReadRetryTime
|
||||
util.RetryWaitTime = *mountReadRetryTime
|
||||
|
||||
umask, umaskErr := strconv.ParseUint(*mountOptions.umaskString, 8, 64)
|
||||
if umaskErr != nil {
|
||||
@@ -58,6 +58,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
util.LoadConfiguration("security", false)
|
||||
// try to connect to filer, filerBucketsPath may be useful later
|
||||
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||
var cipher bool
|
||||
@@ -78,8 +79,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
dir := util.ResolvePath(*option.dir)
|
||||
chunkSizeLimitMB := *mountOptions.chunkSizeLimitMB
|
||||
|
||||
util.LoadConfiguration("security", false)
|
||||
|
||||
fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
if dir == "" {
|
||||
fmt.Printf("Please specify the mount directory via \"-dir\"")
|
||||
@@ -101,9 +100,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
uid, gid := uint32(0), uint32(0)
|
||||
mountMode := os.ModeDir | 0777
|
||||
if err == nil {
|
||||
mountMode = os.ModeDir | fileInfo.Mode()
|
||||
mountMode = os.ModeDir | os.FileMode(0777)&^umask
|
||||
uid, gid = util.GetFileUidGid(fileInfo)
|
||||
fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, fileInfo.Mode())
|
||||
fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, mountMode)
|
||||
} else {
|
||||
fmt.Printf("can not stat %s\n", dir)
|
||||
return false
|
||||
@@ -151,6 +150,8 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
fuse.MaxReadahead(1024 * 128),
|
||||
fuse.AsyncRead(),
|
||||
fuse.WritebackCache(),
|
||||
fuse.MaxBackground(128),
|
||||
fuse.CongestionThreshold(128),
|
||||
}
|
||||
|
||||
options = append(options, osSpecificMountOptions()...)
|
||||
@@ -168,26 +169,29 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
}
|
||||
|
||||
seaweedFileSystem := filesys.NewSeaweedFileSystem(&filesys.Option{
|
||||
FilerGrpcAddress: filerGrpcAddress,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
FilerMountRootPath: mountRoot,
|
||||
Collection: *option.collection,
|
||||
Replication: *option.replication,
|
||||
TtlSec: int32(*option.ttlSec),
|
||||
ChunkSizeLimit: int64(chunkSizeLimitMB) * 1024 * 1024,
|
||||
CacheDir: *option.cacheDir,
|
||||
CacheSizeMB: *option.cacheSizeMB,
|
||||
DataCenter: *option.dataCenter,
|
||||
EntryCacheTtl: 3 * time.Second,
|
||||
MountUid: uid,
|
||||
MountGid: gid,
|
||||
MountMode: mountMode,
|
||||
MountCtime: fileInfo.ModTime(),
|
||||
MountMtime: time.Now(),
|
||||
Umask: umask,
|
||||
OutsideContainerClusterMode: *mountOptions.outsideContainerClusterMode,
|
||||
Cipher: cipher,
|
||||
UidGidMapper: uidGidMapper,
|
||||
MountDirectory: dir,
|
||||
FilerAddress: filer,
|
||||
FilerGrpcAddress: filerGrpcAddress,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
FilerMountRootPath: mountRoot,
|
||||
Collection: *option.collection,
|
||||
Replication: *option.replication,
|
||||
TtlSec: int32(*option.ttlSec),
|
||||
ChunkSizeLimit: int64(chunkSizeLimitMB) * 1024 * 1024,
|
||||
ConcurrentWriters: *option.concurrentWriters,
|
||||
CacheDir: *option.cacheDir,
|
||||
CacheSizeMB: *option.cacheSizeMB,
|
||||
DataCenter: *option.dataCenter,
|
||||
EntryCacheTtl: 3 * time.Second,
|
||||
MountUid: uid,
|
||||
MountGid: gid,
|
||||
MountMode: mountMode,
|
||||
MountCtime: fileInfo.ModTime(),
|
||||
MountMtime: time.Now(),
|
||||
Umask: umask,
|
||||
VolumeServerAccess: *mountOptions.volumeServerAccess,
|
||||
Cipher: cipher,
|
||||
UidGidMapper: uidGidMapper,
|
||||
})
|
||||
|
||||
// mount
|
||||
@@ -204,7 +208,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
})
|
||||
|
||||
glog.V(0).Infof("mounted %s%s to %s", filer, mountRoot, dir)
|
||||
err = fs.Serve(c, seaweedFileSystem)
|
||||
server := fs.New(c, nil)
|
||||
seaweedFileSystem.Server = server
|
||||
err = server.Serve(seaweedFileSystem)
|
||||
|
||||
// check if the mount process has an error to report
|
||||
<-c.Ready
|
||||
|
||||
+10
-7
@@ -23,13 +23,14 @@ var (
|
||||
)
|
||||
|
||||
type S3Options struct {
|
||||
filer *string
|
||||
port *int
|
||||
config *string
|
||||
domainName *string
|
||||
tlsPrivateKey *string
|
||||
tlsCertificate *string
|
||||
metricsHttpPort *int
|
||||
filer *string
|
||||
port *int
|
||||
config *string
|
||||
domainName *string
|
||||
tlsPrivateKey *string
|
||||
tlsCertificate *string
|
||||
metricsHttpPort *int
|
||||
allowEmptyFolder *bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -41,6 +42,7 @@ func init() {
|
||||
s3StandaloneOptions.tlsPrivateKey = cmdS3.Flag.String("key.file", "", "path to the TLS private key file")
|
||||
s3StandaloneOptions.tlsCertificate = cmdS3.Flag.String("cert.file", "", "path to the TLS certificate file")
|
||||
s3StandaloneOptions.metricsHttpPort = cmdS3.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
|
||||
s3StandaloneOptions.allowEmptyFolder = cmdS3.Flag.Bool("allowEmptyFolder", false, "allow empty folders")
|
||||
}
|
||||
|
||||
var cmdS3 = &Command{
|
||||
@@ -181,6 +183,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
DomainName: *s3opt.domainName,
|
||||
BucketsPath: filerBucketsPath,
|
||||
GrpcDialOption: grpcDialOption,
|
||||
AllowEmptyFolder: *s3opt.allowEmptyFolder,
|
||||
})
|
||||
if s3ApiServer_err != nil {
|
||||
glog.Fatalf("S3 API Server startup error: %v", s3ApiServer_err)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user