Compare commits

...
1088 Commits
Author SHA1 Message Date
Chris Lu 66d04cb83f 1.12 2018-12-15 08:17:24 -08:00
Chris Lu 42cb9b76c4 reduce needle map memory usage by 25% 2018-12-15 05:55:56 -08:00
Chris Lu 21315f709d HCFS 1.0.2 2018-12-14 09:16:21 -08:00
Chris Lu 42b8f1145a weed mount ls paginate the results
fix https://github.com/chrislusf/seaweedfs/issues/787
2018-12-11 07:41:50 -08:00
Chris Lu 39ea8484c1 adjust proto API 2018-12-11 07:17:10 -08:00
Chris Lu 168f461761 fix MaxUint8 2018-12-10 20:57:51 -08:00
Chris Lu 8f78a069c6 fix possible name overflow when len(name)>255 2018-12-10 20:57:01 -08:00
Chris Lu 4a490fe35a go fmt 2018-12-09 01:27:25 -08:00
Chris Lu 5741134ca0 change overflow from map to slice 2018-12-09 01:27:11 -08:00
Chris Lu d9189c857c remove unused file 2018-12-09 00:30:53 -08:00
Chris Lu df4b9df142 add memory usage report 2018-12-09 00:15:23 -08:00
Chris Lu ec5a305624 refactor 2018-12-09 00:12:37 -08:00
Chris Lu 9d3be33e5c optimize a little bit 2018-12-08 21:45:14 -08:00
Chris LuandGitHub 2371770fe8 Adding Hadoop Compatible File System 2018-12-08 00:50:58 -08:00
Chris Lu 36d1e739e6 1.11 2018-12-08 00:32:34 -08:00
Chris Lu adf12c8825 refactoring 2018-12-07 02:23:45 -08:00
Chris Lu 1bfb96f34d optimization for reading whole chunk with gzip encoding 2018-12-07 01:57:55 -08:00
Chris Lu 29f1673d97 refactoring 2018-12-07 01:25:01 -08:00
Chris Lu 6946c51430 avoid range request for gzipped content 2018-12-07 01:23:30 -08:00
Chris Lu 6b39effe7f add a local test 2018-12-07 01:23:02 -08:00
Chris Lu c28e8a2397 refactoring 2018-12-06 00:44:41 -08:00
Chris Lu ffa2827ab1 fail fast if two notification queues or inputs are enabled 2018-12-06 00:37:59 -08:00
Chris Lu 63619f8b8e fail early if two filer stores are defined 2018-12-06 00:32:36 -08:00
Chris Lu 52b24a9902 default "weed server -filer" to same directory as -mdir 2018-12-05 23:24:25 -08:00
Chris Lu fb43a9d7b9 adjust message 2018-12-05 02:03:03 -08:00
Chris Lu e2276b0626 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-12-04 22:57:26 -08:00
Chris Lu 09383860e9 HCFS recursively delete files 2018-12-04 22:57:23 -08:00
Chris LuandGitHub 093f8bb917 Merge pull request #781 from PapaYofen/fix-780
fix https://github.com/chrislusf/seaweedfs/issues/780
2018-12-04 22:56:19 -08:00
chenwanli 31038acccf fix https://github.com/chrislusf/seaweedfs/issues/780 2018-12-05 14:06:16 +08:00
Chris Lu 060ce1d9a2 HCFS: adjust file modification time to milliseconds 2018-12-04 01:11:13 -08:00
Chris Lu 5f8c8caec6 HCFS: chmod 2018-12-04 01:10:25 -08:00
Chris Lu d5197d6a50 HCFS support chown 2018-12-04 00:48:11 -08:00
Chris Lu 650d1af978 HCFS support change user name and group names 2018-12-04 00:46:00 -08:00
Chris Lu 091f6297c4 add debug 2018-12-04 00:28:10 -08:00
Chris Lu aea86cbf89 add @Override 2018-12-04 00:28:03 -08:00
Chris Lu b0838db5a8 support hdfs user name and group names 2018-12-04 00:13:40 -08:00
Chris Lu ce566f579e add juint 2018-12-03 22:12:31 -08:00
Chris Lu c85ee7c0fd HCFS: read concatenated files 2018-12-03 22:12:20 -08:00
Chris Lu 4119c61df8 HCFS can read files 2018-12-03 20:25:57 -08:00
Chris Lu 5b50182658 put file is working 2018-12-03 01:37:29 -08:00
Chris Lu 7ace0efd65 revert prev 2018-12-02 23:25:37 -08:00
Chris Lu 738247d47e do not throw error if not found 2018-12-02 23:20:29 -08:00
Chris Lu c6b646e92c skip creation for root 2018-12-02 23:16:17 -08:00
Chris Lu bea162c34c add warning for empty attributes 2018-12-02 22:57:59 -08:00
Chris Lu d3be8e022f HCFS can ls, mkdir 2018-12-02 22:44:49 -08:00
Chris Lu f0677c5af1 default root to 0755 2018-12-02 19:59:47 -08:00
Chris Lu db22666a30 add default root directory 2018-12-02 19:42:50 -08:00
Chris Lu 4263805c78 resolve java jar dependencies 2018-12-02 15:16:49 -08:00
Chris Lu a3def2bbd9 for 1.10 2018-11-29 09:15:04 -08:00
Chris LuandGitHub 44d7a49a48 Merge pull request #778 from peick/master
Build statically linked release executables.
2018-11-29 09:14:18 -08:00
Michael PeickandMichael Peick 57e159b369 Build statically linked release executables. 2018-11-29 13:26:48 +01:00
Chris Lu c6a567acab add SeaweedInputStream 2018-11-29 01:04:40 -08:00
Chris Lu 7a6f49cd42 refactor a bit more
fix https://github.com/chrislusf/seaweedfs/issues/777
2018-11-29 00:07:54 -08:00
Chris Lu ceca078acb avoid overwriting file or directory
fix https://github.com/chrislusf/seaweedfs/issues/777
2018-11-29 00:00:56 -08:00
Chris Lu 021f5d689b copy the visible chunks logic from Go implementation 2018-11-26 00:32:04 -08:00
Chris Lu b089f1a492 SeaweedWrite close http client 2018-11-26 00:00:55 -08:00
Chris Lu 11eb014311 SeaweedFileSystem add SeaweedOutputStream to write 2018-11-25 23:49:05 -08:00
Chris Lu f970abf14a WIP: adding SeaweedOutputStream for write 2018-11-25 18:01:57 -08:00
Chris Lu 6ecefad692 SeaweedFileSystem add rename 2018-11-25 14:48:17 -08:00
Chris Lu 1cbd53c01c WIP SeaweedFileSystem added mkdirs, getFileStatus, listStatus, delete 2018-11-25 13:43:26 -08:00
Chris Lu d9871e92d2 adjust module name 2018-11-24 03:24:40 -08:00
Chris Lu 76cba561cf starting with hadoop compatible 2018-11-24 03:22:25 -08:00
Chris Lu 871dee4674 fix test 2018-11-23 12:37:09 -08:00
Chris Lu 86dd933596 go fmt 2018-11-23 00:26:15 -08:00
Chris Lu 444dfded84 add fs.FSStatfser for SeaweedFS weed mount 2018-11-23 00:24:51 -08:00
Chris Lu 2e32b44061 add template for fs.FSStatfser 2018-11-22 23:05:22 -08:00
Chris Lu 6344469a9b set fuse subtype 2018-11-22 23:04:23 -08:00
Chris Lu d64546c791 adjust logs 2018-11-22 14:31:10 -08:00
Chris Lu e12b6c0306 add subimit count parameter
related to https://github.com/chrislusf/seaweedfs/issues/631
2018-11-22 13:45:29 -08:00
Chris Lu 0ed816d4e9 formatting error log 2018-11-22 00:42:56 -08:00
Chris Lu fb1ed0e30d add tool to printout dat file content 2018-11-22 00:24:50 -08:00
Chris Lu 19d546938a forgot to upgrade version number 2018-11-21 23:30:59 -08:00
Chris Lu 60d3681371 default images.fix.orientation to false 2018-11-21 16:50:42 -08:00
Chris Lu 393e1f70ad remove unused file 2018-11-21 16:45:36 -08:00
Chris Lu 99256100a0 fix tests 2018-11-21 16:35:27 -08:00
Chris Lu e5ad2223a1 add missing intervals
fix https://github.com/chrislusf/seaweedfs/issues/774
2018-11-21 16:25:13 -08:00
Chris Lu c9b3ef58de options to control filer.export verbosity 2018-11-21 15:15:40 -08:00
Chris Lu 2f2e417b68 detailed export 2018-11-21 15:12:47 -08:00
Chris Lu b282e34dc2 async file chunk deletion 2018-11-20 20:56:28 -08:00
Chris Lu 5065d4ab2d master add grpc API for fileid assigning 2018-11-20 11:35:45 -08:00
Chris Lu b318167cd3 batch delete file ids 2018-11-20 11:35:08 -08:00
Chris Lu 06474e5990 1.08 2018-11-18 22:43:05 -08:00
Chris Lu ee3fe07acf fix sort bug 2018-11-18 21:59:53 -08:00
Chris Lu 469863f7f0 merge interval now is quite faster with reusing slices 2018-11-18 21:24:58 -08:00
Chris Lu d2924c7748 merge intervals is a bit faster 2018-11-18 20:31:39 -08:00
Chris Lu cb5a10c6a3 adjust the tests 2018-11-18 17:05:36 -08:00
Chris Lu f1db22d48b add benchmark test for merging intervals 2018-11-18 16:49:08 -08:00
Chris Lu 4fcfc9410f cleanup 2018-11-18 11:51:38 -08:00
Chris Lu 9655dc9ca9 simpler logic 2018-11-18 10:07:30 -08:00
Chris Lu f8eb988347 1.07 2018-11-18 08:10:15 -08:00
Chris Lu 60421b23f0 reduce weed mount memory usage 2018-11-18 07:49:14 -08:00
Chris Lu 2929563294 1.06 2018-11-14 23:51:15 -08:00
Chris Lu d3b7965c76 caching grpc clients 2018-11-14 23:50:46 -08:00
Chris Lu d89774cb7f cache grpc client connections to improve performance 2018-11-14 23:31:39 -08:00
Chris Lu 2e6a3c7b16 reduce memory usage 2018-11-14 22:48:54 -08:00
Chris Lu f224b9fe28 1.05 2018-11-13 23:54:17 -08:00
Chris Lu 11b0a9cb76 fix possible error when reusing file handles 2018-11-13 23:53:17 -08:00
Chris Lu a62adf62ca a bit faster 2018-11-12 11:19:13 -08:00
Chris Lu affe708961 a bit faster 2018-11-11 00:43:30 -08:00
Chris Lu a09eaf8756 adjusting, seems there are some perf benefit 2018-11-11 00:08:22 -08:00
Chris Lu 8178fc9973 adjust logging 2018-11-11 00:07:46 -08:00
Chris Lu 2697d6a4fb fix deleting bucket 2018-11-08 22:15:21 -08:00
Chris Lu 70008b093e test deletes 2018-11-08 22:08:11 -08:00
Chris Lu 0d095f72e4 update to 1.04 2018-11-08 21:42:45 -08:00
Chris Lu a4ceb051a7 use MD5 for ETag to be consistent with Amazon S3 2018-11-08 21:41:02 -08:00
Chris Lu 6e53c38c2f update to 1.03 2018-11-08 07:37:44 -08:00
Chris Lu cbd94b18a5 improve "ls -al" performance for large directory 2018-11-08 07:37:34 -08:00
Chris Lu 6e11923551 upgrade to 1.02 2018-11-07 11:37:32 -08:00
Chris Lu ed8efb5aef much improved "ls -al" performance 2018-11-07 11:35:13 -08:00
Chris Lu 60db731e36 update to 1.01 2018-11-05 08:54:40 -08:00
Chris Lu f9dbaf6292 delete file by name instead of referencing the dataFile pointer 2018-11-05 08:53:38 -08:00
Chris LuandGitHub c77c4e3c60 Merge pull request #763 from alex8224/master
fix invalid point when delete collection
2018-11-05 08:48:32 -08:00
alex8224 8d3f9c0b75 fix invalid point when delete collection 2018-11-05 19:53:55 +08:00
Chris Lu 1a64a817ac update to 1.00 2018-11-04 22:13:16 -08:00
Chris Lu 85d1d99459 default "weed export to current enabled store" 2018-11-04 12:59:29 -08:00
Chris Lu 6c45787a37 selectively export files 2018-11-04 12:07:33 -08:00
Chris Lu ff4189ab25 go fmt 2018-11-04 11:59:08 -08:00
Chris Lu 8dfac6a4cf working b2 sink 2018-11-04 11:58:59 -08:00
Chris Lu f050b22d6c close file in ScanVolumeFile()
fix https://github.com/chrislusf/seaweedfs/issues/761
2018-11-04 00:28:24 -07:00
Chris Lu 1fcbed4e90 add delete 2018-11-03 23:14:05 -07:00
Chris Lu 776308c4eb add to stress test vacuum 2018-11-03 22:32:36 -07:00
Chris Lu 93e0dddbf3 add function description 2018-11-03 22:32:21 -07:00
Chris Lu 3c54b3cff4 go fmt 2018-11-03 22:32:04 -07:00
Chris Lu a64613172d bootstrap filer replication with weed filer.export -targetStore=notification 2018-11-03 12:43:45 -07:00
Chris Lu 3674ad9f8e go fmt 2018-11-01 01:12:21 -07:00
Chris Lu db584ff7f8 separate into notification.toml, add gcp pub/sub message queue 2018-11-01 01:11:09 -07:00
Chris Lu 6219a9ad1f filer ordered notifications 2018-10-31 21:48:05 -07:00
Chris Lu 4c97ff3717 support AWS SQS as file change notification message queue 2018-10-31 01:11:19 -07:00
Chris LuandGitHub 200cbcde62 Merge pull request #756 from zzoe/patch-1
Update volume_server_handlers_write.go
2018-10-30 21:52:19 -07:00
zoeandGitHub a11c28cb4c Update volume_server_handlers_write.go
size declared and not used
2018-10-31 09:36:32 +08:00
Chris Lu 79371c03a8 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-10-30 02:29:13 -07:00
Chris Lu fe31f389b0 add configuration instructions 2018-10-30 02:29:11 -07:00
zoeandGitHub 29eff0ecd7 Update volume_server_handlers_write.go
返回实际文件大小,而不是needle的大小
2018-10-29 21:05:51 +08:00
Chris LuandGitHub 082bb49c7c Merge pull request #755 from codeskyblue/master
update dev-plan with markdown format
2018-10-24 22:24:32 -07:00
codeskyblue 854fb38612 update dev-plan with markdown format 2018-10-25 13:16:55 +08:00
Chris Lu 9e98bba206 go fmt 2018-10-24 00:00:01 -07:00
Chris Lu 6a756136ef go fmt 2018-10-23 23:59:49 -07:00
Chris Lu c58b73ad9b adding BackBlaze, Azure 2018-10-23 23:59:40 -07:00
Chris Lu 7f1de86779 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-10-23 02:18:49 -07:00
Chris Lu b861899919 use released version of azblob
replace https://github.com/chrislusf/seaweedfs/pull/754
2018-10-23 02:18:46 -07:00
Chris LuandGitHub a846a11e10 Merge pull request #753 from HaraldNordgren/master
Bump Travis versions
2018-10-19 16:28:07 -07:00
Harald Nordgren 07f635c3bc Bump Travis versions 2018-10-20 01:19:48 +02:00
Chris Lu 82a5d39778 add back volume vacuum cleanup 2018-10-18 23:02:58 -07:00
Chris Lu 496a7398d1 adjust logging 2018-10-18 20:47:30 -07:00
Chris Lu 037b2b0135 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-10-18 20:35:17 -07:00
Chris Lu 201c24c110 add locking to volume layout vacuum 2018-10-18 20:34:43 -07:00
Chris LuandGitHub f4ad5b0579 Update README.md 2018-10-17 18:44:49 -07:00
Chris LuandGitHub f3f0f9750a Update README.md 2018-10-17 11:39:40 -07:00
Chris Lu 46eb77f9bb move DiskStatus and MemStatus to protobuf 2018-10-15 22:25:28 -07:00
Chris Lu eec951cad2 migrate volume sync to gRpc 2018-10-15 21:44:41 -07:00
Chris Lu f8b2d3cacc move volume mount/unmount on volume server to grpc 2018-10-15 01:48:15 -07:00
Chris Lu 66a353dcb5 remove volume server /admin/volume/delete 2018-10-15 01:26:49 -07:00
Chris Lu fda771c83f migrate volume sync status to grpc API on volume server 2018-10-15 01:19:15 -07:00
Chris Lu b423bb9e2d migrate assign volume to grpc API on volume server 2018-10-15 00:40:46 -07:00
Chris Lu 333709657c adjust log level 2018-10-15 00:40:12 -07:00
Chris Lu f9410bcaa7 adjust log level for deletes 2018-10-15 00:39:56 -07:00
Chris Lu 8301519fb0 migrate delete collection to grpc API on volume server 2018-10-15 00:03:55 -07:00
Chris Lu db152ca540 fix error log format 2018-10-14 23:50:54 -07:00
Chris Lu 67d1e572ca rename files 2018-10-14 23:47:29 -07:00
Chris Lu b1daede91b move volume vacuum to gRpc 2018-10-14 23:12:43 -07:00
Chris Lu 91ac2e0dd9 go fmt 2018-10-14 00:30:20 -07:00
Chris Lu 249eeccf32 0.99 release 2018-10-14 00:19:04 -07:00
Chris Lu 9db53cf256 use the configurable chunk size 2018-10-14 00:18:52 -07:00
Chris Lu ff66269b62 use grpc to replace http APIs for batch volume id lookup and batch delete
1. remove batch volume id lookup http API /vol/lookup
2. remove batch delete http API /delete
2018-10-14 00:12:28 -07:00
Chris Lu 3ddcd87098 increase weed mount IO perforance 2018-10-14 00:10:17 -07:00
Chris Lu ab85118233 error handling when kafka not ready 2018-10-13 23:30:00 -07:00
Chris Lu 41aba0c02c fix when no files in filer 2018-10-12 00:45:28 -07:00
Chris Lu 4af6b4eb0e fix master static resource display problem 2018-10-12 00:05:15 -07:00
Chris Lu 556382ff5f adding basic grpc to volume server 2018-10-11 01:16:33 -07:00
Chris Lu 64ed47502b rename file 2018-10-11 00:21:32 -07:00
Chris Lu 08266b7256 go fmt 2018-10-11 00:08:13 -07:00
Chris Lu 1d7e01b754 reformat 2018-10-11 00:05:54 -07:00
Chris Lu da6154b29c refactor volume server to startVolumeServer() 2018-10-11 00:04:31 -07:00
Chris Lu 60d2f1557d refactor func name 2018-10-10 23:19:54 -07:00
Chris Lu b7763c1cb6 releasing 0.98 2018-10-10 21:44:10 -07:00
Chris Lu 3f6f50d0ba add Azure to readme 2018-10-09 01:44:26 -07:00
Chris Lu 9f4c2f87d3 adding filer replication sink to Azure 2018-10-09 01:35:48 -07:00
Chris Lu 453d0be4d4 adjust error format 2018-10-09 01:35:30 -07:00
Chris LuandGitHub 88ad8d3a89 Merge pull request #747 from a1exwang/master
[bugfix] Fix interrupt hook overwritten bug
2018-10-09 00:53:30 -07:00
Alex Wang 4db68669b6 [bugfix] Fix interrupt hook overwritten bug 2018-10-09 15:43:32 +08:00
Chris Lu 1d3f045e85 adjust readme 2018-10-08 00:15:04 -07:00
Chris Lu 96a23591e7 remove comments 2018-10-07 22:21:00 -07:00
Chris Lu dbe33236fb embed static resources via statik 2018-10-07 21:42:57 -07:00
Chris Lu fffbc2d490 embed static resources via statik 2018-10-07 10:54:05 -07:00
Chris Lu a5b5887efc add filer replication to aws s3 2018-10-06 17:10:15 -07:00
Chris Lu 84d4a2c8a0 add filer replication to google cloud storage 2018-10-06 13:08:38 -07:00
Chris Lu f8c2704d2b reformat 2018-10-06 13:04:33 -07:00
Chris Lu 14858f6224 add logging 2018-10-06 13:01:38 -07:00
Chris Lu 04da4c8094 add logging 2018-10-06 13:01:29 -07:00
Chris Lu 4a8ef198d7 add logging 2018-10-06 13:00:33 -07:00
Chris Lu 56c5c7b1b6 add google cloud storage 2018-10-04 01:14:44 -07:00
Chris Lu e8ef501f02 add s3 replication sink 2018-10-03 23:36:52 -07:00
Chris Lu 56a5d5af8d ignore duplicated key error if the store has duplicated primary key prevention
fix https://github.com/chrislusf/seaweedfs/issues/741
2018-09-30 00:49:52 -07:00
Chris Lu 98384b62fd adjust description 2018-09-28 01:58:34 -07:00
Chris Lu f11d531d45 adjust logging 2018-09-28 01:54:10 -07:00
Chris Lu 06c43286cd skip error when deletion failed 2018-09-28 00:10:49 -07:00
Chris Lu 746017ea4f releasing 0.97 2018-09-27 21:51:41 -07:00
Chris Lu f7ff84f17f Revert "smalller block size for better accuracy"
This reverts commit 97ecd20fc9.
2018-09-27 09:42:24 -07:00
Chris Lu 97ecd20fc9 smalller block size for better accuracy 2018-09-27 09:39:17 -07:00
Chris Lu 8904bc4cde Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-09-27 09:33:39 -07:00
Chris Lu f53632a84b add block size for linux du
du already working on Mac
2018-09-27 09:33:36 -07:00
Chris LuandGitHub b5f3578fe4 Update README.md 2018-09-27 01:13:22 -07:00
Chris LuandGitHub 83ab3966d1 do not use PUT for uploading files 2018-09-27 01:11:16 -07:00
Chris Lu a362261972 avoid possible racing issue if leveldb or boltdb is used.
remove the to-be-generated files during commit

fix https://github.com/chrislusf/seaweedfs/issues/738
2018-09-26 18:45:51 -07:00
Chris Lu 31ed352ab6 replication handle cases when entry already exists 2018-09-25 09:27:03 -07:00
Chris Lu 60c1ada4c1 remove filer grpc API GetEntryAttributes 2018-09-25 09:18:06 -07:00
Chris Lu 578f8a6dec fix empty file reading on linux 2018-09-23 10:01:00 -07:00
Chris Lu 200d9b9ac2 add file name to log 2018-09-23 09:48:55 -07:00
Chris Lu a60767c43a add more instructions 2018-09-23 09:26:39 -07:00
Chris Lu 12ba8b9799 generate useable filer.toml 2018-09-23 09:26:25 -07:00
Chris Lu b1b8c4ed32 join via filepath 2018-09-23 01:46:50 -07:00
Chris Lu b16d47030e save and use last processed kafka offsets 2018-09-23 01:34:40 -07:00
Chris Lu 9fe24991d5 refactoring 2018-09-23 00:40:36 -07:00
Chris Lu 7d6b2a4740 add ETag to upload results and chunks 2018-09-22 22:12:21 -07:00
Chris Lu 420f068376 add more instructions 2018-09-22 22:12:06 -07:00
Chris Lu d3e1f20dcc adjust log level 2018-09-22 22:11:49 -07:00
Chris Lu 13d6a74eb2 consolidate file chunks during create 2018-09-22 22:11:37 -07:00
Chris Lu fdc8a267de remove unused function 2018-09-22 22:11:13 -07:00
Chris Lu 9a3d46f8e3 handle duplicated replication when retry happens 2018-09-22 11:14:04 -07:00
Chris Lu 01ceace18e adjust sink options 2018-09-22 00:53:52 -07:00
Chris Lu 402ab598b6 go fmt 2018-09-22 00:12:10 -07:00
Chris Lu 7bb62b9f21 delay file creation without chunks from dir.Create to file.Flush 2018-09-22 00:11:46 -07:00
Chris Lu f905a1e779 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-09-21 01:56:45 -07:00
Chris Lu db69ce89f0 go fmt 2018-09-21 01:56:43 -07:00
Chris Lu a6cfaba018 able to sync the changes 2018-09-21 01:54:29 -07:00
Chris LuandGitHub d6d62e8d4b Update README.md 2018-09-20 22:57:53 -07:00
Chris Lu a8cd7df44a let filer choose default replication 2018-09-20 09:00:15 -07:00
Chris Lu d5149e592e add logging on kafka reading 2018-09-19 22:31:45 -07:00
Chris Lu d9b32db8d7 adjust uploads to a hidden folder 2018-09-19 22:03:16 -07:00
Chris Lu f5471bcebf add bucket name in the redirection 2018-09-19 22:01:41 -07:00
Chris Lu 25fb6f9a46 fix compilation 2018-09-17 02:23:21 -07:00
Chris Lu 779641e9d4 adjust replicated entry name 2018-09-17 01:37:24 -07:00
Chris Lu b7a375f5aa set large limit for "weed filer.export" directory listing 2018-09-17 00:42:36 -07:00
Chris Lu 4e1f1a0b92 set large limit for "weed mount" directory listing 2018-09-17 00:41:33 -07:00
Chris Lu 788acdf527 add WIP filer.replicate 2018-09-17 00:27:56 -07:00
Chris Lu 865a017936 fix when if buffer is not aligned 2018-09-17 00:27:40 -07:00
Chris Lu 97575e7185 do not set attributes if the file is still open 2018-09-16 12:37:06 -07:00
Chris Lu 70e67b7c6d add notification log 2018-09-16 11:20:36 -07:00
Chris Lu 5c6e3f457f adjust log 2018-09-16 11:20:08 -07:00
Chris Lu bc03233364 refactoring 2018-09-16 01:37:35 -07:00
Chris Lu d923ba2206 renaming msgqueue to notification 2018-09-16 01:18:30 -07:00
Chris Lu bea4f6ca14 go fmt 2018-09-12 01:00:57 -07:00
Chris Lu f628953c33 cleanup tmp upload directories 2018-09-12 01:00:51 -07:00
Chris Lu f6d8525d1d working S3 multipart uploads 2018-09-12 00:46:12 -07:00
Chris LuandGitHub 98d9aadd37 Merge pull request #729 from hans-strudle/master
Add filer to docker-compose and entrypoint and build from local Dockerfile
2018-09-11 18:05:11 -07:00
hans-strudle bcd06304cc changed filer.toml to leveldb, added comments in docker-compose 2018-09-11 16:05:27 -07:00
Chris Lu 439e88b5a6 fix format error 2018-09-11 13:01:51 -07:00
hans-strudle 47b2e49068 Merge branch 'master' of https://github.com/hans-strudle/seaweedfs 2018-09-11 12:42:32 -07:00
Author Nameandhans-strudle 2bd2959ac3 add filer.toml 2018-09-11 12:41:38 -07:00
jenkins.ow bb7e165ce8 add filer.toml 2018-09-11 12:38:38 -07:00
jenkins.ow 1690a080b2 Merge branch 'master' of https://github.com/hans-strudle/seaweedfs 2018-09-11 12:38:10 -07:00
jenkins.ow bc025d5305 add filer to docker-compose and entrypoint, build from local Dockerfile 2018-09-11 12:37:27 -07:00
Chris Lu 267201ff44 handle non continuous writes
fix #728
2018-09-10 02:39:41 -07:00
Chris Lu 109385e955 simplifying logic to avoid handling non continuous writes
fix https://github.com/chrislusf/seaweedfs/issues/728
2018-09-10 02:21:57 -07:00
Chris Lu 296acc4a0a go fmt 2018-09-09 16:26:11 -07:00
Chris Lu 164091c269 add s3 multipart upload 2018-09-09 16:25:43 -07:00
Chris Lu 9b3bf0e46c fix "weed backup" rerunning
"weed backup" rerunning will already have ReplicaPlacement set, while version is not set.
2018-09-09 02:48:58 -07:00
Chris Lu bc8d34143d Revert "fix "weed backup" rerunning"
This reverts commit 1937be2bb2.
2018-09-09 02:38:30 -07:00
Chris Lu 1937be2bb2 fix "weed backup" rerunning
"weed backup" rerunning will already have ReplicaPlacement set, while version is not set.
2018-09-09 02:31:12 -07:00
Chris Lu d7a4b12d2a fix build error 2018-09-07 14:21:10 -07:00
Chris Lu 83ca595472 adjust version number 2018-09-07 14:19:48 -07:00
Chris Lu 747377142b fix file permission during writes 2018-09-07 13:57:06 -07:00
Chris Lu dd37f0540f add extended attributes 2018-09-07 13:12:52 -07:00
Chris Lu 75e749039b fix data writes error when consecutive calls overlaps with previous writes 2018-09-07 13:11:43 -07:00
Chris Lu 43ff60cb23 travis 1.10.x 2018-09-05 08:55:08 -07:00
Chris Lu 40d19d2064 travis using 1.10 2018-09-05 08:53:05 -07:00
Chris Lu f177cec509 avoid slice out of bounds
avoid this problem

2018/09/04 16:27:14 fuse: panic in handler for Write [ID=0x27c0d Node=0x2 Uid=0 Gid=0 Pid=0] 0x1 131072 @10607788032 fl=WriteCache lock=0 ffl=OpenReadOnly: runtime error: slice bounds out of range
goroutine 211141 [running]:
bazil.org/fuse/fs.(*Server).serve.func2(0x10d3e60, 0xc00014be30, 0xc00052fef8, 0xc00052fe77)
	/home/travis/gopath/src/bazil.org/fuse/fs/serve.go:857 +0x1ac
panic(0xe2d080, 0x17f62b0)
	/home/travis/.gimme/versions/go/src/runtime/panic.go:513 +0x1b9
github.com/chrislusf/seaweedfs/weed/filesys.(*ContinuousDirtyPages).saveToStorage(0xc0000aca80, 0x10d7ba0, 0xc0003fcc00, 0xc0005dc000, 0x20000, 0x1000000, 0x276720000, 0xc0003feaa0, 0x0, 0x0)
	/home/travis/gopath/src/github.com/chrislusf/seaweedfs/weed/filesys/dirty_page.go:142 +0x8ec
github.com/chrislusf/seaweedfs/weed/filesys.(*ContinuousDirtyPages).saveExistingPagesToStorage(0xc0000aca80, 0x10d7ba0, 0xc0003fcc00, 0x0, 0x0, 0x0)
	/home/travis/gopath/src/github.com/chrislusf/seaweedfs/weed/filesys/dirty_page.go:107 +0x6c
github.com/chrislusf/seaweedfs/weed/filesys.(*ContinuousDirtyPages).AddPage(0xc0000aca80, 0x10d7ba0, 0xc0003fcc00, 0x278460000, 0xc011966050, 0x20000, 0x20fb0, 0x6fc23ac00, 0x4a817c800, 0x0, ...)
	/home/travis/gopath/src/github.com/chrislusf/seaweedfs/weed/filesys/dirty_page.go:70 +0x8f
github.com/chrislusf/seaweedfs/weed/filesys.(*FileHandle).Write(0xc000548410, 0x10d7ba0, 0xc0003fcc00, 0xc00014be30, 0xc011946af8, 0x47fa01, 0x0)
	/home/travis/gopath/src/github.com/chrislusf/seaweedfs/weed/filesys/filehandle.go:141 +0x245
bazil.org/fuse/fs.(*Server).handleRequest(0xc0002cc0c0, 0x10d7ba0, 0xc0003fcc00, 0x10cb020, 0xc000394140, 0xc0000acac0, 0x10d3e60, 0xc00014be30, 0xc00052fef8, 0x10ca6a0, ...)
	/home/travis/gopath/src/bazil.org/fuse/fs/serve.go:1265 +0x1599
bazil.org/fuse/fs.(*Server).serve(0xc0002cc0c0, 0x10d3e60, 0xc00014be30)
	/home/travis/gopath/src/bazil.org/fuse/fs/serve.go:878 +0x410
bazil.org/fuse/fs.(*Server).Serve.func1(0xc0002cc0c0, 0x10d3e60, 0xc00014be30)
	/home/travis/gopath/src/bazil.org/fuse/fs/serve.go:425 +0x6e
created by bazil.org/fuse/fs.(*Server).Serve
	/home/travis/gopath/src/bazil.org/fuse/fs/serve.go:423 +0x321
2018-09-05 02:17:04 -07:00
Chris Lu d614716ff2 fix compilation error 2018-09-04 00:43:46 -07:00
Chris Lu ce6a2140a2 preparing to support S3 multipart uploads 2018-09-04 00:42:44 -07:00
Chris Lu 7f760f16b0 prepare for 0.95 2018-09-03 20:52:52 -07:00
Chris Lu 35bafd9861 remove unused function 2018-09-03 16:50:32 -07:00
Chris Lu 7ad5a90127 go fmt 2018-09-03 16:47:00 -07:00
Chris Lu 2ed6d8cca6 refactoring filer rm 2018-09-03 13:16:26 -07:00
Chris Lu 03f852c799 refactoring 2018-09-03 13:03:16 -07:00
Chris Lu 0b0ece9649 add place holders for multiplarts upload 2018-09-03 11:38:10 -07:00
Chris Lu 0adb2ba6b8 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-09-03 00:29:02 -07:00
Chris Lu 06b81c063a fix possible connection counting error 2018-09-03 00:28:58 -07:00
Chris LuandGitHub 4fbb57f789 Merge pull request #719 from zhangmingfeng/master
Update volume_server_handlers_read.go
2018-09-02 21:08:59 -07:00
zhangmingfengandGitHub aafe8d84cb Update volume_server_handlers_read.go
change ext to lower-case
2018-09-03 11:01:35 +08:00
Chris Lu 88f1d32cc4 add s3ChunkedReader
fix https://github.com/chrislusf/seaweedfs/issues/718
2018-09-02 14:20:47 -07:00
Chris Lu acd8836d27 add ignore .class files 2018-09-02 14:19:47 -07:00
Chris Lu 73f331df84 add java example of copying to weed s3 end point 2018-09-02 14:19:24 -07:00
Chris Lu 76cbe8bf33 instant notification of new volumes added or deleted 2018-08-24 01:26:56 -07:00
Chris Lu 5ccf8e8078 reformat 2018-08-24 00:30:03 -07:00
Chris Lu ac793a3c5a clean remove all left over files when deleting a collection 2018-08-23 23:33:16 -07:00
Chris Lu d361a1ddf5 fix id String() instead 2018-08-23 22:44:30 -07:00
Chris Lu 694f93de80 parse needle id correctly
fix https://github.com/chrislusf/seaweedfs/issues/713
2018-08-23 20:48:43 -07:00
Chris Lu 37e4e022fd prepare for 0.94 release 2018-08-23 00:02:13 -07:00
Chris Lu 6e3f4d1079 add working filer.export command 2018-08-23 00:02:04 -07:00
Chris Lu 98b8f8649d adding filer.export prototype 2018-08-22 23:54:22 -07:00
Chris Lu 2b4277ec18 fix nil bug with filer.toml 2018-08-22 23:54:00 -07:00
Chris Lu ae4e654d2a add example configuration for default redis create-cluster setup 2018-08-22 18:23:19 -07:00
Chris Lu b23883b3b6 fix a bug on error reporting 2018-08-22 18:19:26 -07:00
Chris Lu 1f030af06f adjusting startup logging 2018-08-22 13:59:04 -07:00
Chris Lu bb92fd1db0 fix test error 2018-08-19 18:52:50 -07:00
Chris Lu 53d171cf5b releasing 0.93 2018-08-19 18:45:26 -07:00
Chris Lu 906472b21d filer add upload and drag&dorp 2018-08-19 18:42:40 -07:00
Chris Lu f22d6b87e1 go fmt 2018-08-19 15:42:50 -07:00
Chris Lu 748870b03f add "weed scaffold" command 2018-08-19 15:36:30 -07:00
Chris Lu 95b997fb35 go fmt 2018-08-19 15:18:37 -07:00
Chris Lu f827ada811 merge notification config with filer.toml 2018-08-19 15:17:55 -07:00
Chris Lu c91372daa6 add one example filer.toml 2018-08-19 14:58:24 -07:00
Chris Lu ee920d4f83 kafka set the right topic 2018-08-19 01:27:30 -07:00
Chris Lu 708acee502 add support for redis cluster
fix https://github.com/chrislusf/seaweedfs/issues/705
2018-08-15 00:01:38 -07:00
Chris Lu 9b51ed7af2 fix test 2018-08-13 01:38:18 -07:00
Chris Lu ae9b81c18f fix tests 2018-08-13 01:33:21 -07:00
Chris Lu 5c467083e5 go fmt 2018-08-13 01:22:32 -07:00
Chris Lu f036ef8a3c add filer notification 2018-08-13 01:20:49 -07:00
Chris Lu 75d63db60d randomize raft server startup
also some go fmt
2018-08-12 14:27:14 -07:00
Chris Lu eca4b928d2 ensure master server count is odd 2018-08-12 14:25:31 -07:00
Chris Lu b931ced0a3 readable file size 2018-08-10 23:47:31 -07:00
Chris Lu 2e427c66ac small refactoring 2018-08-10 23:47:17 -07:00
Chris Lu c81f1cda47 correctly recursively delete folders 2018-08-01 01:26:41 -07:00
Chris Lu fa3ded4134 adjust naming 2018-07-31 23:39:56 -07:00
Chris Lu 495a776671 support 128 bit NeedleId 2018-07-31 23:25:26 -07:00
Chris Lu 44d8e35988 add listener for volumd id location for benchmark tool 2018-07-31 19:12:36 -07:00
Chris Lu d09606c65b refactoring 2018-07-29 02:25:24 -07:00
Chris Lu 032f1226db fix compilation error 2018-07-29 02:25:03 -07:00
Chris LuandGitHub 452bd0b013 Merge pull request #702 from chrislusf/add_topo_listener
Add volume id location change listener
2018-07-28 21:03:29 -07:00
Chris Lu d3205a0070 go fmt 2018-07-28 21:02:56 -07:00
Chris Lu 97603d6e17 Update issue templates 2018-07-28 20:57:16 -07:00
Chris Lu b5100a7aaf Update issue templates 2018-07-28 20:54:38 -07:00
Chris Lu 7214a8e265 fix init error 2018-07-28 18:40:31 -07:00
Chris Lu cfbfc7cb67 fix compilation error 2018-07-28 18:34:15 -07:00
Chris Lu 4e0522a80c adjust API 2018-07-28 18:17:31 -07:00
Chris Lu 888eb2abb5 filer read write all via locations from MasterClient 2018-07-28 14:51:36 -07:00
Chris Lu 1d779389cb MasterClient replicates all vid locations 2018-07-28 14:22:46 -07:00
Chris Lu 01bcc89803 refactor into MasterClient 2018-07-28 02:10:32 -07:00
Chris Lu 1ab8232b55 filer only talk to the master leader 2018-07-28 01:30:03 -07:00
Chris Lu e8d4be579d send initial list of volume location 2018-07-28 01:17:35 -07:00
Chris Lu a12c7b86b0 broadcast messages of new and deleted volumes 2018-07-27 23:09:55 -07:00
Chris Lu f82ac793b4 Merge branch 'master' into add_topo_listener 2018-07-27 02:26:43 -07:00
Chris Lu 10bb76fe6b go fmt 2018-07-27 02:10:42 -07:00
Chris Lu 4d322df95e avoid reparsing the multipart form 2018-07-27 02:10:10 -07:00
Chris Lu 0d98949199 tmp commit 2018-07-27 01:54:45 -07:00
Chris Lu e4b7e31902 add back recursively delete a folder 2018-07-24 22:33:26 -07:00
Chris Lu 57ded4fd80 speed up leveldb bolddb loading 2018-07-24 22:17:56 -07:00
Chris Lu ea8ecf35f4 set needle AppendAtNs value during writes 2018-07-24 02:44:33 -07:00
Chris Lu 655b449270 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-07-24 02:20:02 -07:00
Chris LuandGitHub 0fb4f59fa6 Merge pull request #697 from chrislusf/add_version3
Add version3
2018-07-24 02:19:06 -07:00
Chris Lu 69d1b88b9e starting on 0.93 beta 2018-07-24 02:18:39 -07:00
Chris Lu e8a5ee4f48 releasing 0.92
ensure the nil problem is resolved.
2018-07-24 01:54:15 -07:00
Chris Lu 77fadab560 go fmt 2018-07-24 01:38:08 -07:00
Chris Lu 4f317c7e3d working version3 2018-07-24 01:36:04 -07:00
Chris Lu 0b99557c29 fix compilation error 2018-07-24 01:09:32 -07:00
Chris Lu 9bf93e698e refactoring: avoid common code 2018-07-24 00:58:52 -07:00
Chris Lu aae77e8ea3 initialize read seeker 2018-07-24 00:54:15 -07:00
Chris Lu c7af289f4b Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-07-24 00:49:59 -07:00
Chris Lu 5a237af973 fix nil if not images 2018-07-24 00:49:53 -07:00
Chris LuandGitHub 6491192aa3 add amazon s3 2018-07-23 22:18:26 -07:00
Chris Lu a9069520ae start 0.92 beta2 2018-07-23 01:59:23 -07:00
Chris Lu 04356ad141 re-releasing 0.91 2018-07-23 01:58:13 -07:00
Chris Lu 6039b25bc9 s3API working with listing objects 2018-07-23 01:55:26 -07:00
Chris Lu 3c7ffa2a2c go fmt 2018-07-23 01:16:17 -07:00
Chris Lu ebad3a44ab s3api do not proxy directory requests 2018-07-23 01:15:59 -07:00
Chris Lu 7f32eb1e25 s3 add https support 2018-07-22 21:28:54 -07:00
Chris Lu c133e72f62 print out unsupported methods 2018-07-22 19:55:53 -07:00
Chris LuandGitHub ff331178aa Merge pull request #695 from zhangmingfeng/master
增加chunk图片文件支持width和height
2018-07-22 19:54:04 -07:00
Chris LuandGitHub 7e76a7d21b Merge pull request #694 from himyxia/inprove_switch_case
better way to handler switch case
2018-07-22 10:29:37 -07:00
Jimmy 1c77d323b0 better way to handler switch case 2018-07-23 01:27:10 +08:00
Chris Lu 5e55f0da4d add WIP 0.92 beta 2018-07-22 02:15:24 -07:00
Chris Lu 19c537b687 releasing 0.91 2018-07-22 02:12:02 -07:00
Chris Lu 20853972b2 s3 API add ListObjectsV2Handler 2018-07-22 02:04:07 -07:00
Chris Lu 056871242b remove println 2018-07-22 01:17:49 -07:00
Chris Lu 0655151b8c go fmt 2018-07-22 01:15:11 -07:00
Chris Lu 6319d84f42 s3 API add ListObjectsV1 2018-07-22 01:14:36 -07:00
Chris Lu 922c614bde fix compilation problem 2018-07-21 20:42:14 -07:00
Chris Lu 16c66e097b adjust default buckets folder 2018-07-21 20:29:33 -07:00
Chris LuandGitHub 3423c1da18 Merge pull request #693 from chrislusf/add_s3
Add "weed s3" to support S3 API
2018-07-21 20:14:38 -07:00
Chris Lu 49375d6031 adjust DELETE response to S3 format 2018-07-21 19:12:44 -07:00
Chris Lu 5068b6ae7d fix compilation 2018-07-21 19:06:09 -07:00
Chris Lu 12825938d7 unused method 2018-07-21 18:50:56 -07:00
Chris Lu 8f240276db s3 add HEAD DELETE 2018-07-21 18:49:47 -07:00
Chris Lu bfa87b4f50 adjust log level 2018-07-21 18:47:23 -07:00
Chris Lu 648626115a fix printing 2018-07-21 18:47:07 -07:00
Chris Lu 12e6ccc581 remove unused code 2018-07-21 17:54:14 -07:00
Chris Lu 702d7ac424 delegating old chunk purging to CreateEntry
1. filer add file user id and group id
2. auto-chunking set the file name correctly
3. delegating old chunk purging to CreateEntry
2018-07-21 17:47:59 -07:00
Chris Lu 852af28f91 needle priting format 2018-07-21 17:41:21 -07:00
Chris Lu 6f30a78a6c update also delete old chunks if different from latest chunks 2018-07-21 17:40:00 -07:00
Chris Lu 7e2031b18f go fmt 2018-07-21 17:39:10 -07:00
Chris Lu dc8dd8b486 Merge branch 'master' into add_s3 2018-07-21 15:59:18 -07:00
Chris Lu c98df05ed0 support PUT 2018-07-21 15:58:48 -07:00
Chris Lu feb8eeb830 refactor: separate multipart parsing file 2018-07-21 12:01:35 -07:00
Chris Lu 8480008a9a add s3 upload, and removing mono and multi part upload analyzer
removing mono and multi part upload analyzer, which were used just to determine the file name
2018-07-21 10:39:02 -07:00
Chris Lu 80d80daf64 set filer2.ErrNotFound for not found entry 2018-07-20 02:14:18 -07:00
Chris Lu dad733086a fix string printing 2018-07-20 00:58:10 -07:00
Chris Lu e73b859b1d fix compilation error 2018-07-20 00:46:02 -07:00
Chris Lu 568132c0c9 delete bucket also removes collection data 2018-07-20 00:10:01 -07:00
Chris Lu 13e5541e17 FUSE can change file or folder attributes
FUSE can change file or folder attributes
2018-07-19 02:17:36 -07:00
Chris Lu a09ef6002a adjust name to avoid confusion 2018-07-19 01:46:18 -07:00
Chris Lu feea33d5e0 add HeadBucket 2018-07-19 01:43:27 -07:00
Chris Lu d429b6296b fix test 2018-07-19 01:27:45 -07:00
Chris Lu c7228fefa7 add bucket creation and deletion
1. option for "weed s3 -filer.dir.buckets" to choose a folder for buckets
2. create a bucket
3. delete a bucket, recursively delete all metadata on filer
2018-07-19 01:21:44 -07:00
Chris Lu 702fbdf731 refactoring 2018-07-18 02:39:12 -07:00
Chris Lu 834a25f084 add list all my buckets 2018-07-18 02:37:09 -07:00
Chris Lu 7abfab8e77 add feature to mount a specific filer path to local directory 2018-07-14 21:09:21 -07:00
Chris Lu 3edfe1d28f extend export command to show tombstone + change output format to CSV
merging https://github.com/chrislusf/seaweedfs/pull/610 and add "-limit" option
2018-07-14 20:51:17 -07:00
Chris Lu 85eae57e4c Store and replicate timestamp of file deletion in tombstone marker
merging changes from https://github.com/chrislusf/seaweedfs/pull/609/files
2018-07-14 20:26:45 -07:00
Chris Lu 67763fb375 fix data center setting error 2018-07-14 14:09:45 -07:00
Chris Lu 6d58916cfd fix compilation error 2018-07-14 13:39:57 -07:00
Chris Lu 842dab07b4 weed mount can request to prioritize to write to a data center 2018-07-14 13:36:28 -07:00
Chris Lu a51aa4c586 avoid extra log messages
fix https://github.com/chrislusf/seaweedfs/issues/689
2018-07-13 22:07:32 -07:00
Chris LuandGitHub 9e974f6a1b Merge pull request #686 from qszxnp/filer2_dir_cache_bugfix
bugfix:delete Directory cache when filer2 delete Directory
2018-07-12 01:17:32 -07:00
Chris LuandGitHub cf5739946b Merge pull request #687 from qszxnp/filer2_dir_create_bugfix
bugfix:check Directory attr  before create it
2018-07-12 01:16:49 -07:00
聂鹏 1ed41f8b21 bugfix:check Directory attr before create it 2018-07-12 00:53:31 -04:00
聂鹏 c523bc4f07 bugfix:delete Directory cache when filer2 delete Directory 2018-07-11 21:27:25 -04:00
Chris Lu 2fd48df9d9 fix test 2018-07-11 12:52:48 -07:00
Chris Lu f7afa80250 also remove volumeLayout if collection is empty 2018-07-11 02:01:33 -07:00
Chris Lu 1e1c368ae8 adjust filer help message 2018-07-10 00:22:11 -07:00
Chris Lu 007c4a1965 alt request for dc preferred requests 2018-07-10 00:20:50 -07:00
Chris Lu 8a12fd9ec4 add error checking when loading dat file 2018-07-09 23:31:25 -07:00
Chris Lu 6c4fee85e3 add primary request and backup request when requesting volumes for a data center 2018-07-09 23:18:20 -07:00
Chris Lu e2849a36e5 fix compilation errors 2018-07-09 22:48:30 -07:00
Chris LuandGitHub 3db6182181 Merge pull request #680 from moqi-ai/master
fix the tool for change replication factor
2018-07-09 22:36:53 -07:00
Qin Liu 940b5edcaa fix the tool for change replication factor 2018-07-09 20:35:04 +08:00
Chris Lu 50d351944f filer add data center option 2018-07-09 02:22:48 -07:00
Chris Lu d0982cafa7 refactoring 2018-07-09 00:22:50 -07:00
Chris Lu 5b1fd374be fix all tests 2018-07-08 02:39:04 -07:00
Chris Lu d4d7ced922 refactoring: add type for needle id, offset
later the type size can possibly be adjusted
2018-07-08 02:28:04 -07:00
Chris Lu 922032b9bb add filer sub dir list size limit to "weed server" 2018-07-08 02:11:36 -07:00
Chris Lu 702ff48cda add dir list limit option 2018-07-07 02:18:47 -07:00
Chris Lu 2ad45ca04f refactor 2018-07-07 02:03:25 -07:00
Chris Lu 5bfb72d058 faster loading boltdb or leveldb needle map metrics by bloomfilter
avoid btree
2018-07-07 00:51:17 -07:00
Chris Lu aba1fe01b3 use constant NeedleIndexSize instead of the number 16 2018-07-07 00:50:14 -07:00
Chris Lu defcd4edc5 adjust log level 2018-07-07 00:48:58 -07:00
Chris Lu c8234a5af6 ensure using none nil attributes
fix https://github.com/chrislusf/seaweedfs/issues/674
2018-07-05 23:16:34 -07:00
zhangmingfeng 79d18c69b4 增加chunk图片文件支持width和height 2018-07-05 10:34:17 +08:00
Chris Lu 77fc8c5914 keep alive for gRpc calls 2018-07-03 19:07:55 -07:00
Chris Lu 28e5f20c8e add super block extra for erasure coding 2018-06-28 22:20:37 -07:00
Chris Lu 9e6ad558c5 upgrading dependencies to latest versions 2018-06-28 22:03:47 -07:00
Chris Lu 9bcaa65574 avoid divided by 0
fix https://github.com/chrislusf/seaweedfs/issues/650
2018-06-25 01:20:15 -07:00
Chris Lu 0d83c1b91e refactoring 2018-06-25 00:01:53 -07:00
Chris Lu 018a9a20be add test for volume registeration at master 2018-06-24 23:55:27 -07:00
Chris Lu cb19a7da99 add a note 2018-06-24 23:55:06 -07:00
Chris Lu ef98d26c9e ensuring removing registered datanode when disconnecting 2018-06-24 23:20:27 -07:00
Chris Lu afc1532b24 file rename 2018-06-24 23:19:56 -07:00
Chris Lu 233a9a9b6c fix log error 2018-06-24 16:12:41 -07:00
Chris Lu 49f4216280 fix compilation error 2018-06-24 15:37:54 -07:00
Chris Lu 7f6500ae07 super block reserves a couple of bits for future extension 2018-06-24 15:23:03 -07:00
Chris Lu f339e9c284 add support for extra super block info 2018-06-24 15:19:57 -07:00
Chris Lu 03a0def4c3 write in original super block version 2018-06-24 15:19:36 -07:00
Chris Lu 9ba335a7c4 prepare for flexible super block 2018-06-24 11:37:08 -07:00
Chris Lu b46cf2bb0e Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-06-24 09:29:36 -07:00
Chris Lu c0122eeef3 start 0.91 beta 2018-06-24 09:29:34 -07:00
Chris LuandGitHub f1e3ac5dec Merge pull request #667 from amincheloh/patch-2
README.md: fix invalid markdown support link
2018-06-24 00:21:01 -07:00
Amin ChelohandGitHub 16e72211c9 README.md: fix invalid markdown support link 2018-06-24 12:10:02 +07:00
Chris Lu cf76711789 releasing 0.90 version 2018-06-23 18:36:27 -07:00
Chris Lu 241039ebbe travis does not support 1.10 format 2018-06-23 18:34:50 -07:00
Chris Lu 697427b584 trying 1.10 2018-06-23 18:33:55 -07:00
Chris Lu 3a6f9c9bcf increase test size 2018-06-23 18:30:13 -07:00
Chris Lu 7f7e4e9885 fix error for deleted files during compaction
deletion during commit may cause trouble when make up the difference during commitCompact()
2018-06-23 18:24:59 -07:00
Chris Lu 3699996985 add compaction test 2018-06-23 16:48:19 -07:00
Chris LuandGitHub 00fbca99dc Update README.md 2018-06-17 16:55:35 -07:00
Chris Lu 131dfd0c19 adjust the differences 2018-06-17 16:34:10 -07:00
Chris Lu a8d343318b compared to GlusterFS, Ceph 2018-06-17 16:28:25 -07:00
Chris Lu f0845c7fff add Filer 2018-06-17 13:40:15 -07:00
Chris Lu 14c6f12b0c adjust interface parameter name and doc 2018-06-17 13:24:57 -07:00
Chris Lu a08f8a4b80 Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2018-06-17 13:02:25 -07:00
Chris Lu db0c25b3d8 delete old entry only after successful insert 2018-06-17 13:02:22 -07:00
Chris Lu eb2acd11c2 decouple from viper for filer store 2018-06-17 13:01:57 -07:00
Chris LuandGitHub 5519dbd445 Update README.md 2018-06-17 02:57:30 -07:00
Chris LuandGitHub 7b621bb5e1 Update README.md 2018-06-17 02:52:09 -07:00
Chris Lu 0467195f07 fix file closing on windows 2018-06-13 00:36:51 -07:00
Chris Lu 4433b4c006 treat 0 sized file as normal
fix https://github.com/chrislusf/seaweedfs/issues/409
2018-06-12 21:11:26 -07:00
Chris Lu 03f50180f3 simplifying the leader election by raft
fixing https://github.com/chrislusf/seaweedfs/issues/629
2018-06-12 01:54:09 -07:00
Chris Lu 69b4f93830 fix compilation for go tip 2018-06-12 00:58:43 -07:00
Chris Lu 17918f0b82 fix compilation error on go tip 2018-06-11 23:51:36 -07:00
Chris Lu eaf000eff1 fix error on go tip 2018-06-11 23:39:42 -07:00
Chris Lu d2a811eef2 fix compilation 2018-06-11 23:29:09 -07:00
Chris Lu 5bd72696ac weed mount add ttl option 2018-06-11 23:13:33 -07:00
Chris Lu 98110c1697 add collection, replication, ttl to each file 2018-06-10 16:57:32 -07:00
Chris Lu f1273073fc switch to fs.NodeRequestLookuper
in order to set the entry valid duration
2018-06-07 00:07:37 -07:00
Chris Lu cec1d97035 recursively move files and directories 2018-06-06 23:39:30 -07:00
Chris Lu ae23e46313 better error log 2018-06-06 23:06:19 -07:00
Chris Lu e755540be9 fix caching during directory listing 2018-06-06 22:55:59 -07:00
Chris Lu daabdfe357 remove nodemap, fix directory listing cache 2018-06-06 22:48:51 -07:00
Chris Lu 5c25d29272 support renaming files 2018-06-06 22:11:01 -07:00
Chris Lu b3447f4375 adjust logging 2018-06-06 02:21:36 -07:00
Chris Lu 6816661b0f fixed file handle by file full path 2018-06-06 02:09:57 -07:00
Chris Lu 299312c805 use separate filer grpc port 2018-06-05 23:37:41 -07:00
Chris Lu 95fe745a0c compatible with previous json rendering 2018-06-04 19:28:59 -07:00
Chris Lu c546c309f1 Merge branch 'filer2_development' 2018-06-02 00:35:03 -07:00
Chris Lu a146a48ccc remove old file 2018-06-02 00:34:15 -07:00
Chris Lu ebf58709d3 remove unused register file function 2018-06-01 23:24:34 -07:00
Chris Lu 43e3f5724c use fixed list of masters in both filer and volume servers 2018-06-01 00:39:39 -07:00
Chris Lu a6f7f9b0b8 add missing modify and create times 2018-05-31 22:50:19 -07:00
Chris Lu 7efeb146c5 fix log 2018-05-31 22:49:55 -07:00
Chris Lu 4d1eedfa28 prepare to merge with master 2018-05-30 22:29:18 -07:00
Chris Lu dc13e10637 fix copy error on 0 size files 2018-05-30 22:28:14 -07:00
Chris Lu a218eaf1f0 fix log 2018-05-30 22:09:24 -07:00
Chris Lu 430eb67489 handle large file copy when write request is larger than buffer 2018-05-30 22:02:21 -07:00
Chris Lu 44acf4b756 fix file name
f.Name() is not the base file name
2018-05-30 20:48:13 -07:00
Chris Lu 0301504184 add mime, use simple insert and update filer store API
1. add mime type to file in filer
2. purge old chunks if overwrite during insert
2018-05-30 20:24:57 -07:00
Chris Lu 26e7cd8c75 assign a different volume on large file copying 2018-05-30 01:05:26 -07:00
Chris Lu 2fe0d479f1 filer.copy supports chunking 2018-05-30 00:54:56 -07:00
Chris Lu 2d7dea1a09 a little refactoring 2018-05-29 23:52:27 -07:00
Chris Lu 8a48baa056 add single chunk file copying to new filer 2018-05-29 23:46:45 -07:00
Chris Lu 78aabf66ce add error details 2018-05-29 23:46:17 -07:00
Chris Lu 4e3ea49cff properly working local write buffer 2018-05-29 01:21:21 -07:00
Chris Lu c4b92e17d0 fix isPerfectAppend 2018-05-28 22:45:52 -07:00
Chris Lu 74332e1a61 minor 2018-05-28 14:32:16 -07:00
Chris Lu be0e88a606 fix chunk size limit for default 0 2018-05-28 13:44:27 -07:00
Chris Lu 5c4480ec6c add mountOptions.chunkSizeLimitMB, remove cmdMount.IsDebug 2018-05-28 13:42:25 -07:00
Chris Lu 8ab7dd9d08 weed mount add options for collection and replication 2018-05-28 13:24:48 -07:00
Chris Lu d0b238d2db cache local writes before flushing to volume server 2018-05-28 12:30:17 -07:00
Chris Lu 07e0d13d2d filer support reading multiple chunks, with range support 2018-05-28 05:39:12 -07:00
Chris Lu dd5661b297 adding favicon to filer also 2018-05-28 02:35:58 -07:00
Chris Lu 9b603f5ffa split filer read into 2 files 2018-05-28 02:24:14 -07:00
Chris Lu 8db9319a06 refactoring: go fmt, reorg 2018-05-27 23:59:49 -07:00
Chris Lu 480a073f1f refactoring: split into 4 files 2018-05-27 23:53:10 -07:00
Chris Lu 5b844d7011 pass reference of master server
SendHeartbeat passes lock by value: weed_server.MasterServer contains
sync.Mutex
2018-05-27 11:58:00 -07:00
Chris Lu 8b0718ac92 go vet 2018-05-27 11:56:49 -07:00
Chris Lu 458ada173e go fmt 2018-05-27 11:52:26 -07:00
Chris Lu 8647191bee removing filer package! 2018-05-27 11:52:15 -07:00
Chris Lu 03d76479a5 add reds 2018-05-27 11:14:29 -07:00
Chris Lu a6a6b87f1b adjust readme 2018-05-27 11:14:12 -07:00
Chris Lu ab86d263c9 add Cassandra 2018-05-27 00:01:15 -07:00
Chris Lu 603de2d5db skip deletion if entry not found 2018-05-27 00:00:56 -07:00
Chris Lu f124ebab1d fix ordering 2018-05-27 00:00:11 -07:00
Chris Lu 1c0a2a7db8 reformat 2018-05-26 23:59:56 -07:00
Chris Lu 869161a261 support both mysql and postgres 2018-05-26 22:02:49 -07:00
Chris Lu 87b3b84471 simplifying dirhash to 64bit integer 2018-05-26 21:24:03 -07:00
Chris Lu 955eae3500 this works great and support long file names 2018-05-26 14:08:55 -07:00
Chris Lu 2da84ed331 working fine now
but index length can be improved
2018-05-26 13:35:56 -07:00
Chris Lu 68bcaff14d mysql can compile, not yet tested! 2018-05-26 05:32:15 -07:00
Chris Lu c1e353491a refactoring variable names 2018-05-26 04:50:55 -07:00
Chris Lu 828e4a5ace fix test error 2018-05-26 04:13:50 -07:00
Chris Lu 9e77563c99 add leveldb store
1. switch to viper for filer store configuration
2. simplify FindEntry() return values, removing “found”
3. add leveldb store
2018-05-26 03:49:46 -07:00
Chris Lu c34feca59c refactoring 2018-05-25 23:27:06 -07:00
Chris Lu 6de84c64c6 adding create time 2018-05-25 23:26:40 -07:00
Chris Lu 72fc001d0d remove unused code 2018-05-25 01:30:16 -07:00
Chris Lu ac66c133a5 do not read attributes when file is opened 2018-05-25 01:27:21 -07:00
Chris Lu 6d1bcd4b8c use existing attributes instead of fetching from filer 2018-05-25 01:22:31 -07:00
Chris Lu 0a223838bd refactoring 2018-05-25 00:57:25 -07:00
Chris Lu f8776ad5cd add breadcrumb to filer UI 2018-05-24 23:42:13 -07:00
Chris Lu a01557f4cb adjust filer web page 2018-05-24 23:21:12 -07:00
Chris Lu c11d84f314 fix reading from a url 2018-05-24 23:20:56 -07:00
Chris Lu 7b81cf3762 better logs 2018-05-24 23:20:26 -07:00
Chris Lu 9f4f8de9ad skip printout 2018-05-24 23:20:12 -07:00
Chris Lu e18c7e160c avoid empty chunk view 2018-05-24 23:19:56 -07:00
Chris Lu d773e11c7a file handler directly read from volume servers
this mostly works fine now!

next: need to cache files to local disk
2018-05-24 01:22:37 -07:00
Chris Lu 00d0274fd7 prepare to read from multiple file chunks 2018-05-23 22:28:54 -07:00
Chris Lu 849b6ec28d seems editing already working
Need to handle multiple chunks read.
Need to cache local file changes.
2018-05-23 20:55:24 -07:00
Chris Lu d84b80b795 similar changes as master 2018-05-23 03:23:47 -07:00
Chris Lu 536559f62d copy works, edit somehow still fails 2018-05-23 03:08:46 -07:00
Chris Lu 1675243f29 maybe speed up a little when loading index 2018-05-23 02:38:11 -07:00
Chris Lu e97c60cc10 avoid overwriting variables 2018-05-23 02:36:19 -07:00
Chris Lu 468514f525 reduce logs 2018-05-23 02:36:06 -07:00
Chris Lu 3a3553dc46 remove unused files 2018-05-22 11:12:59 -07:00
Chris Lu 88d608b661 avoid using the bytes out side of the transaction
fix https://github.com/chrislusf/seaweedfs/issues/656
2018-05-22 10:19:44 -07:00
Chris Lu 69b9d8c3c2 fix boltdb variable usage 2018-05-22 10:18:09 -07:00
Chris Lu 873868cc10 not working now
need to add file handler
2018-05-22 04:31:44 -07:00
Chris Lu 7362de9a18 weed mount can work well
TODO: somehow filer url is returning empty content
2018-05-22 03:26:38 -07:00
Chris LuandGitHub 03fd66e209 Merge pull request #655 from chrislusf/filer1_maintenance_branch
fix mysql bug
2018-05-22 00:16:42 -07:00
Chris Lu 04c2c5e58d fix mysql data store bug 2018-05-22 00:15:11 -07:00
Chris Lu c5a63f4775 Merge branch 'master' into filer1_maintenance_branch 2018-05-21 10:33:17 -07:00
Chris Lu 6ce0097846 add left padding
fix https://github.com/chrislusf/seaweedfs/issues/654
2018-05-21 10:28:46 -07:00
Chris Lu 9dd228747c filer copy added uid/gid 2018-05-21 01:25:30 -07:00
Chris Lu f07482382b able to update file content
having some issue when vi reports file changed.
2018-05-21 00:00:28 -07:00
Chris Lu cd47528a75 remove unused code 2018-05-20 17:08:54 -07:00
Chris Lu cc66e25cd2 merge intervals 2018-05-20 17:06:09 -07:00
Chris Lu 7ca5052942 create files correctly! 2018-05-19 13:51:44 -07:00
Chris Lu 793dd81ca2 skip permission checking when creating dir or files 2018-05-19 12:40:24 -07:00
Chris Lu ab4ddb1e0e fix directory creation, directory listing 2018-05-19 12:07:15 -07:00
Chris Lu e31c514b00 adding modified time to file chunk 2018-05-16 00:54:44 -07:00
Chris Lu 6bf31467c7 adding empty fsync
less error now. But still:

vi on write: E514: write error (file system full?)
cp: Input/output error
2018-05-16 00:54:27 -07:00
Chris Lu b303a02461 cp file can work
1. consolidate to filer_pb.FileChunk
2. dir add file, mkdir
3. file flush, write

updates having issue
2018-05-16 00:08:44 -07:00
Chris Lu c7a71d35b0 fix on pagination 2018-05-14 20:27:48 -07:00
Chris Lu 58954bf46f pass file attributes from filer to mount 2018-05-14 02:02:17 -07:00
Chris Lu 67401f1908 fix gRpc nil response 2018-05-14 00:17:22 -07:00
Chris Lu c5cf9bd290 properly working filer 2018-05-13 23:56:16 -07:00
Chris Lu f01d5616b3 add better listing directory entries 2018-05-13 14:02:29 -07:00
Chris Lu a4740ca836 fix compilation error for windows 2018-05-13 00:49:59 -07:00
Chris Lu 2dd5b777f7 prepare for 0.77 release
right before changing to filer2
2018-05-13 00:26:23 -07:00
Chris Lu 75a2b05aa3 remove Size from Attr 2018-05-13 00:11:26 -07:00
Chris Lu fc7f81f6e7 remove nlink property from directory node 2018-05-12 22:42:28 -07:00
Chris Lu 9ecbf92569 more tests 2018-05-12 13:50:11 -07:00
Chris Lu 81af1bafba file store adds memdb implementation 2018-05-12 13:45:29 -07:00
Chris Lu a808df5019 visit parent folder first 2018-05-11 02:27:57 -07:00
Chris Lu 8a1d640dc4 starting filer2 2018-05-11 02:20:15 -07:00
Chris Lu 9f345da20f mv filer proto to filer_pb 2018-05-09 23:18:02 -07:00
Chris Lu 942c2cbd7b mv pb to master_pb 2018-05-09 23:11:54 -07:00
Chris Lu 57c5405657 add todo 2018-05-09 04:18:31 -07:00
Chris Lu 6db1c8bafb Revert "adjust the needle mapper"
This reverts commit e2c7be3a2e.
2018-05-09 03:57:07 -07:00
Chris Lu e2c7be3a2e adjust the needle mapper
as discussed on
https://github.com/chrislusf/seaweedfs/pull/642#issuecomment-387606960
2018-05-09 03:50:14 -07:00
Chris Lu 43a69d20bf change filer API to gRPC 2018-05-08 01:59:43 -07:00
Chris Lu 4936d6c342 adjusting for FUSE 2018-05-07 00:37:47 -07:00
Chris Lu cb6b1d8d41 fix broken master health checking 2018-05-07 00:37:33 -07:00
Chris Lu be58993f47 ensure the same Node is returned 2018-05-05 23:50:34 -07:00
Chris Lu b857cf9d9b format 2018-05-05 23:39:29 -07:00
Chris Lu 456738ba64 refactoring fuse 2018-05-05 22:47:16 -07:00
Chris Lu 2cada598c6 fix test 2018-05-05 15:08:23 -07:00
Chris Lu f3e07c231b adding folder icon 2018-05-05 14:41:40 -07:00
Chris LuandGitHub 4d5e1e5947 Merge pull request #645 from chrislusf/vasto_filer
merge back filer related refactoring
2018-05-05 14:12:51 -07:00
Chris Lu 7f49514f6f fix filer UI 2018-05-05 14:10:50 -07:00
Chris Lu fffb14bc87 better support FUSE Lookup() 2018-05-05 02:01:50 -07:00
Chris Lu 050ab19264 add design 2018-05-04 23:38:42 -07:00
Chris Lu 94a35f25f3 simple refactoring 2018-05-03 00:16:02 -07:00
Chris LuandGitHub 63c4884b16 Merge pull request #642 from whynotAC/master
fix leveldb mount bug
2018-05-02 21:38:11 -07:00
WenLong LIandGitHub b9068c1304 use volumes variable
fix leveldb mount bug
2018-05-03 12:13:53 +08:00
liwenlong05 02a1597642 fix leveldb mount bug 2018-05-03 11:20:38 +08:00
Chris LuandGitHub 230258702f Merge pull request #637 from mtolman/master
Fix for issue #635
2018-04-17 12:53:38 -07:00
mtolmanandGitHub e4b1e5c3c8 Fix for issue #635
Fix for the following issue on master: https://github.com/chrislusf/seaweedfs/issues/635
2018-04-17 13:26:48 -06:00
Chris Lu d49e91cd78 cassandra use local quorum for better performance 2018-04-16 01:33:05 -07:00
Chris Lu 69facc0e2f skip 1.10 on travis
travis misunderstood 1.10 as 1.1
2018-04-16 01:30:10 -07:00
Chris Lu 15b8499f8d refactoring 2018-04-16 01:16:06 -07:00
Chris Lu 01dc9e8abb update go version 2018-04-16 01:01:10 -07:00
Chris Lu d712c87ec0 update glide 2018-04-16 00:59:10 -07:00
Chris Lu 6448e0c25d update glide 2018-04-16 00:38:49 -07:00
Chris Lu 8c64d06b3a update redis library 2018-04-16 00:27:35 -07:00
Chris LuandGitHub da0d090af9 Update README.md 2018-03-12 01:28:29 -07:00
Chris LuandGitHub b3d1db249d Update README.md 2018-03-12 01:28:00 -07:00
Chris Lu 9cd6b33e97 fix https://github.com/chrislusf/seaweedfs/issues/603 2018-02-27 14:18:00 -08:00
Chris Lu 4b9b9547d4 unused option 2018-02-27 13:57:15 -08:00
Chris Lu fc3924ac70 set file name to base file name
fixing https://github.com/chrislusf/seaweedfs/issues/632
2018-02-26 08:01:23 -08:00
Chris LuandGitHub b2b8a003c1 Merge pull request #615 from tengattack/master
fix reserve volume on node
2018-01-18 22:32:17 -08:00
tengattack 371ad2e397 fix reserve volume on node 2018-01-17 19:53:41 +08:00
Chris Lu 61ff330e99 adjust instructions 2018-01-15 20:33:12 -08:00
Chris Lu 1d09291f95 update upstream 2018-01-15 20:32:57 -08:00
Chris Lu 70f6740309 better fix for single master restart without peers changing 2017-12-06 00:14:14 -08:00
Chris Lu 9026b3e86e always remember the max volume id 2017-11-28 17:08:59 -08:00
Chris Lu 97f757c669 add debug message
display the error message for debugging
https://github.com/chrislusf/seaweedfs/issues/603
2017-11-25 01:31:13 -08:00
Chris LuandGitHub d8c275306e Merge pull request #581 from vinhhrv/patch-1
Add Haravan to backers.md
2017-11-03 15:45:35 -07:00
Vinh VoandGitHub 70c5cbd436 Add Haravan to backers.md
from vinh.vodinh@haravan.com
2017-11-03 10:39:29 +07:00
Chris LuandGitHub a44700ed1f Merge pull request #579 from marcoherbst/patch-1
Add Evercam to Backers.md
2017-11-02 10:59:07 -07:00
Marco HerbstandGitHub 8b4597aefa Add Evercam to Backers.md 2017-11-02 17:34:46 +00:00
Chris LuandGitHub 6d4925af17 Merge pull request #565 from mountain/master
update backer.md by adding colorfulclouds
2017-09-21 01:24:12 -07:00
Mingli YuanandGitHub 0e67fa9688 update backer.md by adding colorfulclouds 2017-09-21 15:47:48 +08:00
Chris Lu ef6224e45d filer default to auto chunking file size to 32MB 2017-09-15 08:24:30 -07:00
Chris Lu e7e378c211 update about sponsoring 2017-09-14 23:49:09 -07:00
Chris Lu 5a1fc03545 adding Patreon 2017-09-14 23:25:44 -07:00
Chris LuandGitHub e1530dc190 Merge pull request #560 from BruceWangNo1/master
some minor fixes to docker/Dockerfile
2017-09-14 17:55:27 -07:00
BruceWangNo1 7c1008e5e5 some minor fixes to docker/Dockerfile 2017-09-14 22:42:28 +08:00
Chris LuandGitHub 2815e3bae9 Merge pull request #558 from BruceWangNo1/patch-1
Fixed several typos in README.md
2017-09-13 22:29:08 -07:00
Bruce WangandGitHub f080ca43df Fixed several typos in README.md 2017-09-13 21:48:41 -05:00
Chris LuandGitHub 5409dfbb0f Merge pull request #555 from nail1208/patch-1
fix ldb dir deleting
2017-08-30 10:45:37 -07:00
Yang PengchengandGitHub a2112e1be0 fix ldb dir deleting 2017-08-30 22:55:03 +08:00
Chris Lu 3288d729ae fix compilation error 2017-08-30 02:24:19 -07:00
Chris Lu 58344980e4 preallocate disk space during compaction also, add cleanup for failed compaction 2017-08-29 23:59:53 -07:00
Chris Lu f7c22f0159 fix bug when collecting vacuum errors 2017-08-29 23:11:20 -07:00
Chris LuandGitHub b3f714612d Merge pull request #552 from chanyoung/filer-fixWrongErrorLog
filer: fixup print error message when make new directory
2017-08-15 00:19:19 -07:00
chanyoung 52fc476a6f filer: fixup print error message when make new directory 2017-08-15 15:54:12 +09:00
Chris Lu dbee816205 fix typo
fix https://github.com/chrislusf/seaweedfs/issues/549
2017-08-10 10:26:39 -07:00
Chris Lu 3a8746ca54 better error message 2017-08-10 10:26:39 -07:00
Chris LuandGitHub 1129956f7f Merge pull request #544 from kelgon/master
(fix #543)added response body to error when util.Post encounter 4xx response
2017-07-28 23:33:22 -07:00
kelgonandGitHub 3bf883327e (fix #543)added body to error when Post encounter 4xx response 2017-07-28 16:11:23 +08:00
Chris Lu e0dfa7a069 ensure 30GB or less
avoid https://github.com/chrislusf/seaweedfs/issues/535
2017-07-16 21:40:55 -07:00
Chris LuandGitHub 0909f669a0 Merge pull request #533 from xxr3376/patch-1
Fix panic error when target collection is not exist.
2017-07-14 11:42:51 -07:00
Xinran XuandGitHub 4a0939a4e4 Fix panic error when target collection is not exist.
Fix: interface conversion: interface {} is nil, not *topology.Collection.
2017-07-14 20:04:33 +08:00
Chris Lu f9a1769df1 fix nil raft server caused crash
fix https://github.com/chrislusf/seaweedfs/issues/524
2017-07-03 22:35:28 -07:00
Chris Lu 1fbb8723dc add cpu and mem pprof 2017-06-22 01:33:58 -07:00
Chris Lu 2c7dad589d stop when dn.parent is nil
fix https://github.com/chrislusf/seaweedfs/issues/473
2017-06-16 08:27:50 -07:00
Chris Lu 72e89b615b 301 is reported as 404 for http post
fix https://github.com/chrislusf/seaweedfs/issues/512
2017-06-15 21:21:32 -07:00
Chris LuandGitHub 761c0eb1ed Merge pull request #508 from chrislusf/skip_memory_pool
skip bytes cache
2017-06-09 22:52:24 -07:00
Chris Lu 5047bdb4a2 skip bytes cache 2017-06-03 11:44:24 -07:00
Chris Lu f5bed84340 add volume id to error message 2017-06-03 01:58:28 -07:00
Chris Lu 83efe1547d revert webp support due to "go test"
related to https://github.com/chai2010/webp/issues/9
2017-05-28 10:25:40 -07:00
Chris Lu 48919009fc build with tip 2017-05-28 10:23:10 -07:00
Chris Lu f66fef0ec5 adjust build 2017-05-28 10:10:14 -07:00
Chris Lu 478fe0ecf2 filer add readonly public port 2017-05-27 20:14:22 -07:00
Chris Lu 7ecc0f4b11 rename variables 2017-05-27 18:11:18 -07:00
Chris Lu 021ef0d14b adjust version to 0.76 2017-05-26 23:04:38 -07:00
Chris Lu e3fc676260 add doc for 0.75 2017-05-26 23:01:40 -07:00
Chris Lu 82c3ccc8dd add btree for volume index 2017-05-26 22:51:25 -07:00
Chris Lu 80cefade65 adjust travis 2017-05-25 20:50:56 -07:00
Chris Lu bd97cbc523 skip busy reporting back to master
possible fix for https://github.com/chrislusf/seaweedfs/issues/499
2017-05-23 21:12:03 -07:00
Chris Lu c5f23b26fc add 1.8.1 2017-05-22 23:09:20 -07:00
Chris Lu a7faefd4ce fix compilation error 2017-05-22 23:04:43 -07:00
Chris Lu b3513d3167 skip only readonly volumes
fix https://github.com/chrislusf/seaweedfs/issues/405
2017-05-22 17:05:27 -07:00
Chris Lu 4ce6586710 use local random generator to avoid global lock 2017-05-19 23:33:36 -07:00
Chris LuandGitHub 68e2dee2cd Merge pull request #487 from r-m-n/fill_images
Scale images to fit or fill
2017-05-05 22:05:31 -07:00
rmn 366fe0d394 Scale images to fit or fill 2017-05-05 12:17:30 +03:00
Chris LuandGitHub 9fcff1bd9a Merge pull request #480 from vitalybaev/webp-resize-support
WebP resize support
2017-04-02 09:02:55 -07:00
Vitaly Baev 7ab9160ee1 WebP resize support 2017-03-31 09:56:00 +03:00
Chris Lu a337b844ec properly send heartbeats
fix https://github.com/chrislusf/seaweedfs/issues/476
2017-03-27 10:11:54 -07:00
Chris LuandGitHub e936fe9001 Merge pull request #472 from sparklxb/master
complement weed mount: add read and delete
2017-03-17 09:27:50 -07:00
sparklxb c46e91d229 complement weed mount: add read and delete 2017-03-17 15:02:27 +08:00
Chris LuandGitHub 37b79174d1 Merge pull request #469 from sparklxb/master
complement weed mount
2017-03-14 08:21:29 -07:00
Chris LuandGitHub b095496d30 Merge pull request #471 from stepanbujnak/master
Show contents of manifest file when cm=false is passed
2017-03-12 22:25:34 -07:00
Stepan Bujnak 14ec3bfc1d Show contents of manifest file when cm=false is passed 2017-03-12 18:40:24 -07:00
Chris LuandGitHub 427c7fbf93 Merge pull request #470 from stepanbujnak/master
Added locking on the filer move operation
2017-03-11 20:20:43 -08:00
Stepan Bujnak a72c92e55f Added locking on the filer move operation 2017-03-11 02:52:04 -08:00
sparklxb 3a67b6ddc1 complement weed mount 2017-03-10 14:46:00 +08:00
sparklxb 7f85e15990 complement weed mount 2017-03-10 10:28:19 +08:00
Chris LuandGitHub b20cdff82d Merge pull request #463 from sparklxb/master
Fixes #266
2017-03-05 22:46:46 -08:00
sparklxb 813a4f7456 Fixes #266 2017-03-06 13:04:52 +08:00
Chris LuandGitHub 470d950918 Merge pull request #461 from sparklxb/master
fix bug: replication, metadata not replicated
2017-03-03 20:02:56 -08:00
sparklxb 4049c72e6c fix bug: replication, metadata not replicated 2017-03-04 11:24:12 +08:00
Chris Lu 30f138a538 fix tests 2017-02-12 22:18:48 -08:00
Chris Lu 41f686ab9d remove weeds.conf 2017-02-12 21:58:44 -08:00
Chris Lu 98d66a569b avoid nil
fix https://github.com/chrislusf/seaweedfs/issues/452
2017-02-11 10:21:28 -08:00
Chris LuandGitHub 654173e7c7 Merge pull request #451 from vancepym/patch-2
Update Dockerfile
2017-02-07 09:47:09 -08:00
vancepymandGitHub 543476cba1 Update Dockerfile
Bump docker base image version to alpine 3.5
2017-02-07 22:50:09 +08:00
Chris Lu 9a8e020559 compile with 1.7 2017-02-06 13:21:28 -08:00
Chris LuandGitHub f0d140f164 Merge pull request #448 from vancepym/patch-1
Fix:  the maximum-length character exceed 255
2017-01-23 12:25:07 -08:00
vancepymandGitHub ee6067e98c Fix: the maximum-length character exceed 255
The length of a CHAR column is fixed to the length that you declare when you create the table. The length can be any value from 0 to 255. When CHAR values are stored, they are right-padded with spaces to the specified length. 

see https://dev.mysql.com/doc/refman/5.7/en/char.html
2017-01-23 20:33:00 +08:00
Chris Lu c2ce97785f set the volume size limit
fixing https://github.com/chrislusf/seaweedfs/issues/447
2017-01-22 21:36:36 -08:00
Chris Lu f302c9fda2 always send the leader info on each heartbeat 2017-01-21 13:58:56 -08:00
Chris Lu 3cb0071025 supporting reading file of size zero 2017-01-21 13:45:16 -08:00
Chris Lu 043b7a7c65 add locking to access l.volumes 2017-01-20 10:18:43 -08:00
Chris LuandGitHub bfd2739fce Merge pull request #443 from Jaumo/jaumo
Online mount, unmount + deletion of single volumes
2017-01-20 10:11:33 -08:00
brstgt 0656838fe5 Don't return actual file size from CheckVolumeDataIntegrity, it will be 0 if last needle is a tombstone, so it's not reliable anyway 2017-01-20 16:37:45 +01:00
brstgt 4fb5bb09b6 Remove obsolete property Volume.dataFileSize 2017-01-20 16:31:11 +01:00
brstgt e074a54a20 Delete volumes online without restarting volume server 2017-01-20 13:02:37 +01:00
brstgt 492f93416d Mount and unmount volumes online without restarting volume server 2017-01-20 12:49:20 +01:00
brstgt 18b3afc97a Log volumeId if dataFileSize != actual data file size - improve log types 2017-01-20 12:22:25 +01:00
brstgt 0c898df430 Log volumeId if dataFileSize != actual data file size 2017-01-20 11:52:23 +01:00
Chris Lu c8f54aad8b adjust timing of leader election 2017-01-18 09:54:43 -08:00
Chris Lu 8de0027df5 Release 0.74 2017-01-18 09:39:47 -08:00
Chris Lu 5b0f5f456e volume server get notified of leader change 2017-01-18 09:34:27 -08:00
Chris Lu 59022b6fe0 fix byte counter on loading index file
fix https://github.com/chrislusf/seaweedfs/issues/441
2017-01-18 08:40:39 -08:00
Chris LuandGitHub 8b15523c9d Update README.md 2017-01-15 12:39:39 -08:00
Chris LuandGitHub e05cc109a6 Update README.md 2017-01-15 12:34:32 -08:00
Chris LuandGitHub a55ae8ad52 Update README.md 2017-01-15 12:33:38 -08:00
Chris Lu 76da296799 upgrade to 0.73 2017-01-15 12:29:53 -08:00
Chris Lu 3065506b38 volume servers always connect to the master leader 2017-01-13 10:20:40 -08:00
Chris Lu d953ed442c auto detect volume server ip address 2017-01-12 13:42:53 -08:00
Chris Lu 1be354bdce default IP to "localhost"
so that the master can know the ip address.
2017-01-10 23:57:59 -08:00
Chris Lu adc8d344a5 update proto buffer 2017-01-10 23:57:16 -08:00
Chris Lu e767c3ea4f disable master connection timeout
temporarily disable master connection timeout due to heartbeat
connection timeout
2017-01-10 01:30:00 -08:00
Chris Lu e46c3415f7 gRpc for master~volume heartbeat 2017-01-10 01:01:12 -08:00
Chris Lu 4beaaa0650 simplify the gzip optimization logic
fix https://github.com/chrislusf/seaweedfs/issues/436
2017-01-09 19:31:58 -08:00
Chris LuandGitHub 9fa648e570 Merge pull request #435 from sparklxb/master
support uploading files to specific dataCenter
2017-01-09 10:07:07 -08:00
sparklxb 66e7013dfe suport uploading files to specific dataCenter 2017-01-09 23:34:47 +08:00
Chris LuandGitHub 07a51815e9 Merge pull request #434 from wangjie/master
fix the bug that we can't get filename when download file.
2017-01-09 00:11:27 -08:00
wangjie 90a6f43c56 fix the bug than we can't get filename when download file. 2017-01-09 14:55:21 +08:00
Chris Lu 53cf1b4900 change the basic unit to MB 2017-01-08 14:57:32 -08:00
Chris Lu ed44f12f6d support Fallocate on linux 2017-01-08 11:01:46 -08:00
Chris Lu 7b6837cbc2 move back the section 2017-01-08 10:35:47 -08:00
Chris LuandGitHub b14332df96 Merge pull request #432 from sparklxb/master
support additional header name-value pairs
2017-01-08 10:30:40 -08:00
sparklxb da9b672d1b support additional header name-value pairs 2017-01-08 22:34:42 +08:00
sparklxb 86a7c56275 support additional header name-value pairs 2017-01-08 09:16:40 +08:00
Chris Lu 13e7069eb9 keep track of total data file size 2017-01-06 10:22:20 -08:00
Chris Lu 54dd925283 remove deprecated instructions 2017-01-05 14:53:07 -08:00
Chris LuandGitHub 80398a2a3a Merge pull request #430 from vancepym/master
Update Dockerfile
2017-01-05 10:15:09 -08:00
vancepymandGitHub 4f28876c8d Update Dockerfile
Replace with Alpine Linux based image
2017-01-05 21:52:55 +08:00
Chris Lu e61c9af523 check Accept instead of content type
fix https://github.com/chrislusf/seaweedfs/issues/429
2017-01-04 19:58:27 -08:00
Chris Lu 4d1ac7332e better organized travis 2017-01-04 01:59:26 -08:00
Chris Lu e2100d0626 move links from bintray to github release page 2017-01-04 00:33:22 -08:00
Chris Lu 47b6ce93f8 download from github release page 2017-01-04 00:29:58 -08:00
Chris Lu 0d8cf87a8c remove tab 2017-01-03 22:40:04 -08:00
Chris Lu f9c20aff9d deploy on tags 2017-01-03 22:38:08 -08:00
Chris Lu 83ce8c30fc skip cleanup on travis deployment 2017-01-03 22:36:39 -08:00
Chris Lu 19159ec479 adjust makefile and travis 2017-01-03 22:20:15 -08:00
Chris Lu 9cb034d49e adjust makefile 2017-01-03 22:11:33 -08:00
Chris Lu d1562e48e1 default to release 2017-01-03 22:08:53 -08:00
Chris Lu dc42d15352 release 0.72 2017-01-03 21:32:59 -08:00
Chris Lu 4d3353750e fix build issue 2017-01-03 21:28:30 -08:00
Chris LuandGitHub 297f64e286 Merge pull request #428 from sparklxb/master
fix bug: upload big .gz file more than maxMB
2017-01-03 21:23:08 -08:00
Chris Lu 8fb29e5230 change to 0.71 version 2017-01-03 21:17:54 -08:00
Chris Lu 2d13382c68 add releasing configs 2017-01-03 21:14:46 -08:00
lixianbin d96d0a87cf fix bug: upload big .gz file more than maxMB 2017-01-04 11:23:40 +08:00
Chris LuandGitHub f7ff98c747 Merge pull request #424 from listeng/master
Return json data when content-type is "application/json"
2016-12-26 02:14:09 -08:00
listengandGitHub 5b18cf09e7 Return json data when content-type is "application/json"
Before #344, the filer will return json data, and cschiano make a template that render to html. But sometimes need json data, so I add some code will return json data when content-type is "application/json".
2016-12-26 16:49:43 +08:00
Chris LuandGitHub 6b5867ce9d Merge pull request #414 from mtolman/master
Updating PostgreSQL file store integration to support directories.
2016-12-12 20:06:26 -08:00
Mike Tolman 43e655afc8 Updating PostgreSQL file store integration to support directories. 2016-12-12 13:03:32 -07:00
Chris LuandGitHub cc34ac0bd5 Merge pull request #411 from mtolman/master
Adding PostgreSQL Filer Support
2016-12-07 17:31:28 -08:00
Mike Tolman e88795be13 Adding PostgreSQL Support 2016-12-07 17:24:40 -07:00
Mike Tolman b18ec59c56 Revert "Adding PostgreSQL Filer Support"
This reverts commit 10853e4d2f.
2016-12-07 17:16:10 -07:00
Mike Tolman 10853e4d2f Adding PostgreSQL Filer Support 2016-12-07 17:13:03 -07:00
Mike Tolman a1783a14cc Revert "Adding PostgreSQL Filer Support"
This reverts commit 855058858a.
2016-12-07 16:54:06 -07:00
Mike Tolman 855058858a Adding PostgreSQL Filer Support 2016-12-07 16:51:36 -07:00
Chris Lu 64509dd6ca fix text. 2016-11-28 20:07:27 -08:00
Chris LuandGitHub 924f797c58 Merge pull request #406 from eshujiushiwo/master
Fix the filer connection of cassandra
2016-11-28 20:05:45 -08:00
eshujiushiwoandGitHub e025fc00a4 let filer use all cassandra server
let filer use all cassandra server
2016-11-28 18:23:39 +08:00
eshujiushiwoandGitHub 5ee7fdc0cc fix hosts strings
fix hosts strings
2016-11-28 16:03:39 +08:00
eshujiushiwoandGitHub 3450eff288 fix info log
fix info log
2016-11-28 15:32:16 +08:00
eshujiushiwoandGitHub 3e29d0f75b Update cassandra_store.go 2016-11-28 15:14:19 +08:00
eshujiushiwoandGitHub 809aa028ec fix the connection of cassandra
fix the connection of cassandra ,let filer can connect to multi cassandra nodes which are in the one cluster
2016-11-28 15:01:04 +08:00
eshujiushiwoandGitHub 89ccb6be05 Update cassandra_store.go 2016-11-28 12:16:51 +08:00
eshujiushiwoandGitHub 5385bf9555 update cassandra connections
update cassandra connections
2016-11-28 11:27:40 +08:00
Chris Lu 9ba52db585 add writable checking
fix https://github.com/chrislusf/seaweedfs/issues/405
2016-11-26 12:14:06 -08:00
Chris Lu dcaf1796fe add option to enable caching 2016-11-16 07:09:57 -08:00
Chris Lu f54f530ada adjust verbose logging 2016-11-13 14:07:51 -08:00
Chris LuandGitHub d0cc9a5634 Merge pull request #395 from hxiaodon/master
concurrent loading volume when starting volume server
2016-11-13 00:00:11 -08:00
霍晓栋 b9f385bd66 refactor concurrent loading code logic 2016-11-13 13:24:52 +08:00
霍晓栋 096ffa9744 concurrent loading volume 2016-11-11 11:53:22 +08:00
Chris LuandGitHub 3320e495f4 Merge pull request #394 from bedragon/master
fix bug : join post is empty
2016-11-07 19:09:19 -08:00
Yang zhixiang 57005ed7f8 fix bug : join post is empty 2016-11-08 11:00:46 +08:00
Chris Lu 36f9633223 add locks for location.volumes
fix https://github.com/chrislusf/seaweedfs/issues/392
2016-11-06 20:55:22 -08:00
Chris Lu df49692dff add tool to change replication or ttl
fix https://github.com/chrislusf/seaweedfs/issues/386
2016-11-04 20:42:31 -07:00
Chris LuandGitHub 05eda424fc Merge pull request #385 from bedragon/master
fix raft_server.go isPeersChanged bug
2016-11-03 10:08:13 -07:00
Yang zhixiang 4194ae33fd fix raft_server.go isPeersChanged bug 2016-11-03 15:46:59 +08:00
Chris LuandGitHub 88d0df523e Merge pull request #378 from amincheloh/patch-1
Update Dockerfile and entrypoint.sh
2016-10-12 21:18:51 -07:00
Amin ChelohandGitHub 54bd1c406a Update Dockerfile
Move COPY /entrypoint.sh to bottom and make sure entrypoint.sh have execute permission
2016-10-12 17:10:38 +07:00
Amin ChelohandGitHub 48a24559a3 Update entrypoint.sh
Running application becomes container's PID 1 allow to receives Unix signals
2016-10-12 16:47:56 +07:00
Chris LuandGitHub 79fdea0de4 Merge pull request #375 from hxiaodon/master
supplemental data between compacting and commit compacting
2016-10-08 10:12:50 -07:00
霍晓栋 7d73bbb073 comment UT case 2016-10-07 16:40:51 +08:00
霍晓栋 7d382ba5fe comment UT case 2016-10-07 16:34:22 +08:00
霍晓栋 ce1f7ab662 makediff func with UT case 2016-10-07 16:22:24 +08:00
霍晓栋 ed848425c7 supplemental data between compacting and commit compacting 2016-09-29 13:57:23 +08:00
Chris Lu dffad65f2f fix compilation 2016-09-26 22:30:44 -07:00
Chris Lu 7e29218327 add a template for makeupDiff 2016-09-26 22:26:41 -07:00
Chris LuandGitHub 8fa61c28e4 Update README.md 2016-09-23 10:07:06 -07:00
Chris LuandGitHub 61550f1b0a Merge pull request #373 from feliperoberto/docker
how to use with docker
2016-09-23 10:02:27 -07:00
Felipe Roberto 0fbec28858 how to use with docker 2016-09-23 13:49:38 -03:00
Chris Lu 1bc041b46d add a new way to manually compact corrupted volume
fix https://github.com/chrislusf/seaweedfs/issues/371
2016-09-22 20:31:20 -07:00
Chris LuandGitHub 603f8e4f86 Merge pull request #366 from jze/master
Changed Dockerfile so it uses a special entrypoint script.
2016-09-08 20:50:07 -07:00
Chris Lu 01cbd5cb58 lock
fix https://github.com/chrislusf/seaweedfs/issues/367
2016-09-08 09:50:31 -07:00
Jesper Zedlitz 1c36706306 Changed Dockerfile so it uses a special entrypoint script. All parameters are passed through to weed. Depending on the command the entrypoint.sh script adds parameters to link containers. 2016-09-08 09:19:06 +02:00
Chris LuandGitHub 99e47fc5fc Merge pull request #361 from hxiaodon/master
add mysql filer support
2016-09-07 21:07:11 -07:00
霍晓栋 78474409a5 filer mysqlstore bug fix 2016-09-08 11:35:54 +08:00
霍晓栋 1c2affea8e Merge remote-tracking branch 'upstream/master' 2016-09-08 10:34:24 +08:00
Chris Lu 0559aa9673 use Lock instead of RLock
fix https://github.com/chrislusf/seaweedfs/issues/364
2016-09-07 18:21:05 -07:00
Chris Lu c4b7966dbe minor help message change 2016-09-07 18:21:05 -07:00
霍晓栋 3aa021a812 refactoring mysql store code 2016-09-05 14:10:22 +08:00
霍晓栋 e7b237c8da UT case fix 2016-08-31 11:55:02 +08:00
霍晓栋 b0035747e3 add filer support 2016-08-31 11:32:30 +08:00
Chris LuandGitHub 9992fe648d Merge pull request #356 from chareice/master
Make Docker build works correctly
2016-08-10 11:07:58 -07:00
ChengLei ShaoandGitHub a3c8235f86 Make Docker build works correctly 2016-08-10 16:11:00 +08:00
Chris LuandGitHub fa8198f44b Merge pull request #354 from hxiaodon/master
if replicated volume has one copy in readonly mode at one node,it sho…
2016-08-09 18:03:57 -07:00
霍晓栋 f04d8fcbcc if replicated volume has one copy in readonly mode at one node,it should be removed from writable list 2016-08-09 20:12:39 +08:00
Chris LuandGitHub d5b0538f7b Merge pull request #352 from mtolman/master
Adding AutoChunk/MaxMB Support to Filer API
2016-08-05 20:07:45 -07:00
Mike Tolman 01d3f69c52 Adding AutoChunk/MaxMB Support to Filer API
This is related to the following issue I added to chrislusf/seaweedfs:
https://github.com/chrislusf/seaweedfs/issues/342
2016-08-05 16:01:30 -06:00
Mike Tolman a89a3c86d0 Revert "Add AutoChunking to the Filer API, so that you can upload really large files through the filer API."
This reverts commit 09059bfdcc.
2016-08-05 15:47:46 -06:00
Mike Tolman 0d331c1e3a Revert "Changing needle_byte_cache so that it doesn't grow so big when larger files are added."
This reverts commit 87fee21ef5.
2016-08-05 15:46:45 -06:00
Mike Tolman 0f4c7dd8fd Revert "Ooops. Missed a line."
This reverts commit 14d4252904.
2016-08-05 15:46:08 -06:00
Mike Tolman ce99bb927d Revert "Adding HTTP verb whitelisting options."
This reverts commit 34837afc7a.
2016-08-05 15:45:48 -06:00
Mike Tolman 34837afc7a Adding HTTP verb whitelisting options. 2016-08-05 15:23:43 -06:00
Mike Tolman 14d4252904 Ooops. Missed a line. 2016-08-05 15:22:46 -06:00
Mike Tolman 6d12230021 Merge branch 'master' of https://github.com/mtolman/seaweedfs 2016-08-05 15:19:55 -06:00
Mike Tolman 09059bfdcc Add AutoChunking to the Filer API, so that you can upload really large files through the filer API. 2016-08-05 15:19:10 -06:00
Mike Tolman b6ce40e87f Add AutoChunking to the Filer API, so that you can upload really large files through the filer API. 2016-08-05 15:16:17 -06:00
Mike Tolman 87fee21ef5 Changing needle_byte_cache so that it doesn't grow so big when larger files are added. 2016-08-05 15:14:24 -06:00
Chris LuandGitHub 761ef1c73e Merge pull request #347 from hxiaodon/master
deleted needle does not need checksum verification(patch)
2016-07-25 10:50:23 -07:00
霍晓栋 52e55508da deleted index entry could not point to deleted needle 2016-07-25 15:07:11 +08:00
霍晓栋 b9b3651a98 deleted index entry could not point to deleted needle 2016-07-25 14:56:58 +08:00
霍晓栋 09bd3d015d deleted index entry could not point to deleted needle 2016-07-25 14:54:40 +08:00
Chris LuandGitHub 0ba3f47e31 Merge pull request #346 from hxiaodon/master
deleted needle does not need checksum verification
2016-07-24 22:23:13 -07:00
霍晓栋 78678f4bcb deleted needle does not need checksum verification 2016-07-25 11:40:35 +08:00
Chris Lu 185a916f5e adjusting command options 2016-07-21 15:00:07 -07:00
Chris Lu a5be4a6d40 fix package name 2016-07-21 01:23:56 -07:00
Chris Lu a57162e8bf delete operation does not need this checking 2016-07-21 00:40:13 -07:00
Chris Lu cdae9fc680 add "weed copy" command to copy files to filer 2016-07-20 23:45:55 -07:00
Chris LuandGitHub 40ba6d2a6f Merge pull request #345 from hxiaodon/master
recover filer read handler logic
2016-07-20 21:28:22 -07:00
霍晓栋 574cc41f0b recover filer read handler logic 2016-07-21 12:20:22 +08:00
Chris LuandGitHub dd84637c34 Merge pull request #344 from cschiano/master
Add a visualization for the filer
2016-07-20 07:48:13 -07:00
cschiano af905a3ff7 Add limit parameter and pagination for files 2016-07-20 10:46:28 +02:00
cschiano c63cebaee1 Add a visualization for the filer 2016-07-18 10:28:24 +02:00
Chris Lu d3ae3a8e14 collect proof when stopping 2016-07-05 10:23:14 -07:00
Chris Lu b38ff39e8b add master profiling option 2016-07-03 15:39:57 -07:00
Chris Lu d981eb282f refactor volume_read_write.go out of volume.go 2016-07-03 00:10:27 -07:00
Chris Lu 576573711a refactoring 2016-07-02 23:58:23 -07:00
Chris Lu 3d8df0f709 refactor volume_loading.go out of volume.go 2016-07-02 23:56:49 -07:00
Chris Lu 582d5d526c refactoring 2016-07-02 23:50:58 -07:00
Chris Lu 3713119ab9 adjust verification logic.
1. log file name for easier debugging
2. remove unused version in readIndexEntryAtOffset()
3. if indexSize==0, the dataFile usually still has a super block of
size 8. So skipping this case.
2016-07-02 23:45:37 -07:00
Chris LuandGitHub 2fa2235fe3 Merge pull request #338 from hxiaodon/master
add index,dat file's integrity checking
2016-07-02 23:29:17 -07:00
霍晓栋 c527d85d97 refactor data integrity checking code v3 2016-07-03 14:13:43 +08:00
霍晓栋 16f6984d02 refactor data integrity checking code v2 2016-07-03 14:11:25 +08:00
霍晓栋 dda13def2a refactor data integrity checking code 2016-07-03 12:53:03 +08:00
霍晓栋 3018443cd7 code change for fix_dat.go 2016-07-02 21:37:14 +08:00
霍晓栋 2e74fb60c8 add data integrity checking 2016-07-02 21:34:30 +08:00
Chris LuandGitHub 8dfe22ef1c Merge pull request #335 from hxiaodon/master
fix bug when choosing the healthy master in filer service
2016-06-30 08:03:35 -07:00
霍晓栋 339856eb78 fix bug when choosing the healthy master in filer service 2016-06-30 19:54:05 +08:00
Chris LuandGitHub 5771225f54 Merge pull request #333 from hxiaodon/master
fix volume_layout bug, add new feature for volume etag write,filer pa…
2016-06-29 01:48:30 -07:00
霍晓栋 079fdbad13 fix volume_layout bug, add new feature for volume etag write,filer pass through query params' 2016-06-29 16:05:00 +08:00
Chris Lu b617b13c43 remember oversized volumes
fix https://github.com/chrislusf/seaweedfs/issues/331
2016-06-27 15:28:23 -07:00
Chris Lu d0dbf6d2ea adjust logging 2016-06-26 12:49:10 -07:00
Chris LuandGitHub b6de5f00b3 Merge pull request #329 from hxiaodon/master
refactor Assign func to accept main[dataCenter|rack|dataNode] params if needed
2016-06-25 22:53:38 -07:00
霍晓栋 1f63094542 refactor Volume Assign function 2016-06-26 10:50:18 +08:00
霍晓栋 c601ef03b1 Merge remote-tracking branch 'upstream/master' 2016-06-26 10:27:50 +08:00
Chris Lu 0647c75a06 update dependency versions 2016-06-24 14:20:32 -07:00
Chris Lu 101e784577 add locking on possible concurrent map access
fix https://github.com/chrislusf/seaweedfs/issues/328
2016-06-23 09:10:25 -07:00
霍晓栋 79b0a58132 add options params for the Assign func 2016-06-23 11:19:09 +08:00
Chris Lu 356b8048c5 fix compilation problem due to API changes 2016-06-18 18:57:33 -07:00
Chris LuandGitHub 18cf59c375 Merge pull request #323 from ham1/tidy-up
Added LICENSE file and updated README.md
2016-06-18 18:12:26 -07:00
Graham Russell fe8dc26849 Added LICENSE file and updated README.md 2016-06-18 23:11:09 +01:00
Chris Lu cc01c352c3 refactoring 2016-06-13 15:07:17 -07:00
Chris Lu a61453d3ec fix nil crash
fix https://github.com/chrislusf/seaweedfs/issues/320
2016-06-13 14:38:35 -07:00
Chris LuandGitHub f6d75476b9 Merge pull request #315 from 030io/master
fix bug: filer DELETE
2016-06-10 10:53:55 -07:00
DaLin fa2a7b5f2f fix bug: filer DELETE 2016-06-11 01:16:19 +08:00
Chris Lu e416679cea Merge pull request #314 from hxiaodon/master
made a little change for Amazon S3 API's compatibility(v2)
2016-06-08 11:38:57 -07:00
霍晓栋 3a25af223f refactor work for filer write handler 2016-06-08 15:46:14 +08:00
霍晓栋 26bb9094d4 made a little change for Amazon S3 API's compatibility 2016-06-08 12:30:20 +08:00
Chris Lu 0e3140c54c a little refactoring 2016-06-06 23:50:27 -07:00
Chris Lu 04380d6a36 Merge pull request #312 from hxiaodon/master
filer server concern the lead change
2016-06-06 23:44:02 -07:00
霍晓栋 d514b6f3f9 read write handler update 2016-06-07 11:45:00 +08:00
霍晓栋 1ef81ac518 filer could detect master nodes healthy status and choose a working one when encountering SPOF 2016-06-07 11:38:40 +08:00
Chris Lu e20ef8a077 add tool to change a volume's replication
fix https://github.com/chrislusf/seaweedfs/issues/29
2016-06-06 12:27:28 -07:00
Chris Lu bb839ba268 fix 2016-06-05 10:57:14 -07:00
Chris Lu 96d2f11f81 adjust log 2016-06-05 09:05:03 -07:00
Chris Lu b456173eb8 delete file storage when POST to update
PUT is used to update. But sometimes POST is also used to post the same
file again. In this case, the actual file storage was not marked as
deleted.

This fix will try its best to delete the orphan files.
2016-06-03 00:24:55 -07:00
Chris Lu 535b1610c8 add fid and url to filer post result
fix https://github.com/chrislusf/seaweedfs/issues/274
2016-06-02 20:44:50 -07:00
Chris Lu 4f47b8cff9 refactoring 2016-06-02 20:05:34 -07:00
Chris Lu a6e948840d fix path 2016-06-02 18:15:54 -07:00
Chris Lu 5ce6bbf076 directory structure change to work with glide
glide has its own requirements. My previous workaround caused me some
code checkin errors. Need to fix this.
2016-06-02 18:09:14 -07:00
Chris Lu caeffa3998 add missing unlock()!!!!! 2016-06-02 11:06:05 -07:00
Chris Lu 6df18a9181 rwlock concurrent read map 2016-05-30 12:30:26 -07:00
Chris Lu 46a89a7d61 fix concurrent write map
fix https://github.com/chrislusf/seaweedfs/issues/311
2016-05-27 08:13:43 -07:00
Chris Lu 59017da4f2 add missing parameters.
strange git errors happen twice since adding glide.
2016-05-26 08:37:27 -07:00
Chris Lu 45a12505f2 bind ip address when starting filer
fix https://github.com/chrislusf/seaweedfs/issues/290
2016-05-25 08:22:31 -07:00
Chris Lu 8e67c4f597 clean up old config only when peers are changed.
fix https://github.com/chrislusf/seaweedfs/issues/307
2016-05-24 10:45:28 -07:00
Chris Lu c24c1ffd1a skip vendor folder 2016-05-23 14:28:38 -07:00
Chris Lu 9d7868ee4e add glide to manage dependency
fix https://github.com/chrislusf/seaweedfs/issues/256
2016-05-23 14:27:39 -07:00
Chris Lu dfde029430 Merge pull request #306 from hankya/master
master: redirect handler accept collection parameter
2016-05-23 10:13:01 -07:00
yangrun db1ac7b3eb master: redirect handler accept collection parameter 2016-05-23 18:21:38 +08:00
Chris Lu 4734efa9a1 lock node.children 2016-05-19 23:57:31 -07:00
Chris Lu 57c85adc53 lock dn.volumes 2016-05-19 23:32:56 -07:00
Chris Lu f8b03c45ef properly release memory
fix https://github.com/chrislusf/seaweedfs/issues/301
2016-05-13 00:19:59 -07:00
Chris Lu e639f1f889 Merge pull request #302 from alexanderk23/fix_content_disposition_header
Fix Content-Disposition header
2016-05-12 10:05:50 -07:00
Alexander Kovalenko f101ad6855 Fix Content-Disposition header; add dl=(0|1) URI param to use inline (default) or attachment 2016-05-12 13:11:27 +03:00
Chris Lu 766512c475 close current needle map when switching 2016-05-09 10:04:21 -07:00
Chris Lu 3626ec5cf7 do not forget to vacuum if cluster changes 2016-05-09 09:58:06 -07:00
Chris Lu e8193ec201 return nil if no error
fix https://github.com/chrislusf/seaweedfs/issues/299
2016-05-05 10:37:33 -07:00
Chris Lu 596f58507e fix refactoring error 2016-04-28 09:29:21 -07:00
Chris Lu 442e798985 use detailed map of errors for distributed operations 2016-04-28 09:11:01 -07:00
Chris Lu 1d86ac8761 just refactoring 2016-04-26 20:45:35 -07:00
Chris Lu c9e33d3709 separate into 2 files, no logic change 2016-04-26 20:10:26 -07:00
chrislusf adcfaa5735 correct cache: fix racing condition 2016-04-17 12:03:45 -07:00
chrislusf 6dd257a81f adjust to real case cache size 2016-04-15 12:06:43 -07:00
chrislusf b03e7b26b5 add []byte caching and pooling
fixes https://github.com/chrislusf/seaweedfs/issues/211
2016-04-15 11:56:53 -07:00
chrislusf 3fb98a904b fmt 2016-04-15 11:04:39 -07:00
chrislusf 74b045e9b6 improve performance a little bit when having white list 2016-04-14 14:42:19 -07:00
Chris Lu 6e21aeea59 Merge pull request #291 from vitalybaev/favicon
Added /favicon.ico handler
2016-04-14 09:06:35 -07:00
Vitaly Baev d1fd372eb2 Added /favicon.ico handler 2016-04-14 11:46:16 +03:00
chrislusf f91baa4b4b mark it as 0.71beta 2016-04-14 01:31:03 -07:00
chrislusf 0649d778a7 pooling []byte
reduce the number of requests to make([]byte)
2016-04-14 01:30:26 -07:00
chrislusf 95e0d2f1b2 make VolumeLayout thread safe 2016-04-14 01:28:40 -07:00
Chris Lu 1b41b8682b remove test image 2016-04-11 23:48:21 -07:00
Chris Lu 274230dbcb releasing 0.70 2016-04-11 23:38:38 -07:00
Chris Lu 6c5eddaf1d Merge pull request #288 from bluefoxah/master
[MOD] fix a bug.if node is nil will get an panic.
2016-04-11 07:43:20 -07:00
张远浩 7a7f4cecbb [MOD] fix a bug.if node is nil will get an panic. 2016-04-11 18:17:22 +08:00
Chris Lu b873b856f2 Merge pull request #287 from bluefoxah/master
modify the lock
2016-04-11 01:11:49 -07:00
bluefoxah c67aee7012 modify the lock
It seems that we did not use the feture of rwlock now.
delete the sync.Mutex only use sync.RWMutex.
2016-04-11 15:53:59 +08:00
chrislusf 112e2c6922 clearer error message 2016-04-10 01:50:58 -07:00
Chris Lu 62f9ce180d Merge pull request #286 from thinxer/parseKeyHash
faster ParseKeyHash
2016-04-10 01:42:17 -07:00
Jianfei Wang eec72b5136 storage: faster ParseKeyHash 2016-04-10 15:54:40 +08:00
Jianfei Wang 5364b3d8f5 storage: add test for ParseKeyHash 2016-04-10 15:52:18 +08:00
chrislusf b39c384d6d Revert "Merge pull request #284 from thinxer/binary"
This reverts commit 3523ad5239, reversing
changes made to 5d100994b1.
2016-04-10 00:24:22 -07:00
Chris Lu 113392bce8 Merge pull request #285 from thinxer/needle-mutex
storage: do not copy sync.Mutex by value
2016-04-09 23:57:18 -07:00
Jianfei Wang a192373c25 storage: do not copy sync.Mutex by value 2016-04-10 13:47:03 +08:00
Chris Lu 3523ad5239 Merge pull request #284 from thinxer/binary
replace util/bytes.go with binary.BigEndian (again)
2016-04-09 01:16:13 -07:00
Jianfei WangandJianfei Wang 18254d9aae replace util/bytes.go with binary.BigEndian 2016-04-09 14:38:01 +08:00
Jianfei Wang f0cc264757 storage: add test for ParseKeyHash 2016-04-09 11:33:06 +08:00
chrislusf 5d100994b1 Revert "Merge pull request #281 from thinxer/binary"
This reverts commit a87fe8ffce, reversing
changes made to 6876bfa685.
2016-04-08 15:52:03 -07:00
Chris Lu a87fe8ffce Merge pull request #281 from thinxer/binary
replace util/bytes.go with binary.BigEndian
2016-04-06 07:08:55 -07:00
Jianfei Wang 7bc10d46e3 replace util/bytes.go with binary.BigEndian 2016-04-06 17:08:52 +08:00
chrislusf 6876bfa685 avoid same directory with different dir ids
fix https://github.com/chrislusf/seaweedfs/issues/277
2016-04-04 16:32:18 -07:00
Chris Lu 7dcbbaa653 Merge pull request #276 from ernado/master
use klauspost/crc32 instead of hash/crc32
2016-04-01 12:35:44 -07:00
Alexandr Razumov a2bb5a34fd update .travis.yml 2016-04-01 22:23:37 +03:00
Alexandr Razumov ca2a6cc36b use klauspost/crc32 instead of hash/crc32 2016-04-01 22:18:58 +03:00
chrislusf 67cd8959bc fix tests 2016-03-30 12:53:37 -07:00
chrislusf c359a5117f add lock to dir children map
fix https://github.com/chrislusf/seaweedfs/issues/273
2016-03-30 12:34:14 -07:00
Chris Lu cc4d475519 Update README.md 2016-03-16 23:22:43 -07:00
chrislusf 62074c7653 adjust reading ip address
adjust reading ip address
2016-03-08 08:46:46 -08:00
chrislusf 4c8367a8b3 add redis password
fix https://github.com/chrislusf/seaweedfs/issues/263
2016-03-08 08:46:28 -08:00
Chris Lu f7f9129b05 Merge pull request #259 from tnextday/master
Fix chunk manifest replicate upload error
2016-02-27 09:12:45 -08:00
tnextday a98f359122 Fix chunk manifest replicate upload error
Escape the redirect url in volume read handler
2016-02-27 17:42:41 +08:00
Chris Lu a436b0345a Merge pull request #239 from hxiaodon/master
dereplicate filer post request & support filer get req with raw fileId
2016-01-17 21:29:36 -08:00
Xiaodong Huo 61b1d73f5a Update filer_server_handlers.go
filer service PUT method update file while POST method create file
2016-01-17 12:30:23 +08:00
霍晓栋 7cf64ae07a dereplicate filer post request & support filer get request with raw fileId 2016-01-13 16:58:07 +08:00
Chris Lu 40f7c5d286 Merge pull request #236 from hxiaodon/master
collection name could be specified in url
2016-01-04 15:15:37 -08:00
Xiaodong Huo 166cc0d48a collection name could be specified in url
uploading file with filer service could specify the collection name in get/post field, if no collection specified, the default collection setting against the filer service will be used
2016-01-04 09:52:32 +08:00
chrislusf 6c88abf62f adjust print out when error 2015-12-30 22:48:19 -08:00
Chris Lu 963c36338a Merge pull request #233 from hxiaodon/master
Update store_replicate.go
2015-12-30 22:44:05 -08:00
hxiaodon 4e48f64512 Update store_replicate.go
replication operations should be equal to volume's replication setting
2015-12-28 14:23:26 +08:00
Chris Lu 4d56039ad4 Merge pull request #229 from Mohanson/patch-2
fix bug : master cann't redirect urlquery string
2015-12-25 00:56:05 -08:00
Chris Lu 9210bf4be1 Merge pull request #228 from Mohanson/patch-1
add Introductions of scale of image
2015-12-25 00:54:24 -08:00
Mohanson 3c9ef6245e fix bug : master cann't redirect urlquery string
There is a bug that master can not redirect urlquery to slave volumn.
so i fix it, and it run good now.
2015-12-25 16:32:50 +08:00
Mohanson 688b99eb73 add Introductions of scale of image 2015-12-25 14:47:33 +08:00
chrislusf 7ae7029154 adjust usage text to fit the display 2015-12-14 22:52:52 -08:00
chrislusf e921cb1a9d format changes 2015-12-14 22:38:58 -08:00
Chris Lu df5e54e02a Merge pull request #224 from tnextday/feature/chunked-file-support
Feature/chunked file support
2015-12-14 21:42:38 -08:00
tnextday 031d26527f update 2015-12-15 13:08:09 +08:00
tnextday 521be17a9e verbose log 2015-12-15 13:05:59 +08:00
tnextday f925374db6 Move write response content into a function 2015-12-15 11:26:16 +08:00
tnextday 90d410d627 verbose log 2015-12-15 00:30:18 +08:00
tnextday b177afc326 weed download command use stream download the large file. 2015-12-15 00:14:02 +08:00
tnextday aa44028b46 update 2015-12-14 22:14:57 +08:00
tnextday f240c57f16 Rename ChunkManifest.GetData to ChunkManifest.Marshal 2015-12-14 22:01:30 +08:00
tnextday 82e6d67ccc update 2015-12-03 21:35:33 +08:00
tnextday 4fda5f8bfd update 2015-12-03 18:21:58 +08:00
tnextday daac5de1ba more check in http_util.Delete
add status code in `DeleteResult` struct
operation.DeleteFiles maybe unsafe, so `ChunkManifest.DeleteChunks` manually delete each chunks
2015-12-03 16:27:02 +08:00
tnextday a9a336fdff delete file return needle size 2015-12-02 21:45:26 +08:00
tnextday 34963de458 returen chunks file size when delete a chunkmanifest 2015-12-02 21:35:50 +08:00
tnextday 662915e691 Delete all chunks when delete a ChunkManifest
LoadChunkManifest can uncompress buffer
move compress.go from storage to operation because of import cycle
MakeFile add cross complete command
2015-12-02 21:27:29 +08:00
tnextday 520875d455 update 2015-12-02 18:25:18 +08:00
tnextday d0e2475ece FilePart.Upload use base name instead of full path
fix chunked file download error
2015-12-02 16:35:16 +08:00
tnextday 2c0a7fe75e complete submit chunked file 2015-12-02 15:00:46 +08:00
tnextday de5e07ce3e rename FlagChunkedFile to FlagChunkManifest 2015-12-01 20:53:44 +08:00
tnextday 6b0894d806 update ChunkedFile to seekable reader, so we can use io.* to read data 2015-12-01 20:23:50 +08:00
tnextday f825d23789 fix build error 2015-11-30 00:21:42 +08:00
tnextday 1817864a45 add chunk file helper to support large file 2015-11-29 23:49:41 +08:00
tnextday a4f64c0116 edit git ignore 2015-11-26 23:30:08 +08:00
chrislusf 020dd480ed fix 2015-11-22 10:11:25 -08:00
chrislusf 70d050416b fix
set HTTP_X_FORWARDED_FOR when proxying

https://github.com/chrislusf/seaweedfs/issues/214
2015-11-22 09:31:43 -08:00
Chris Lu dcb8215482 Merge pull request #221 from euforia/master
Added basic makefile
2015-11-17 18:42:06 -08:00
euforia 94757a209b Added basic makefile 2015-11-17 18:03:23 -08:00
chrislusf 805740069a go fmt 2015-10-19 22:03:18 -07:00
chrislusf faaf44f171 following travis instructions to move to new architecture 2015-10-19 01:07:48 -07:00
chrislusf 6983277042 test with go 1.5, skipping 1.3 and 1.4 2015-10-19 01:05:35 -07:00
chrislusf 2395ee9b2e Merge pull request #210 from maggot611/deamon_exit
[bug fix]weed daemon exit due to hangup signal when terminal close
2015-10-19 01:01:24 -07:00
tianshimao 3c48d871d1 [bug fix]weed daemon exit due to hangup signal when terminal close 2015-10-19 14:39:11 +08:00
chrislusf 5feae4f74c enable collection deletion 2015-10-17 09:02:59 -07:00
chrislusf 2dcdd121ec Merge pull request #202 from elcct/server_filer_fix
server_filer_fix
2015-09-27 12:06:48 -07:00
Pawel Szymanski bbcde8a19d server_filer_fix 2015-09-27 16:00:23 +01:00
chrislusf 1e78b8bc28 Merge pull request #187 from skx/master
Allow whitelisting by CIDR range, not just literally.
2015-09-05 00:09:30 -07:00
Steve Kemp 2a777a970c Allow whitelisting by CIDR range, not just literally.
This allows you to write something like this:

         /usr/local/bin/weed master -mdir /srv/weed/master -whiteList=192.168.0.0/24,127.0.0.1

This will whitelist all the 192.168.0.XX hosts, as well as localhost.
2015-09-05 10:00:13 +03:00
chrislusf 91db227b27 avoid using empty fileId to delete
fix https://github.com/chrislusf/seaweedfs/issues/183
2015-08-21 14:09:36 -07:00
chrislusf 1c6729d4c1 Merge pull request #178 from seifer/underlining.in.collection.name
fix extract collection name from .dat file
2015-08-18 10:19:11 -07:00
Timur Nurutdinov 4b0904e9c4 fix extract collection name from .dat file 2015-08-18 19:58:45 +03:00
chrislusf 7d1e9a6b8a add option to redirect moved or non-local volumes
fix https://github.com/chrislusf/seaweedfs/issues/168
2015-08-03 14:43:15 -07:00
chrislusf 320e946d50 fix ttl change detection
https://github.com/chrislusf/seaweedfs/issues/166
2015-07-17 19:30:25 -07:00
chrislusf c4897df09c Revert "adjust symlink"
This reverts commit 8b1f83a0ed.
2015-07-13 11:13:47 -07:00
chrislusf 8b1f83a0ed adjust symlink
as recommended in https://github.com/chrislusf/seaweedfs/issues/160
2015-07-12 21:50:44 -07:00
chrislusf 430f371a97 fix wrong logic 2015-07-11 12:20:39 -07:00
chrislusf 9b8f8f4bb9 add logging 2015-07-11 12:20:09 -07:00
chrislusf d8bb3845ae fix glog format 2015-07-11 09:55:45 -07:00
Chris Lu 9bf10975bd Merge branch 'master' of github.com:chrislusf/weed-fs 2015-07-11 01:07:12 -07:00
Chris Lu a6aa79aab2 merge with latest glog 2015-07-11 01:06:58 -07:00
chrislusf 2c595d2d16 skip isFileUnchanged checking since ttl always change
skip checking since ttl always change. Fixing
https://github.com/chrislusf/seaweedfs/issues/166
2015-07-10 09:43:49 -07:00
chrislusf a4d3f2da3d Avoid nil case when fixing data 2015-07-08 23:21:56 -07:00
chrislusf 3476d31e50 Add Ext format when exporting files. 2015-07-08 23:21:56 -07:00
chrislusf 8d10b91cf3 Update README.md 2015-07-08 02:05:11 -07:00
chrislusf 95855da282 Adjust for window path
fix https://github.com/chrislusf/seaweedfs/issues/161
2015-07-08 01:43:26 -07:00
chrislusf 3b2645979d Use request host info for master url if possible
Fix https://github.com/chrislusf/seaweedfs/issues/148
2015-06-24 09:50:41 -07:00
chrislusf 418878edce In case lastNode==0, need to set it to -1
Fix https://github.com/chrislusf/seaweedfs/issues/156
2015-06-23 22:49:09 -07:00
chrislusf 30242409f4 allowing proxy to leader for volume id lookup then redirect to volume server
Fix https://github.com/chrislusf/seaweedfs/issues/154
2015-06-22 11:41:51 -07:00
chrislusf fd2e6bcf5d Easier variable names 2015-06-02 00:33:13 -07:00
chrislusf 26cdd11d8f Fix compilation error!!! 2015-06-02 00:23:41 -07:00
chrislusf 51aac49e82 each command use its own options to avoid parameter collision
fix https://github.com/chrislusf/seaweedfs/issues/152
2015-06-01 19:25:01 -07:00
chrislusf e09f45f5ed Fix compilation error.
Fix for https://github.com/chrislusf/seaweedfs/issues/150
2015-05-30 13:28:39 -07:00
chrislusf 32ba7fc6c0 Fix a possible index out of range error. Remove unnecessary caching. 2015-05-26 10:29:49 -07:00
chrislusf 86cd40fba8 Add "weed backup" command.
This is a pre-cursor for asynchronous replication.
2015-05-26 00:58:41 -07:00
chrislusf 7272af8ec4 Adjust dir parameter default value. 2015-05-25 23:53:45 -07:00
chrislusf c86ae76e02 Remove old file 2015-05-25 23:51:41 -07:00
chrislusf c89b4fd18b Add ParseUint64 function 2015-05-25 23:51:12 -07:00
chrislusf 36a31771f1 Add Key String() function 2015-05-25 23:50:19 -07:00
chrislusf 8f88d382a5 Rename variables 2015-05-23 10:16:01 -07:00
chrislusf d3d23e779a Adjust log message 2015-05-23 10:15:16 -07:00
chrislusf 3afa3bea73 Adjust log message 2015-05-23 10:14:18 -07:00
chrislusf 85bfab612c Add comments 2015-05-23 10:13:26 -07:00
chrislusf dc2c1aedcc Merge branch 'master' of https://github.com/chrislusf/seaweedfs 2015-05-22 09:13:10 -07:00
chrislusf c7cf4fa46c Fix looking for master nodes. 2015-05-22 08:12:51 -07:00
chrislusf ebf8e9f663 Merge pull request #145 from dyeldandi/incrementalExport
Using LastModified in export to export only new files and fill up ctime and mtime in tar archive
2015-05-20 09:38:52 -07:00
Denis Yeldandi 4e1c30b60a Added -newer argument, which takes a date/time in RFC3339 format and
exports only files newer than this time
2015-05-20 15:11:12 +03:00
chrislusf 99aee22e08 Adjust admin url pattern 2015-05-17 13:19:43 -07:00
chrislusf 5646ca9458 Merge pull request #142 from yanyiwu/master
if mt != "application/octet-stream"
2015-05-16 19:43:14 -07:00
yanyiwu 96b73e3e94 if mt != "application/octet-stream"
became
if !strings.HasPrefix(mt, "application/octet-stream")

In our situation,
    mt can be 'application/octet-stream;charset=ISO-8859-1',
    so I think HasPrefix will be more accurate.
2015-05-16 18:55:58 +08:00
chrislusf 3188382ea7 More debug information for connecting to master 2015-05-14 21:46:59 -07:00
chrislusf b8314fb054 Textual changes. 2015-05-08 23:44:11 -07:00
chrislusf 66317b8438 Increase compact revision during each compaction 2015-05-08 23:43:59 -07:00
chrislusf dac3b592ed Add compact revision in volume super block 2015-05-08 23:34:14 -07:00
chrislusf 98d4adbb8a Merge pull request #138 from carmark/master
modify the dockerfile to make sure it was built successfully
2015-05-07 05:11:32 -07:00
Lei Xue e9790c030e modify the dockerfile to make sure it was built successfully 2015-05-07 15:33:33 +08:00
chrislusf 49b1ba5bbf break away from code.google.com 2015-05-03 12:37:49 -07:00
chrislusf 8660ecc7ec Merge pull request #135 from blalab/master
Some orthographic suggestions
2015-05-03 09:30:14 -07:00
Ricardo Cid 50b909e537 Some orthographic suggestions 2015-05-02 06:15:25 -04:00
chrislusf 5c760523be Merge pull request #128 from yanyiwu/master
[ui] Ordered VolumeInfos is more Human-readable
2015-04-20 10:56:24 -07:00
yanyiwu dc47db5512 unittest: TestSortVolumeInfos 2015-04-21 01:22:13 +08:00
yanyiwu b7a18580b8 Sort VolumeInfos by VolumeId in Store.Status();
Ordered VolumeInfos is more Human-readable,
especially when there is a lot of volumes.
2015-04-21 01:21:21 +08:00
chrislusf c42d33e800 Merge pull request #127 from yanyiwu/master
BUG FIXED and IMPROVEMENT
2015-04-20 07:42:21 -07:00
yanyiwu eaad4fa3c4 BUG FIXED: RoundRobinCounter.Add will cause a out of range crash
when index >= len(rrc.Values)
2015-04-20 22:05:20 +08:00
yanyiwu c65b9588e2 [ui] BUG FIXED: Concurrent Connections incorrect. 2015-04-20 21:50:07 +08:00
yanyiwu 9cd221cc33 More readable peers usage:
other master nodes in comma separated ip:port list, example:
127.0.0.1:9333,127.0.0.1:9334

Add example to prompt ip:port list is split by ','
2015-04-20 14:47:41 +08:00
chrislusf 5c81fa1075 Prepend prefix "0" for odd number of hex characters. 2015-04-19 18:54:05 -07:00
chrislusf d47a6701c6 Merge pull request #126 from yanyiwu/master
FIXED: When RaftServer cannot find a leader, Return a more readable erro...
2015-04-19 07:05:54 -07:00
yanyiwu 6f96862662 FIXED: When RaftServer cannot find a leader, Return a more readable error.
Before:
curl -F "file=1234" "http://127.0.0.1:9333/submit"
{"error":"Post http:///dir/assign: http: no Host in request URL"}
After:
curl -F "file=1234" "http://127.0.0.1:9333/submit"
{"error":"Raft Server not initialized!"}
2015-04-19 19:07:06 +08:00
chrislusf 6e876e0582 Merge pull request #125 from yanyiwu/master
[ui] seaweedfs logo and icon
2015-04-17 12:29:59 -07:00
yanyiwu 3f253a587c [ui] seaweedfs logo and icon 2015-04-18 02:26:27 +08:00
chrislusf 382441f9ff Update README.md 2015-04-16 17:19:51 -07:00
chrislusf 71dfecd96d Update README.md 2015-04-16 17:18:14 -07:00
chrislusf 283138eb86 Update README.md 2015-04-16 17:15:53 -07:00
chrislusf ea37a8ca64 Merge pull request #122 from stuartpb/seaweedfs-unify
Fix a couple more weedfs -> SeaweedFS instances
2015-04-16 15:44:18 -07:00
Stuart P. Bentley 86e80aa9a0 Fix a couple more weedfs -> SeaweedFS instances
Apparently I forgot to save these files before submitting #121
2015-04-16 21:46:37 +00:00
chrislusf d09475d23b Update Dockerfile 2015-04-16 14:29:45 -07:00
chrislusf df17271f05 Update README.md 2015-04-16 14:28:08 -07:00
chrislusf b9e8b5a816 Merge pull request #121 from stuartpb/seaweedfs-unify
More consistency with SeaweedFS name
2015-04-16 14:18:17 -07:00
Stuart P. Bentley 1a194a578c More consistency with SeaweedFS name
Among the changes, this replaces a couple instances of "Seaweed File System"
with "SeaweedFS", for the same reason that nobody says "Mongo Data Base".
2015-04-16 21:11:25 +00:00
chrislusf d960e94290 Update README.md 2015-04-16 14:02:38 -07:00
chrislusf 08c41fc7e7 Update README.md 2015-04-16 14:01:47 -07:00
chrislusf 51432ac724 Update .project 2015-04-16 13:52:13 -07:00
chrislusf ba84350e58 Update README.md 2015-04-16 13:47:54 -07:00
chrislusf bf32977dcb Update README.md 2015-04-16 13:44:08 -07:00
chrislusf e736963f3c Merge pull request #119 from stuartpb/fix-namesapce
Fix flat_namespace_filer.go implementing "FlatNamesapceFiler"
2015-04-16 13:27:13 -07:00
chrislusf 4d1ccf3e10 Merge pull request #118 from stuartpb/seaweedfs-rename
Change all chrislusf/weed-fs links to point to chrislu/seaweedfs
2015-04-16 13:21:55 -07:00
Stuart P. Bentley 03d99503bb Fix flat_namespace_filer.go implementing "FlatNamesapceFiler" 2015-04-16 20:18:34 +00:00
Stuart P. Bentley f0c2a2dcb3 Change all chrislusf/weed-fs links to point to chrislu/seaweedfs 2015-04-16 19:18:06 +00:00
chrislusf 41f29e9d8c Adjust versioning. Beta version should not have a date. 2015-04-16 11:51:38 -07:00
chrislusf c1bc529654 Use public url for returned file url result 2015-04-16 11:37:05 -07:00
chrislusf 087b839354 Nomalize url before redirecting. 2015-04-16 10:02:53 -07:00
chrislusf 67146ffa78 Adjust publicUrl to volume server only.
Remove publicUrl from master server.
2015-04-16 09:29:20 -07:00
chrislusf 53b663af35 Adjust publicUrl to volume server only. 2015-04-16 09:16:43 -07:00
chrislusf 2a7972e8ea Avoid system exit if only logging failed. 2015-04-15 18:02:02 -07:00
chrislusf 7cc1f473b6 Adjust log message for glog. 2015-04-15 00:01:32 -07:00
chrislusf 49d639ecab Add error checking for file reads. 2015-04-14 23:05:33 -07:00
chrislusf c28d55d343 0.70 beta start 2015-04-14 10:10:50 -07:00
chrislusf 3a024d62df skip directory listing when paginating files. 2015-04-14 10:09:46 -07:00
366 changed files with 30101 additions and 8051 deletions
+22
View File
@@ -0,0 +1,22 @@
---
name: Bug report
about: Create a report to help us improve
---
Sponsors SeaweedFS via Patreon https://www.patreon.com/seaweedfs
**Describe the bug**
A clear and concise description of what the bug is.
**System Setup**
List the command line to start "weed master", "weed volume", "weed filer", "weed s3", "weed mount".
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+80 -1
View File
@@ -1,3 +1,82 @@
go/weed/.goxc*
.goxc*
vendor
tags
*.swp
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
test_data
build
target
*.class
other/java/hdfs/dependency-reduced-pom.xml
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>weed-fs</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
<nature>goclipse.goNature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
</projectDescription>
+38 -8
View File
@@ -1,14 +1,44 @@
sudo: false
language: go
go:
- 1.3
- 1.4.1
- tip
go:
- 1.10.x
- 1.11.x
- tip
before_install:
- export PATH=/home/travis/gopath/bin:$PATH
- export PATH=/home/travis/gopath/bin:$PATH
install:
- go get ./go/...
- go get ./weed/...
script:
- go test ./go/...
- go test ./weed/...
before_deploy:
- make release
deploy:
provider: releases
skip_cleanup: true
api_key:
secure: ERL986+ncQ8lwAJUYDrQ8s2/FxF/cyNIwJIFCqspnWxQgGNNyokET9HapmlPSxjpFRF0q6L2WCg9OY3mSVRq4oI6hg1igOQ12KlLyN71XSJ3c8w0Ay5ho48TQ9l3f3Iu97mntBCe9l0R9pnT8wj1VI8YJxloXwUMG2yeTjA9aBI=
file:
- build/linux_arm.tar.gz
- build/linux_arm64.tar.gz
- build/linux_386.tar.gz
- build/linux_amd64.tar.gz
- build/darwin_amd64.tar.gz
- build/windows_386.zip
- build/windows_amd64.zip
- build/freebsd_arm.tar.gz
- build/freebsd_amd64.tar.gz
- build/freebsd_386.tar.gz
- build/netbsd_arm.tar.gz
- build/netbsd_amd64.tar.gz
- build/netbsd_386.tar.gz
- build/openbsd_arm.tar.gz
- build/openbsd_amd64.tar.gz
- build/openbsd_386.tar.gz
on:
tags: true
repo: chrislusf/seaweedfs
go: tip
-21
View File
@@ -1,21 +0,0 @@
FROM progrium/busybox
WORKDIR /opt/weed
RUN opkg-install curl
RUN echo insecure >> ~/.curlrc
RUN \
curl -Lks https://bintray.com$(curl -Lk http://bintray.com/chrislusf/Weed-FS/seaweed/_latestVersion | grep linux_amd64.tar.gz | sed -n "/href/ s/.*href=['\"]\([^'\"]*\)['\"].*/\1/gp") | gunzip | tar -xf - -C /opt/weed/ && \
mv weed_*/* /bin && \
chmod +x ./bin/weed
EXPOSE 8080
EXPOSE 9333
VOLUME /data
ENV WEED_HOME /opt/weed
ENV PATH ${PATH}:${WEED_HOME}/bin
ENTRYPOINT ["weed"]
+1 -1
View File
@@ -1,5 +1,5 @@
FROM cydev/go
RUN go get github.com/chrislusf/weed-fs/go/weed
RUN go get github.com/chrislusf/seaweedfs/weed
EXPOSE 8080
EXPOSE 9333
VOLUME /data
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Chris Lu
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+113
View File
@@ -0,0 +1,113 @@
BINARY = weed/weed
package = github.com/chrislusf/seaweedfs/weed
GO_FLAGS = #-v
SOURCE_DIR = ./weed/
appname := weed
sources := $(wildcard *.go)
build = CGO_ENABLED=0 GOOS=$(1) GOARCH=$(2) go build -ldflags "-extldflags -static" -o build/$(appname)$(3) $(SOURCE_DIR)
tar = cd build && tar -cvzf $(1)_$(2).tar.gz $(appname)$(3) && rm $(appname)$(3)
zip = cd build && zip $(1)_$(2).zip $(appname)$(3) && rm $(appname)$(3)
all: build
.PHONY : clean deps build linux release windows_build darwin_build linux_build bsd_build clean
clean:
go clean -i $(GO_FLAGS) $(SOURCE_DIR)
rm -f $(BINARY)
rm -rf build/
deps:
go get $(GO_FLAGS) -d $(SOURCE_DIR)
build: deps
go build $(GO_FLAGS) -o $(BINARY) $(SOURCE_DIR)
linux: deps
mkdir -p linux
GOOS=linux GOARCH=amd64 go build $(GO_FLAGS) -o linux/$(BINARY) $(SOURCE_DIR)
release: deps windows_build darwin_build linux_build bsd_build
##### LINUX BUILDS #####
linux_build: build/linux_arm.tar.gz build/linux_arm64.tar.gz build/linux_386.tar.gz build/linux_amd64.tar.gz
build/linux_386.tar.gz: $(sources)
$(call build,linux,386,)
$(call tar,linux,386)
build/linux_amd64.tar.gz: $(sources)
$(call build,linux,amd64,)
$(call tar,linux,amd64)
build/linux_arm.tar.gz: $(sources)
$(call build,linux,arm,)
$(call tar,linux,arm)
build/linux_arm64.tar.gz: $(sources)
$(call build,linux,arm64,)
$(call tar,linux,arm64)
##### DARWIN (MAC) BUILDS #####
darwin_build: build/darwin_amd64.tar.gz
build/darwin_amd64.tar.gz: $(sources)
$(call build,darwin,amd64,)
$(call tar,darwin,amd64)
##### WINDOWS BUILDS #####
windows_build: build/windows_386.zip build/windows_amd64.zip
build/windows_386.zip: $(sources)
$(call build,windows,386,.exe)
$(call zip,windows,386,.exe)
build/windows_amd64.zip: $(sources)
$(call build,windows,amd64,.exe)
$(call zip,windows,amd64,.exe)
##### BSD BUILDS #####
bsd_build: build/freebsd_arm.tar.gz build/freebsd_386.tar.gz build/freebsd_amd64.tar.gz \
build/netbsd_arm.tar.gz build/netbsd_386.tar.gz build/netbsd_amd64.tar.gz \
build/openbsd_arm.tar.gz build/openbsd_386.tar.gz build/openbsd_amd64.tar.gz
build/freebsd_386.tar.gz: $(sources)
$(call build,freebsd,386,)
$(call tar,freebsd,386)
build/freebsd_amd64.tar.gz: $(sources)
$(call build,freebsd,amd64,)
$(call tar,freebsd,amd64)
build/freebsd_arm.tar.gz: $(sources)
$(call build,freebsd,arm,)
$(call tar,freebsd,arm)
build/netbsd_386.tar.gz: $(sources)
$(call build,netbsd,386,)
$(call tar,netbsd,386)
build/netbsd_amd64.tar.gz: $(sources)
$(call build,netbsd,amd64,)
$(call tar,netbsd,amd64)
build/netbsd_arm.tar.gz: $(sources)
$(call build,netbsd,arm,)
$(call tar,netbsd,arm)
build/openbsd_386.tar.gz: $(sources)
$(call build,openbsd,386,)
$(call tar,openbsd,386)
build/openbsd_amd64.tar.gz: $(sources)
$(call build,openbsd,amd64,)
$(call tar,openbsd,amd64)
build/openbsd_arm.tar.gz: $(sources)
$(call build,openbsd,arm,)
$(call tar,openbsd,arm)
+206 -129
View File
@@ -1,11 +1,56 @@
Seaweed File System
=======
# SeaweedFS
[![Build Status](https://travis-ci.org/chrislusf/weed-fs.svg?branch=master)](https://travis-ci.org/chrislusf/weed-fs)
[![GoDoc](https://godoc.org/github.com/chrislusf/weed-fs/go?status.svg)](https://godoc.org/github.com/chrislusf/weed-fs/go)
[![Wiki](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/chrislusf/weed-fs/wiki)
[![Build Status](https://travis-ci.org/chrislusf/seaweedfs.svg?branch=master)](https://travis-ci.org/chrislusf/seaweedfs)
[![GoDoc](https://godoc.org/github.com/chrislusf/seaweedfs/weed?status.svg)](https://godoc.org/github.com/chrislusf/seaweedfs/weed)
[![Wiki](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/chrislusf/seaweedfs/wiki)
![SeaweedFS Logo](https://raw.githubusercontent.com/chrislusf/seaweedfs/master/note/seaweedfs.png)
<h2 align="center">Supporting SeaweedFS</h2>
SeaweedFS is Apache-licensed open source project, independent project with its ongoing development made
possible entirely thanks to the support by these awesome [backers](https://github.com/chrislusf/seaweedfs/blob/master/backers.md).
If you'd like to grow SeaweedFS even stronger, please consider to
<a href="https://www.patreon.com/seaweedfs">Sponsor SeaweedFS via Patreon</a>.
Platinum ($2500/month), Gold ($500/month): put your company logo on the SeaweedFS github page
Generous Backer($50/month), Backer($10/month): put your name on the SeaweedFS backer page.
Your support will be really appreciated by me and other supporters!
<h3 align="center"><a href="https://www.patreon.com/seaweedfs">Sponsors SeaweedFS via Patreon</a></h3>
<h4 align="center">Platinum</h4>
<p align="center">
<a href="" target="_blank">
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>
---
- [Download Binaries for different platforms](https://github.com/chrislusf/seaweedfs/releases/latest)
- [SeaweedFS on Slack](https://join.slack.com/t/seaweedfs/shared_invite/enQtMzI4MTMwMjU2MzA3LTc4MmVlYmFlNjBmZTgzZmJlYmI1MDE1YzkyNWYyZjkwZDFiM2RlMDdjNjVlNjdjYzc4NGFhZGIyYzEyMzJkYTA)
- [SeaweedFS Mailing List](https://groups.google.com/d/forum/seaweedfs)
- [Wiki Documentation](https://github.com/chrislusf/seaweedfs/wiki)
![SeaweedFS Logo](https://raw.githubusercontent.com/chrislusf/weed-fs/master/note/seaweedfs.png)
## Introduction
@@ -14,45 +59,47 @@ 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!
Instead of supporting full POSIX file system semantics, SeaweedFS choose to implement only a key~file mapping. Similar to the word "NoSQL", you can call it as "NoFS".
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 (just one disk read operation).
Instead of managing all file metadata in a central master, SeaweedFS choose to manages file volumes in the central master, and let volume servers manage files and the metadata. This relieves concurrency pressure from the central master and spreads file metadata into volume servers' memories, allowing faster file access with just one disk read operation!
There is only a 40 bytes disk storage overhead for each file's metadata. It is so simple with O(1) disk read that you are welcome to challenge the performance with your actual use cases.
SeaweedFS models after [Facebook's Haystack design paper](http://www.usenix.org/event/osdi10/tech/full_papers/Beaver.pdf).
SeaweedFS started by implementing [Facebook's Haystack design paper](http://www.usenix.org/event/osdi10/tech/full_papers/Beaver.pdf).
SeaweedFS costs only 40 bytes disk storage for each file's metadata. It is so simple with O(1) disk read that you are welcome to challenge the performance with your actual use cases.
![](https://api.bintray.com/packages/chrislusf/Weed-FS/seaweed/images/download.png)
https://bintray.com/chrislusf/Weed-FS/seaweed Download latest compiled binaries for different platforms here.
http://groups.google.com/group/weed-file-system Seaweed File System Discussion Group
SeaweedFS can work very well with just the object store. [[Filer]] is added later to support directories and POSIX attributes. Filer is a separate linearly-scalable stateless server with customizable metadata stores, e.g., MySql/Postgres/Redis/Cassandra/LevelDB.
## Additional Features
* Can choose no replication or different replication level, rack and data center aware
* Automatic master servers failover. No single point of failure, SPOF.
* Automatic master servers failover - no single point of failure (SPOF)
* Automatic Gzip compression depending on file mime type
* Automatic compaction to reclaimed disk spaces after deletion or update
* Servers in the same cluster can have different disk spaces, different file systems, different OS
* Adding/Removing servers do not cause any data re-balancing
* Optional [filer server][Filer] provides "normal" directories and files via http
* For jpeg pictures, optionally fix the orientation.
* Servers in the same cluster can have different disk spaces, file systems, OS etc.
* Adding/Removing servers does **not** cause any data re-balancing
* Optionally fix the orientation for jpeg pictures
* Support Etag, Accept-Range, Last-Modified, etc.
* Support in-memory/leveldb/boltdb mode tuning for memory/performance balance.
* Support in-memory/leveldb/boltdb/btree mode tuning for memory/performance balance.
## Filer Features
* [filer server][Filer] provide "normal" directories and files via http.
* [mount filer][Mount] to read and write files directly as a local directory via FUSE.
* [Amazon S3 compatible API][AmazonS3API] to access files with S3 tooling.
* [Hadoop Compatible File System][Hadoop] to access files from Hadoop/Spark/Flink/etc jobs.
* [Async Backup To Cloud][BackupToCloud] can enjoy extreme fast local access and backup to Amazon S3, Google Cloud Storage, Azure, BackBlaze.
[Filer]: https://github.com/chrislusf/weed-fs/wiki/Filer
[Filer]: https://github.com/chrislusf/seaweedfs/wiki/Directories-and-Files
[Mount]: https://github.com/chrislusf/seaweedfs/wiki/Mount
[AmazonS3API]: https://github.com/chrislusf/seaweedfs/wiki/Amazon-S3-API
[BackupToCloud]: https://github.com/chrislusf/seaweedfs/wiki/Backup-to-Cloud
[Hadoop]: https://github.com/chrislusf/seaweedfs/wiki/Hadoop-Compatible-File-System
## Example Usage
By default, the master node runs on port 9333, and the volume nodes runs on port 8080.
Here I will start one master node, and two volume nodes on port 8080 and 8081. Ideally, they should be started from different machines. Here I just use localhost as example.
By default, the master node runs on port 9333, and the volume nodes run on port 8080.
Here I will start one master node, and two volume nodes on port 8080 and 8081. Ideally, they should be started from different machines. I just use localhost as an example.
SeaweedFS uses HTTP REST operations to write, read, delete. The return results are JSON or JSONP format.
SeaweedFS uses HTTP REST operations to write, read, delete. The responses are in JSON or JSONP format.
### Start Master Server
```
> ./weed master
```
### Start Volume Servers ###
@@ -60,26 +107,26 @@ SeaweedFS uses HTTP REST operations to write, read, delete. The return results a
```
> weed volume -dir="/tmp/data1" -max=5 -mserver="localhost:9333" -port=8080 &
> weed volume -dir="/tmp/data2" -max=10 -mserver="localhost:9333" -port=8081 &
```
### Write File ###
To upload a file, first, send a HTTP POST, PUT, or GET request to `/dir/assign` to get an fid and a volume server url:
To upload a file: first, send a HTTP POST, PUT, or GET request to `/dir/assign` to get an `fid` and a volume server url:
```
> curl -X POST http://localhost:9333/dir/assign
> curl http://localhost:9333/dir/assign
{"count":1,"fid":"3,01637037d6","url":"127.0.0.1:8080","publicUrl":"localhost:8080"}
```
Second, to store the file content, send a HTTP multipart PUT or POST request to `url + '/' + fid` from the response:
Second, to store the file content, send a HTTP multi-part POST request to `url + '/' + fid` from the response:
```
> curl -X PUT -F file=@/home/chris/myphoto.jpg http://127.0.0.1:8080/3,01637037d6
> curl -F file=@/home/chris/myphoto.jpg http://127.0.0.1:8080/3,01637037d6
{"size": 43234}
```
For update, send another PUT or POST request with updated file content.
To update, send another POST request with updated file content.
For deletion, send an HTTP DELETE request to the same `url + '/' + fid` URL:
@@ -87,30 +134,31 @@ For deletion, send an HTTP DELETE request to the same `url + '/' + fid` URL:
> curl -X DELETE http://127.0.0.1:8080/3,01637037d6
```
### Save File Id ###
Now you can save the fid, 3,01637037d6 in this case, to some database field.
The number 3 here, is a volume id. After the comma, it's one file key, 01, and a file cookie, 637037d6.
Now you can save the `fid`, 3,01637037d6 in this case, to a database field.
The volume id is an unsigned 32 bit integer. The file key is an unsigned 64bit integer. The file cookie is an unsigned 32bit integer, used to prevent URL guessing.
The number 3 at the start represents a volume id. After the comma, it's one file key, 01, and a file cookie, 637037d6.
The file key and file cookie are both coded in hex. You can store the <volume id, file key, file cookie> tuple in your own format, or simply store the fid as string.
The volume id is an unsigned 32-bit integer. The file key is an unsigned 64-bit integer. The file cookie is an unsigned 32-bit integer, used to prevent URL guessing.
The file key and file cookie are both coded in hex. You can store the <volume id, file key, file cookie> tuple in your own format, or simply store the `fid` as string.
If stored as a string, in theory, you would need 8+1+16+8=33 bytes. A char(33) would be enough, if not more than enough, since most usage would not need 2^32 volumes.
If space is really a concern, you can store the file id in your own format. You would need one 4-byte integer for volume id, 8-byte long number for file key, 4-byte integer for file cookie. So 16 bytes are enough (more than enough).
If space is really a concern, you can store the file id in your own format. You would need one 4-byte integer for volume id, 8-byte long number for file key, 4-byte integer for file cookie. So 16 bytes are more than enough.
### Read File ###
Here is the example on how to render the URL.
Here is an example of how to render the URL.
First lookup the volume server's URLs by the file's volumeId:
First look up the volume server's URLs by the file's volumeId:
```
> curl http://localhost:9333/dir/lookup?volumeId=3
{"locations":[{"publicUrl":"localhost:8080","url":"localhost:8080"}]}
```
(However, since usually there are not too many volume servers, and volumes does not move often, you can cache the results most of the time. Depends on the replication type, one volume can have multiple replica locations. Just randomly pick one location to read.)
Since (usually) there are not too many volume servers, and volumes don't move often, you can cache the results most of the time. Depending on the replication type, one volume can have multiple replica locations. Just randomly pick one location to read.
Now you can take the public url, render the url or directly read from the volume server via url:
@@ -118,7 +166,7 @@ Now you can take the public url, render the url or directly read from the volume
http://localhost:8080/3,01637037d6.jpg
```
Notice we add an file extension ".jpg" here. It's optional and just one way for the client to specify the file content type.
Notice we add a file extension ".jpg" here. It's optional and just one way for the client to specify the file content type.
If you want a nicer URL, you can use one of these alternative URL formats:
@@ -130,14 +178,23 @@ If you want a nicer URL, you can use one of these alternative URL formats:
http://localhost:8080/3,01637037d6
```
If you want to get a scaled version of an image, you can add some params:
```
http://localhost:8080/3/01637037d6.jpg?height=200&width=200
http://localhost:8080/3/01637037d6.jpg?height=200&width=200&mode=fit
http://localhost:8080/3/01637037d6.jpg?height=200&width=200&mode=fill
```
### Rack-Aware and Data Center-Aware Replication ###
SeaweedFS apply the replication strategy on a volume level. So when you are getting a file id, you can specify the replication strategy. For example:
SeaweedFS applies the replication strategy at a volume level. So, when you are getting a file id, you can specify the replication strategy. For example:
```
curl -X POST http://localhost:9333/dir/assign?replication=001
curl http://localhost:9333/dir/assign?replication=001
```
Here is the meaning of the replication parameter:
The replication parameter options are:
```
000: no replication
@@ -150,7 +207,7 @@ Here is the meaning of the replication parameter:
More details about replication can be found [on the wiki][Replication].
[Replication]: https://github.com/chrislusf/weed-fs/wiki/Replication
[Replication]: https://github.com/chrislusf/seaweedfs/wiki/Replication
You can also set the default replication strategy when starting the master server.
@@ -163,9 +220,7 @@ Volume servers can start with a specific data center name:
weed volume -dir=/tmp/2 -port=8081 -dataCenter=dc2
```
Or the master server can determine the data center via volume server's IP address and settings in weed.conf file.
Now when requesting a file key, an optional "dataCenter" parameter can limit the assigned volume to the specific data center. For example, this specifies that the assigned volume should be limited to 'dc1':
When requesting a file key, an optional "dataCenter" parameter can limit the assigned volume to the specific data center. For example, this specifies that the assigned volume should be limited to 'dc1':
```
http://localhost:9333/dir/assign?dataCenter=dc1
@@ -177,92 +232,116 @@ Now when requesting a file key, an optional "dataCenter" parameter can limit the
* [Chunking large files][feat-3]
* [Collection as a Simple Name Space][feat-4]
[feat-1]: https://github.com/chrislusf/weed-fs/wiki/Failover-Master-Server
[feat-2]: https://github.com/chrislusf/weed-fs/wiki/Optimization#insert-with-your-own-keys
[feat-3]: https://github.com/chrislusf/weed-fs/wiki/Optimization#upload-large-files
[feat-4]: https://github.com/chrislusf/weed-fs/wiki/Optimization#collection-as-a-simple-name-space
[feat-1]: https://github.com/chrislusf/seaweedfs/wiki/Failover-Master-Server
[feat-2]: https://github.com/chrislusf/seaweedfs/wiki/Optimization#insert-with-your-own-keys
[feat-3]: https://github.com/chrislusf/seaweedfs/wiki/Optimization#upload-large-files
[feat-4]: https://github.com/chrislusf/seaweedfs/wiki/Optimization#collection-as-a-simple-name-space
## Architecture ##
Usually distributed file system split each file into chunks, and a central master keeps a mapping of a filename and a chunk index to chunk handles, and also which chunks each chunk server has.
This has the draw back that the central master can not handle many small files efficiently, and since all read requests need to go through the chunk master, responses would be slow for many concurrent web users.
Usually distributed file systems split each file into chunks, a central master keeps a mapping of filenames, chunk indices to chunk handles, and also which chunks each chunk server has.
Instead of managing chunks, SeaweedFS choose to manage data volumes in the master server. Each data volume is size 32GB, and can hold a lot of files. And each storage node can has many data volumes. So the master node only needs to store the metadata about the volumes, which is fairly small amount of data and pretty static most of the time.
The main drawback is that the central master can't handle many small files efficiently, and since all read requests need to go through the chunk master, might not scale well for many concurrent users.
The actual file metadata is stored in each volume on volume servers. Since each volume server only manage metadata of files on its own disk, and only 16 bytes for each file, all file access can read file metadata just from memory and only needs one disk operation to actually read file data.
Instead of managing chunks, SeaweedFS manages data volumes in the master server. Each data volume is size 32GB, and can hold a lot of files. And each storage node can have many data volumes. So the master node only needs to store the metadata about the volumes, which is fairly small amount of data and is generally stable.
The actual file metadata is stored in each volume on volume servers. Since each volume server only manages metadata of files on its own disk, with only 16 bytes for each file, all file access can read file metadata just from memory and only needs one disk operation to actually read file data.
For comparison, consider that an xfs inode structure in Linux is 536 bytes.
### Master Server and Volume Server ###
The architecture is fairly simple. The actual data is stored in volumes on storage nodes. One volume server can have multiple volumes, and can both support read and write access with basic authentication.
All volumes are managed by a master server. The master server contains volume id to volume server mapping. This is fairly static information, and could be cached easily.
On each write request, the master server also generates a file key, which is a growing 64bit unsigned integer. Since the write requests are not as busy as read requests, one master server should be able to handle the concurrency well.
On each write request, the master server also generates a file key, which is a growing 64-bit unsigned integer. Since write requests are not generally as frequent as read requests, one master server should be able to handle the concurrency well.
### Write and Read files ###
When a client sends a write request, the master server returns <volume id, file key, file cookie, volume node url> for the file. The client then contact the volume node and POST the file content via REST.
When a client sends a write request, the master server returns (volume id, file key, file cookie, volume node url) for the file. The client then contacts the volume node and POSTs the file content.
When a client needs to read a file based on <volume id, file key, file cookie>, it can ask the master server by the <volum id> for the <volume node url, volume node public url>, or from cache. Then the client can HTTP GET the content via REST, or just render the URL on web pages and let browsers to fetch the content.
When a client needs to read a file based on (volume id, file key, file cookie), it can ask the master server by the volume id for the (volume node url, volume node public url), or retrieve this from a cache. Then the client can GET the content, or just render the URL on web pages and let browsers fetch the content.
Please see the example for details on write-read process.
Please see the example for details on the write-read process.
### Storage Size ###
In current implementation, each volume can be size of 8x2^32 bytes (32GiB). This is because of aligning contents to 8 bytes. We can be easily increased to 64G, or 128G, or more, by changing 2 lines of code, at the cost of some wasted padding space due to alignment.
In the current implementation, each volume can be 8x2^32 bytes (32GiB). This is because of we align content to 8 bytes. We can easily increase this to 64G, or 128G, or more, by changing 2 lines of code, at the cost of some wasted padding space due to alignment.
There can be 2^32 volumes. So total system size is 8 x 2^32 bytes x 2^32 = 8 x 4GiB x 4Gi = 128EiB (2^67 bytes, or 128 exbibytes).
Each individual file size is limited to the volume size.
### Saving memory ###
All file meta information on volume server is readable from memory without disk access. Each file just takes an 16-byte map entry of <64bit key, 32bit offset, 32bit size>. Of course, each map entry has its own the space cost for the map. But usually the disk runs out before the memory does.
## Compared to Other File Systems##
Frankly, I don't use other distributed file systems too often. All seems more complicated than necessary. Please correct me if anything here is wrong.
## Compared to Other File Systems ##
### Compared to Ceph ###
Ceph can be setup similar to SeaweedFS as a key~blob store. It is much more complicated, with the need to support layers on top of it. Here is a more detailed comparison. https://code.google.com/p/weed-fs/issues/detail?id=44
Most other distributed file systems seem more complicated than necessary.
SeaweedFS is meant to be fast and simple, both during usage and during setup. If you do not understand how it works when you reach here, we failed! Jokes aside, you should not need any consulting service for it.
SeaweedFS has a centralized master to lookup free volumes, while Ceph uses hashing to locate its objects. Having a centralized master makes it easy to code and manage. HDFS/GFS has the single name node for years. SeaweedFS now support multiple master nodes.
Ceph hashing avoids SPOF, but makes it complicated when moving or adding servers.
SeaweedFS is meant to be fast and simple, both during usage and during setup. If you do not understand how it works when you reach here, we failed! Please raise an issue with any questions or update this file with clarifications.
### Compared to HDFS ###
HDFS uses the chunk approach for each file, and is ideal for streaming large files.
HDFS uses the chunk approach for each file, and is ideal for storing large files.
SeaweedFS is ideal for serving relatively smaller files quickly and concurrently.
SeaweedFS can also store extra large files by splitting them into manageable data chunks, and store the file ids of the data chunks into a meta chunk. This is managed by "weed upload/download" tool, and the weed master or volume servers are agnostic about it.
### Compared to MogileFS###
SeaweedFS has 2 components: directory server, storage nodes.
MogileFS has 3 components: tracers, database, storage nodes.
### Compared to GlusterFS, Ceph ###
One more layer means slower access, more operation complexity, more failure possibility.
The architectures are mostly the same. SeaweedFS aims to store and read files fast, with a simple and flat architecture. The main differences are
* 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, Redis, MySql, Postgres, etc, and is easy to customized.
* SeaweedFS Volume server also communicates directly with clients via HTTP, supporting range queries, direct uploads, etc.
| System | File Meta | File Content Read| POSIX | REST API | Optimized for small files |
| ------------- | ------------------------------- | ---------------- | ------ | -------- | ------------------------- |
| SeaweedFS | lookup volume id, cacheable | O(1) disk seek | | Yes | Yes |
| SeaweedFS Filer| Linearly Scalable, Customizable | O(1) disk seek | FUSE | Yes | Yes |
| GlusterFS | hashing | | FUSE, NFS | | |
| Ceph | hashing + rules | | FUSE | Yes | |
### Compared to GlusterFS ###
SeaweedFS is not POSIX compliant, and has simple implementation.
GlusterFS is POSIX compliant, much more complex.
GlusterFS stores files, both directories and content, in configurable volumes called "bricks".
### Compared to Mongo's GridFS ###
Mongo's GridFS splits files into chunks and manage chunks in the central mongodb. For every read or write request, the database needs to query the metadata. It's OK if this is not a bottleneck yet, but for a lot of concurrent reads this unnecessary query could slow things down.
GlusterFS hashes the path and filename into ids, and assigned to virtual volumes, and then mapped to "bricks".
Since files are chunked(default to 256KB), there will be multiple metadata readings and multiple chunk readings, linear to the file size. One 2.56MB file would require at least 20 disk read requests.
### Compared to Ceph ###
Ceph can be setup similar to SeaweedFS as a key->blob store. It is much more complicated, with the need to support layers on top of it. [Here is a more detailed comparison](https://github.com/chrislusf/seaweedfs/issues/120)
SeaweedFS has a centralized master group to look up free volumes, while Ceph uses hashing and metadata servers to locate its objects. Having a centralized master makes it easy to code and manage.
Same as SeaweedFS, Ceph is also based on a 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.
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, Redis, Cassandra, to manage file directories. There are proven, scalable, and easier to manage.
| SeaweedFS | comparable to Ceph | advantage |
| ------------- | ------------- | ---------------- |
| Master | MDS | simpler |
| Volume | OSD | optimized for small files |
| Filer | Ceph FS | linearly scalable, Customizable, O(1) or O(logN) |
On the contrary, SeaweedFS uses large file volume of 32G size to store lots of files, and only manages file volumes in the master server. Each volume manages file metadata themselves. So all the file metadata is spread onto the volume nodes memories, and just one disk read is needed.
## 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.
This is a super exciting project! And I need helpers!
This is a super exciting project! And I need helpers and [support](https://www.patreon.com/seaweedfs)!
## Installation guide for users who are not familiar with golang
@@ -278,88 +357,81 @@ step 2: also you may need to install Mercurial by following the instructions bel
http://mercurial.selenic.com/downloads
step 3: download, compile, and install the project by executing the following command
go get github.com/chrislusf/weed-fs/go/weed
```bash
go get github.com/chrislusf/seaweedfs/weed
```
once this is done, you should see the executable "weed" under $GOPATH/bin
once this is done, you should see the executable "weed" under `$GOPATH/bin`
step 4: after you modify your code locally, you could start a local build by calling "go install" under $GOPATH/src/github.com/chrislusf/weed-fs/go/weed
step 4: after you modify your code locally, you could start a local build by calling `go install` under
## Reference
For pre-compiled releases,
https://bintray.com/chrislusf/Weed-FS/seaweed
```
$GOPATH/src/github.com/chrislusf/seaweedfs/weed
```
## Disk Related topics ##
### Hard Drive Performance ###
When testing read performance on SeaweedFS, it basically becomes performance test your hard drive's random read speed. Hard Drive usually get 100MB/s~200MB/s.
When testing read performance on SeaweedFS, it basically becomes performance test for your hard drive's random read speed. Hard Drive usually get 100MB/s~200MB/s.
### Solid State Disk
To modify or delete small files, SSD must delete a whole block at a time, and move content in existing blocks to a new block. SSD is fast when brand new, but will get fragmented over time and you have to garbage collect, compacting blocks. SeaweedFS is friendly to SSD since it is append-only. Deletion and compaction are done on volume level in the background, not slowing reading and not causing fragmentation.
## Not Planned
POSIX support
## Benchmark
My Own Unscientific Single Machine Results on Mac Book with Solid State Disk, CPU: 1 Intel Core i7 2.2GHz.
My Own Unscientific Single Machine Results on Mac Book with Solid State Disk, CPU: 1 Intel Core i7 2.6GHz.
Write 1 million 1KB file:
```
Concurrency Level: 64
Time taken for tests: 182.456 seconds
Concurrency Level: 16
Time taken for tests: 88.796 seconds
Complete requests: 1048576
Failed requests: 0
Total transferred: 1073741824 bytes
Requests per second: 5747.01 [#/sec]
Transfer rate: 5747.01 [Kbytes/sec]
Total transferred: 1106764659 bytes
Requests per second: 11808.87 [#/sec]
Transfer rate: 12172.05 [Kbytes/sec]
Connection Times (ms)
min avg max std
Total: 0.3 10.9 430.9 5.7
Total: 0.2 1.3 44.8 0.9
Percentage of the requests served within a certain time (ms)
50% 10.2 ms
66% 12.0 ms
75% 12.6 ms
80% 12.9 ms
90% 14.0 ms
95% 14.9 ms
98% 16.2 ms
99% 17.3 ms
100% 430.9 ms
50% 1.1 ms
66% 1.3 ms
75% 1.5 ms
80% 1.7 ms
90% 2.1 ms
95% 2.6 ms
98% 3.7 ms
99% 4.6 ms
100% 44.8 ms
```
Randomly read 1 million files:
```
Concurrency Level: 64
Time taken for tests: 80.732 seconds
Concurrency Level: 16
Time taken for tests: 34.263 seconds
Complete requests: 1048576
Failed requests: 0
Total transferred: 1073741824 bytes
Requests per second: 12988.37 [#/sec]
Transfer rate: 12988.37 [Kbytes/sec]
Total transferred: 1106762945 bytes
Requests per second: 30603.34 [#/sec]
Transfer rate: 31544.49 [Kbytes/sec]
Connection Times (ms)
min avg max std
Total: 0.0 4.7 254.3 6.3
Total: 0.0 0.5 20.7 0.7
Percentage of the requests served within a certain time (ms)
50% 2.6 ms
66% 2.9 ms
75% 3.7 ms
80% 4.7 ms
90% 10.3 ms
95% 16.6 ms
98% 26.3 ms
99% 34.8 ms
100% 254.3 ms
50% 0.4 ms
75% 0.5 ms
95% 0.6 ms
98% 0.8 ms
99% 1.2 ms
100% 20.7 ms
```
@@ -376,3 +448,8 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Stargazers over time
[![Stargazers over time](https://starcharts.herokuapp.com/chrislusf/seaweedfs.svg)](https://starcharts.herokuapp.com/chrislusf/seaweedfs)
+12
View File
@@ -0,0 +1,12 @@
<h1 align="center">Sponsors &amp; Backers</h1>
- [Become a backer or sponsor on Patreon](https://www.patreon.com/seaweedfs).
<h2 align="center">Generous Backers ($50+)</h2>
- [Evercam Camera Management Software](https://evercam.io/)
<h2 align="center">Backers</h2>
- [ColorfulClouds Tech Co. Ltd.](https://caiyunai.com/)
- [Haravan - Ecommerce Platform](https://www.haravan.com)
+27
View File
@@ -0,0 +1,27 @@
FROM frolvlad/alpine-glibc:alpine-3.5
# Tried to use curl only (curl -o /tmp/linux_amd64.tar.gz ...), however it turned out that the following tar command failed with "gzip: stdin: not in gzip format"
RUN apk add --no-cache --virtual build-dependencies --update wget curl ca-certificates && \
wget -P /tmp https://github.com/$(curl -s -L https://github.com/chrislusf/seaweedfs/releases/latest | egrep -o 'chrislusf/seaweedfs/releases/download/.*/linux_amd64.tar.gz') && \
tar -C /usr/bin/ -xzvf /tmp/linux_amd64.tar.gz && \
apk del build-dependencies && \
rm -rf /tmp/*
# volume server gprc port
EXPOSE 18080
# volume server http port
EXPOSE 8080
# filer server gprc port
EXPOSE 18888
# filer server http port
EXPOSE 8888
# master server shared gprc+http port
EXPOSE 9333
VOLUME /data
COPY filer.toml /etc/seaweedfs/filer.toml
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+40
View File
@@ -0,0 +1,40 @@
version: '2'
services:
master:
#image: chrislusf/seaweedfs # use a remote image
build: . # build our container from the local Dockerfile
ports:
- 9333:9333
command: "master"
networks:
default:
aliases:
- seaweed_master
volume:
#image: chrislusf/seaweedfs # use a remote image
build: . # build our container from the local Dockerfile
ports:
- 8080:8080
- 18080:18080
command: 'volume -max=5 -mserver="master:9333" -port=8080'
depends_on:
- master
networks:
default:
aliases:
- seaweed_volume
filer:
#image: chrislusf/seaweedfs # use a remote image
build: . # build our container from the local Dockerfile
ports:
- 8888:8888
- 18888:18888
command: 'filer -master="master:9333"'
depends_on:
- master
- volume
networks:
default:
aliases:
- seaweed_filer
+42
View File
@@ -0,0 +1,42 @@
#!/bin/sh
case "$1" in
'master')
ARGS="-ip `hostname -i` -mdir /data"
# Is this instance linked with an other master? (Docker commandline "--link master1:master")
if [ -n "$MASTER_PORT_9333_TCP_ADDR" ] ; then
ARGS="$ARGS -peers=$MASTER_PORT_9333_TCP_ADDR:$MASTER_PORT_9333_TCP_PORT"
fi
exec /usr/bin/weed $@ $ARGS
;;
'volume')
ARGS="-ip `hostname -i` -dir /data"
# Is this instance linked with a master? (Docker commandline "--link master1:master")
if [ -n "$MASTER_PORT_9333_TCP_ADDR" ] ; then
ARGS="$ARGS -mserver=$MASTER_PORT_9333_TCP_ADDR:$MASTER_PORT_9333_TCP_PORT"
fi
exec /usr/bin/weed $@ $ARGS
;;
'server')
ARGS="-ip `hostname -i` -dir /data"
if [ -n "$MASTER_PORT_9333_TCP_ADDR" ] ; then
ARGS="$ARGS -master.peers=$MASTER_PORT_9333_TCP_ADDR:$MASTER_PORT_9333_TCP_PORT"
fi
exec /usr/bin/weed $@ $ARGS
;;
'filer')
ARGS="-ip `hostname -i`"
if [ -n "$MASTER_PORT_9333_TCP_ADDR" ] ; then
ARGS="$ARGS -master=$MASTER_PORT_9333_TCP_ADDR:$MASTER_PORT_9333_TCP_PORT"
fi
exec /usr/bin/weed $@ $ARGS
;;
*)
exec /usr/bin/weed $@
;;
esac
+3
View File
@@ -0,0 +1,3 @@
[leveldb]
enabled = true
dir = "."
-45
View File
@@ -1,45 +0,0 @@
package compress
import (
"math/rand"
"testing"
)
func TestSortedData(t *testing.T) {
data := make([]int32, 102400)
for i := 1; i < len(data); i++ {
data[i] = data[i-1] + rand.Int31n(15)
}
testCompressAndUncompress(t, data, "Sorted data")
}
func TestUnsortedData(t *testing.T) {
data := make([]int32, 102400)
for i := 0; i < len(data); i++ {
data[i] = rand.Int31n(255)
}
testCompressAndUncompress(t, data, "Unsorted data")
}
func testCompressAndUncompress(t *testing.T, data []int32, desc string) {
compressed_data, err := Compress32(data)
if err != nil {
t.Fatal("Compress error", err.Error())
}
uncompressed_data, err := Uncompress32(compressed_data, make([]int32, len(data)*2))
if err != nil {
t.Fatal("Compress error", err.Error())
}
if len(uncompressed_data) != len(data) {
t.Fatal("Len differs", len(data), len(uncompressed_data))
}
for i := 0; i < len(data); i++ {
if data[i] != uncompressed_data[i] {
t.Fatal("Data differs:", i, data[i], uncompressed_data[i])
}
}
println(desc, " Data length:", len(data), " => Compressed length:", len(compressed_data))
}
-32
View File
@@ -1,32 +0,0 @@
package compress
import (
"github.com/reducedb/encoding/cursor"
"github.com/reducedb/encoding/delta/bp32"
)
// Compress compresses in[]int32 to out[]int32
func Compress32(in []int32) (out []int32, err error) {
out = make([]int32, len(in)*2)
inpos := cursor.New()
outpos := cursor.New()
if err = bp32.New().Compress(in, inpos, len(in), out, outpos); err != nil {
return nil, err
}
return out[:outpos.Get()], nil
}
// Uncompress uncompresses in[]int32 to out[]int32
func Uncompress32(in []int32, buffer []int32) (out []int32, err error) {
out = buffer
inpos := cursor.New()
outpos := cursor.New()
if err = bp32.New().Uncompress(in, inpos, len(in), out, outpos); err != nil {
return nil, err
}
return out[:outpos.Get()], nil
}
@@ -1,87 +0,0 @@
package cassandra_store
import (
"fmt"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/gocql/gocql"
)
/*
Basically you need a table just like this:
CREATE TABLE seaweed_files (
path varchar,
fids list<varchar>,
PRIMARY KEY (path)
);
Need to match flat_namespace.FlatNamespaceStore interface
Put(fullFileName string, fid string) (err error)
Get(fullFileName string) (fid string, err error)
Delete(fullFileName string) (fid string, err error)
*/
type CassandraStore struct {
cluster *gocql.ClusterConfig
session *gocql.Session
}
func NewCassandraStore(keyspace string, hosts ...string) (c *CassandraStore, err error) {
c = &CassandraStore{}
c.cluster = gocql.NewCluster(hosts...)
c.cluster.Keyspace = keyspace
c.cluster.Consistency = gocql.Quorum
c.session, err = c.cluster.CreateSession()
if err != nil {
glog.V(0).Infof("Failed to open cassandra store, hosts %v, keyspace %s", hosts, keyspace)
}
return
}
func (c *CassandraStore) Put(fullFileName string, fid string) (err error) {
var input []string
input = append(input, fid)
if err := c.session.Query(
`INSERT INTO seaweed_files (path, fids) VALUES (?, ?)`,
fullFileName, input).Exec(); err != nil {
glog.V(0).Infof("Failed to save file %s with id %s: %v", fullFileName, fid, err)
return err
}
return nil
}
func (c *CassandraStore) Get(fullFileName string) (fid string, err error) {
var output []string
if err := c.session.Query(
`select fids FROM seaweed_files WHERE path = ? LIMIT 1`,
fullFileName).Consistency(gocql.One).Scan(&output); err != nil {
if err != gocql.ErrNotFound {
glog.V(0).Infof("Failed to find file %s: %v", fullFileName, fid, err)
}
}
if len(output) == 0 {
return "", fmt.Errorf("No file id found for %s", fullFileName)
}
return output[0], nil
}
// Currently the fid is not returned
func (c *CassandraStore) Delete(fullFileName string) (fid string, err error) {
if err := c.session.Query(
`DELETE FROM seaweed_files WHERE path = ?`,
fullFileName).Exec(); err != nil {
if err != gocql.ErrNotFound {
glog.V(0).Infof("Failed to delete file %s: %v", fullFileName, err)
}
return "", err
}
return "", nil
}
func (c *CassandraStore) Close() {
if c.session != nil {
c.session.Close()
}
}
-22
View File
@@ -1,22 +0,0 @@
/*
Here is the CQL to create the table.CassandraStore
Optionally you can adjust the keyspace name and replication settings.
For production server, very likely you want to set replication_factor to 3
*/
create keyspace seaweed WITH replication = {
'class':'SimpleStrategy',
'replication_factor':1
};
use seaweed;
CREATE TABLE seaweed_files (
path varchar,
fids list<varchar>,
PRIMARY KEY (path)
);
-70
View File
@@ -1,70 +0,0 @@
package filer
import (
"encoding/json"
"errors"
"fmt"
"github.com/chrislusf/weed-fs/go/util"
"net/url"
)
type ApiRequest struct {
Command string //"listFiles", "listDirectories"
Directory string
FileName string
}
type ListFilesResult struct {
Files []FileEntry
Error string `json:"error,omitempty"`
}
func ListFiles(server string, directory string, fileName string) (*ListFilesResult, error) {
var ret ListFilesResult
if err := call(server, ApiRequest{Command: "listFiles", Directory: directory, FileName: fileName}, &ret); err == nil {
if ret.Error != "" {
return nil, errors.New(ret.Error)
}
return &ret, nil
} else {
return nil, err
}
}
type ListDirectoriesResult struct {
Directories []DirectoryEntry
Error string `json:"error,omitempty"`
}
func ListDirectories(server string, directory string) (*ListDirectoriesResult, error) {
var ret ListDirectoriesResult
if err := call(server, ApiRequest{Command: "listDirectories", Directory: directory}, &ret); err == nil {
if ret.Error != "" {
return nil, errors.New(ret.Error)
}
return &ret, nil
} else {
return nil, err
}
}
func call(server string, request ApiRequest, ret interface{}) error {
b, err := json.Marshal(request)
if err != nil {
fmt.Println("error:", err)
return nil
}
values := make(url.Values)
values.Add("request", string(b))
jsonBlob, err := util.Post("http://"+server+"/__api__", values)
if err != nil {
return err
}
err = json.Unmarshal(jsonBlob, ret)
if err != nil {
return err
}
return nil
}
-26
View File
@@ -1,26 +0,0 @@
Design Assumptions:
1. the number of directories are magnitudely smaller than the number of files
2. unlimited number of files under any directories
Phylosophy:
metadata for directories and files should be separated
Design:
Store directories in normal map
all of directories hopefully all be in memory
efficient to move/rename/list_directories
Log directory changes to append only log file
Store files in sorted string table in <dir_id/filename> format
efficient to list_files, just simple iterator
efficient to locate files, binary search
Testing:
1. starting server, "weed server -filer=true"
2. posting files to different folders
curl -F "filename=@design.txt" "http://localhost:8888/sources/"
curl -F "filename=@design.txt" "http://localhost:8888/design/"
curl -F "filename=@directory.go" "http://localhost:8888/sources/weed/go/"
curl -F "filename=@directory.go" "http://localhost:8888/sources/testing/go/"
curl -F "filename=@filer.go" "http://localhost:8888/sources/weed/go/"
curl -F "filename=@filer_in_leveldb.go" "http://localhost:8888/sources/weed/go/"
curl "http://localhost:8888/?pretty=y"
curl "http://localhost:8888/sources/weed/go/?pretty=y"
curl "http://localhost:8888/sources/weed/go/?pretty=y"
-15
View File
@@ -1,15 +0,0 @@
package embedded_filer
import (
"github.com/chrislusf/weed-fs/go/filer"
)
type DirectoryManager interface {
FindDirectory(dirPath string) (filer.DirectoryId, error)
ListDirectories(dirPath string) (dirs []filer.DirectoryEntry, err error)
MakeDirectory(currentDirPath string, dirName string) (filer.DirectoryId, error)
MoveUnderDirectory(oldDirPath string, newParentDirPath string) error
DeleteDirectory(dirPath string) error
//functions used by FUSE
FindDirectoryById(filer.DirectoryId, error)
}
-259
View File
@@ -1,259 +0,0 @@
package embedded_filer
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/chrislusf/weed-fs/go/filer"
"github.com/chrislusf/weed-fs/go/util"
)
var writeLock sync.Mutex //serialize changes to dir.log
type DirectoryEntryInMap struct {
Name string
Parent *DirectoryEntryInMap
SubDirectories map[string]*DirectoryEntryInMap
Id filer.DirectoryId
}
type DirectoryManagerInMap struct {
Root *DirectoryEntryInMap
max filer.DirectoryId
logFile *os.File
isLoading bool
}
func (dm *DirectoryManagerInMap) NewDirectoryEntryInMap(parent *DirectoryEntryInMap, name string) (d *DirectoryEntryInMap, err error) {
writeLock.Lock()
defer writeLock.Unlock()
d = &DirectoryEntryInMap{Name: name, Parent: parent, SubDirectories: make(map[string]*DirectoryEntryInMap)}
var parts []string
for p := d; p != nil && p.Name != ""; p = p.Parent {
parts = append(parts, p.Name)
}
n := len(parts)
if n <= 0 {
return nil, fmt.Errorf("Failed to create folder %s/%s", parent.Name, name)
}
for i := 0; i < n/2; i++ {
parts[i], parts[n-1-i] = parts[n-1-i], parts[i]
}
dm.max++
d.Id = dm.max
dm.log("add", "/"+strings.Join(parts, "/"), strconv.Itoa(int(d.Id)))
return d, nil
}
func (dm *DirectoryManagerInMap) log(words ...string) {
if !dm.isLoading {
dm.logFile.WriteString(strings.Join(words, "\t") + "\n")
}
}
func NewDirectoryManagerInMap(dirLogFile string) (dm *DirectoryManagerInMap, err error) {
dm = &DirectoryManagerInMap{}
//dm.Root do not use NewDirectoryEntryInMap, since dm.max will be changed
dm.Root = &DirectoryEntryInMap{SubDirectories: make(map[string]*DirectoryEntryInMap)}
if dm.logFile, err = os.OpenFile(dirLogFile, os.O_RDWR|os.O_CREATE, 0644); err != nil {
return nil, fmt.Errorf("cannot write directory log file %s.idx: %v", dirLogFile, err)
}
return dm, dm.load()
}
func (dm *DirectoryManagerInMap) processEachLine(line string) error {
if strings.HasPrefix(line, "#") {
return nil
}
if line == "" {
return nil
}
parts := strings.Split(line, "\t")
if len(parts) == 0 {
return nil
}
switch parts[0] {
case "add":
v, pe := strconv.Atoi(parts[2])
if pe != nil {
return pe
}
if e := dm.loadDirectory(parts[1], filer.DirectoryId(v)); e != nil {
return e
}
case "mov":
newName := ""
if len(parts) >= 4 {
newName = parts[3]
}
if e := dm.MoveUnderDirectory(parts[1], parts[2], newName); e != nil {
return e
}
case "del":
if e := dm.DeleteDirectory(parts[1]); e != nil {
return e
}
default:
fmt.Printf("line %s has %s!\n", line, parts[0])
return nil
}
return nil
}
func (dm *DirectoryManagerInMap) load() error {
dm.max = 0
lines := bufio.NewReader(dm.logFile)
dm.isLoading = true
defer func() { dm.isLoading = false }()
for {
line, err := util.Readln(lines)
if err != nil && err != io.EOF {
return err
}
if pe := dm.processEachLine(string(line)); pe != nil {
return pe
}
if err == io.EOF {
return nil
}
}
}
func (dm *DirectoryManagerInMap) findDirectory(dirPath string) (*DirectoryEntryInMap, error) {
if dirPath == "" {
return dm.Root, nil
}
dirPath = filepath.Clean(dirPath)
if dirPath == "/" {
return dm.Root, nil
}
parts := strings.Split(dirPath, "/")
dir := dm.Root
for i := 1; i < len(parts); i++ {
if sub, ok := dir.SubDirectories[parts[i]]; ok {
dir = sub
} else {
return dm.Root, fmt.Errorf("Directory %s Not Found", dirPath)
}
}
return dir, nil
}
func (dm *DirectoryManagerInMap) FindDirectory(dirPath string) (filer.DirectoryId, error) {
d, e := dm.findDirectory(dirPath)
if e == nil {
return d.Id, nil
}
return dm.Root.Id, e
}
func (dm *DirectoryManagerInMap) loadDirectory(dirPath string, dirId filer.DirectoryId) error {
dirPath = filepath.Clean(dirPath)
if dirPath == "/" {
return nil
}
parts := strings.Split(dirPath, "/")
dir := dm.Root
for i := 1; i < len(parts); i++ {
sub, ok := dir.SubDirectories[parts[i]]
if !ok {
if i != len(parts)-1 {
return fmt.Errorf("%s should be created after parent %s", dirPath, parts[i])
}
var err error
sub, err = dm.NewDirectoryEntryInMap(dir, parts[i])
if err != nil {
return err
}
if sub.Id != dirId {
return fmt.Errorf("%s should be have id %v instead of %v", dirPath, sub.Id, dirId)
}
dir.SubDirectories[parts[i]] = sub
}
dir = sub
}
return nil
}
func (dm *DirectoryManagerInMap) makeDirectory(dirPath string) (dir *DirectoryEntryInMap, created bool) {
dirPath = filepath.Clean(dirPath)
if dirPath == "/" {
return dm.Root, false
}
parts := strings.Split(dirPath, "/")
dir = dm.Root
for i := 1; i < len(parts); i++ {
sub, ok := dir.SubDirectories[parts[i]]
if !ok {
var err error
sub, err = dm.NewDirectoryEntryInMap(dir, parts[i])
if err != nil {
return nil, false
}
dir.SubDirectories[parts[i]] = sub
created = true
}
dir = sub
}
return dir, created
}
func (dm *DirectoryManagerInMap) MakeDirectory(dirPath string) (filer.DirectoryId, error) {
dir, _ := dm.makeDirectory(dirPath)
return dir.Id, nil
}
func (dm *DirectoryManagerInMap) MoveUnderDirectory(oldDirPath string, newParentDirPath string, newName string) error {
writeLock.Lock()
defer writeLock.Unlock()
oldDir, oe := dm.findDirectory(oldDirPath)
if oe != nil {
return oe
}
parentDir, pe := dm.findDirectory(newParentDirPath)
if pe != nil {
return pe
}
dm.log("mov", oldDirPath, newParentDirPath, newName)
delete(oldDir.Parent.SubDirectories, oldDir.Name)
if newName == "" {
newName = oldDir.Name
}
parentDir.SubDirectories[newName] = oldDir
oldDir.Name = newName
oldDir.Parent = parentDir
return nil
}
func (dm *DirectoryManagerInMap) ListDirectories(dirPath string) (dirNames []filer.DirectoryEntry, err error) {
d, e := dm.findDirectory(dirPath)
if e != nil {
return dirNames, e
}
for k, v := range d.SubDirectories {
dirNames = append(dirNames, filer.DirectoryEntry{Name: k, Id: v.Id})
}
return dirNames, nil
}
func (dm *DirectoryManagerInMap) DeleteDirectory(dirPath string) error {
writeLock.Lock()
defer writeLock.Unlock()
if dirPath == "/" {
return fmt.Errorf("Can not delete %s", dirPath)
}
d, e := dm.findDirectory(dirPath)
if e != nil {
return e
}
if len(d.SubDirectories) != 0 {
return fmt.Errorf("dir %s still has sub directories", dirPath)
}
delete(d.Parent.SubDirectories, d.Name)
d.Parent = nil
dm.log("del", dirPath)
return nil
}
-86
View File
@@ -1,86 +0,0 @@
package embedded_filer
import (
"os"
"strings"
"testing"
)
func TestDirectory(t *testing.T) {
dm, _ := NewDirectoryManagerInMap("/tmp/dir.log")
defer func() {
if true {
os.Remove("/tmp/dir.log")
}
}()
dm.MakeDirectory("/a/b/c")
dm.MakeDirectory("/a/b/d")
dm.MakeDirectory("/a/b/e")
dm.MakeDirectory("/a/b/e/f")
dm.MakeDirectory("/a/b/e/f/g")
dm.MoveUnderDirectory("/a/b/e/f/g", "/a/b", "t")
if _, err := dm.FindDirectory("/a/b/e/f/g"); err == nil {
t.Fatal("/a/b/e/f/g should not exist any more after moving")
}
if _, err := dm.FindDirectory("/a/b/t"); err != nil {
t.Fatal("/a/b/t should exist after moving")
}
if _, err := dm.FindDirectory("/a/b/g"); err == nil {
t.Fatal("/a/b/g should not exist after moving")
}
dm.MoveUnderDirectory("/a/b/e/f", "/a/b", "")
if _, err := dm.FindDirectory("/a/b/f"); err != nil {
t.Fatal("/a/b/g should not exist after moving")
}
dm.MakeDirectory("/a/b/g/h/i")
dm.DeleteDirectory("/a/b/e/f")
dm.DeleteDirectory("/a/b/e")
dirNames, _ := dm.ListDirectories("/a/b/e")
for _, v := range dirNames {
println("sub1 dir:", v.Name, "id", v.Id)
}
dm.logFile.Close()
var path []string
printTree(dm.Root, path)
dm2, e := NewDirectoryManagerInMap("/tmp/dir.log")
if e != nil {
println("load error", e.Error())
}
if !compare(dm.Root, dm2.Root) {
t.Fatal("restored dir not the same!")
}
printTree(dm2.Root, path)
}
func printTree(node *DirectoryEntryInMap, path []string) {
println(strings.Join(path, "/") + "/" + node.Name)
path = append(path, node.Name)
for _, v := range node.SubDirectories {
printTree(v, path)
}
}
func compare(root1 *DirectoryEntryInMap, root2 *DirectoryEntryInMap) bool {
if len(root1.SubDirectories) != len(root2.SubDirectories) {
return false
}
if root1.Name != root2.Name {
return false
}
if root1.Id != root2.Id {
return false
}
if !(root1.Parent == nil && root2.Parent == nil) {
if root1.Parent.Id != root2.Parent.Id {
return false
}
}
for k, v := range root1.SubDirectories {
if !compare(v, root2.SubDirectories[k]) {
return false
}
}
return true
}
-141
View File
@@ -1,141 +0,0 @@
package embedded_filer
import (
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/chrislusf/weed-fs/go/filer"
"github.com/chrislusf/weed-fs/go/operation"
)
type FilerEmbedded struct {
master string
directories *DirectoryManagerInMap
files *FileListInLevelDb
}
func NewFilerEmbedded(master string, dir string) (filer *FilerEmbedded, err error) {
dm, de := NewDirectoryManagerInMap(filepath.Join(dir, "dir.log"))
if de != nil {
return nil, de
}
fl, fe := NewFileListInLevelDb(dir)
if fe != nil {
return nil, fe
}
filer = &FilerEmbedded{
master: master,
directories: dm,
files: fl,
}
return
}
func (filer *FilerEmbedded) CreateFile(filePath string, fid string) (err error) {
dir, file := filepath.Split(filePath)
dirId, e := filer.directories.MakeDirectory(dir)
if e != nil {
return e
}
return filer.files.CreateFile(dirId, file, fid)
}
func (filer *FilerEmbedded) FindFile(filePath string) (fid string, err error) {
dir, file := filepath.Split(filePath)
dirId, e := filer.directories.FindDirectory(dir)
if e != nil {
return "", e
}
return filer.files.FindFile(dirId, file)
}
func (filer *FilerEmbedded) FindDirectory(dirPath string) (dirId filer.DirectoryId, err error) {
return filer.directories.FindDirectory(dirPath)
}
func (filer *FilerEmbedded) ListDirectories(dirPath string) (dirs []filer.DirectoryEntry, err error) {
return filer.directories.ListDirectories(dirPath)
}
func (filer *FilerEmbedded) ListFiles(dirPath string, lastFileName string, limit int) (files []filer.FileEntry, err error) {
dirId, e := filer.directories.FindDirectory(dirPath)
if e != nil {
return nil, e
}
return filer.files.ListFiles(dirId, lastFileName, limit), nil
}
func (filer *FilerEmbedded) DeleteDirectory(dirPath string, recursive bool) (err error) {
dirId, e := filer.directories.FindDirectory(dirPath)
if e != nil {
return e
}
if sub_dirs, sub_err := filer.directories.ListDirectories(dirPath); sub_err == nil {
if len(sub_dirs) > 0 && !recursive {
return fmt.Errorf("Fail to delete directory %s: %d sub directories found!", dirPath, len(sub_dirs))
}
for _, sub := range sub_dirs {
if delete_sub_err := filer.DeleteDirectory(filepath.Join(dirPath, sub.Name), recursive); delete_sub_err != nil {
return delete_sub_err
}
}
}
list := filer.files.ListFiles(dirId, "", 100)
if len(list) != 0 && !recursive {
if !recursive {
return fmt.Errorf("Fail to delete non-empty directory %s!", dirPath)
}
}
for {
if len(list) == 0 {
return filer.directories.DeleteDirectory(dirPath)
}
var fids []string
for _, fileEntry := range list {
fids = append(fids, string(fileEntry.Id))
}
if result_list, delete_file_err := operation.DeleteFiles(filer.master, fids); delete_file_err != nil {
return delete_file_err
} else {
if len(result_list.Errors) > 0 {
return errors.New(strings.Join(result_list.Errors, "\n"))
}
}
lastFile := list[len(list)-1]
list = filer.files.ListFiles(dirId, lastFile.Name, 100)
}
}
func (filer *FilerEmbedded) DeleteFile(filePath string) (fid string, err error) {
dir, file := filepath.Split(filePath)
dirId, e := filer.directories.FindDirectory(dir)
if e != nil {
return "", e
}
return filer.files.DeleteFile(dirId, file)
}
/*
Move a folder or a file, with 4 Use cases:
mv fromDir toNewDir
mv fromDir toOldDir
mv fromFile toDir
mv fromFile toFile
*/
func (filer *FilerEmbedded) Move(fromPath string, toPath string) error {
if _, dir_err := filer.FindDirectory(fromPath); dir_err == nil {
if _, err := filer.FindDirectory(toPath); err == nil {
// move folder under an existing folder
return filer.directories.MoveUnderDirectory(fromPath, toPath, "")
}
// move folder to a new folder
return filer.directories.MoveUnderDirectory(fromPath, filepath.Dir(toPath), filepath.Base(toPath))
}
if fid, file_err := filer.DeleteFile(fromPath); file_err == nil {
if _, err := filer.FindDirectory(toPath); err == nil {
// move file under an existing folder
return filer.CreateFile(filepath.Join(toPath, filepath.Base(fromPath)), fid)
}
// move to a folder with new name
return filer.CreateFile(toPath, fid)
}
return fmt.Errorf("File %s is not found!", fromPath)
}
@@ -1,82 +0,0 @@
package embedded_filer
import (
"bytes"
"github.com/chrislusf/weed-fs/go/filer"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
/*
The entry in level db has this format:
key: genKey(dirId, fileName)
value: []byte(fid)
And genKey(dirId, fileName) use first 4 bytes to store dirId, and rest for fileName
*/
type FileListInLevelDb struct {
db *leveldb.DB
}
func NewFileListInLevelDb(dir string) (fl *FileListInLevelDb, err error) {
fl = &FileListInLevelDb{}
if fl.db, err = leveldb.OpenFile(dir, nil); err != nil {
return
}
return
}
func genKey(dirId filer.DirectoryId, fileName string) []byte {
ret := make([]byte, 0, 4+len(fileName))
for i := 3; i >= 0; i-- {
ret = append(ret, byte(dirId>>(uint(i)*8)))
}
ret = append(ret, []byte(fileName)...)
return ret
}
func (fl *FileListInLevelDb) CreateFile(dirId filer.DirectoryId, fileName string, fid string) (err error) {
glog.V(4).Infoln("directory", dirId, "fileName", fileName, "fid", fid)
return fl.db.Put(genKey(dirId, fileName), []byte(fid), nil)
}
func (fl *FileListInLevelDb) DeleteFile(dirId filer.DirectoryId, fileName string) (fid string, err error) {
if fid, err = fl.FindFile(dirId, fileName); err != nil {
return
}
err = fl.db.Delete(genKey(dirId, fileName), nil)
return fid, err
}
func (fl *FileListInLevelDb) FindFile(dirId filer.DirectoryId, fileName string) (fid string, err error) {
data, e := fl.db.Get(genKey(dirId, fileName), nil)
if e != nil {
return "", e
}
return string(data), nil
}
func (fl *FileListInLevelDb) ListFiles(dirId filer.DirectoryId, lastFileName string, limit int) (files []filer.FileEntry) {
glog.V(4).Infoln("directory", dirId, "lastFileName", lastFileName, "limit", limit)
dirKey := genKey(dirId, "")
iter := fl.db.NewIterator(&util.Range{Start: genKey(dirId, lastFileName)}, nil)
limitCounter := 0
for iter.Next() {
key := iter.Key()
if !bytes.HasPrefix(key, dirKey) {
break
}
fileName := string(key[len(dirKey):])
if fileName == lastFileName {
continue
}
limitCounter++
if limit > 0 {
if limitCounter > limit {
break
}
}
files = append(files, filer.FileEntry{Name: fileName, Id: filer.FileId(string(iter.Value()))})
}
iter.Release()
return
}
-28
View File
@@ -1,28 +0,0 @@
package filer
type FileId string //file id on weedfs
type FileEntry struct {
Name string `json:"name,omitempty"` //file name without path
Id FileId `json:"fid,omitempty"`
}
type DirectoryId int32
type DirectoryEntry struct {
Name string //dir name without path
Id DirectoryId
}
type Filer interface {
CreateFile(fullFileName string, fid string) (err error)
FindFile(fullFileName string) (fid string, err error)
DeleteFile(fullFileName string) (fid string, err error)
//Optional functions. embedded filer support these
FindDirectory(dirPath string) (dirId DirectoryId, err error)
ListDirectories(dirPath string) (dirs []DirectoryEntry, err error)
ListFiles(dirPath string, lastFileName string, limit int) (files []FileEntry, err error)
DeleteDirectory(dirPath string, recursive bool) (err error)
Move(fromPath string, toPath string) (err error)
}
@@ -1,50 +0,0 @@
package flat_namespace
import (
"errors"
"github.com/chrislusf/weed-fs/go/filer"
)
type FlatNamesapceFiler struct {
master string
store FlatNamespaceStore
}
var (
ErrNotImplemented = errors.New("Not Implemented for flat namespace meta data store")
)
func NewFlatNamesapceFiler(master string, store FlatNamespaceStore) *FlatNamesapceFiler {
return &FlatNamesapceFiler{
master: master,
store: store,
}
}
func (filer *FlatNamesapceFiler) CreateFile(fullFileName string, fid string) (err error) {
return filer.store.Put(fullFileName, fid)
}
func (filer *FlatNamesapceFiler) FindFile(fullFileName string) (fid string, err error) {
return filer.store.Get(fullFileName)
}
func (filer *FlatNamesapceFiler) FindDirectory(dirPath string) (dirId filer.DirectoryId, err error) {
return 0, ErrNotImplemented
}
func (filer *FlatNamesapceFiler) ListDirectories(dirPath string) (dirs []filer.DirectoryEntry, err error) {
return nil, ErrNotImplemented
}
func (filer *FlatNamesapceFiler) ListFiles(dirPath string, lastFileName string, limit int) (files []filer.FileEntry, err error) {
return nil, ErrNotImplemented
}
func (filer *FlatNamesapceFiler) DeleteDirectory(dirPath string, recursive bool) (err error) {
return ErrNotImplemented
}
func (filer *FlatNamesapceFiler) DeleteFile(fullFileName string) (fid string, err error) {
return filer.store.Delete(fullFileName)
}
func (filer *FlatNamesapceFiler) Move(fromPath string, toPath string) error {
return ErrNotImplemented
}
@@ -1,9 +0,0 @@
package flat_namespace
import ()
type FlatNamespaceStore interface {
Put(fullFileName string, fid string) (err error)
Get(fullFileName string) (fid string, err error)
Delete(fullFileName string) (fid string, err error)
}
-48
View File
@@ -1,48 +0,0 @@
package redis_store
import (
redis "gopkg.in/redis.v2"
)
type RedisStore struct {
Client *redis.Client
}
func NewRedisStore(hostPort string, database int) *RedisStore {
client := redis.NewTCPClient(&redis.Options{
Addr: hostPort,
Password: "", // no password set
DB: int64(database),
})
return &RedisStore{Client: client}
}
func (s *RedisStore) Get(fullFileName string) (fid string, err error) {
fid, err = s.Client.Get(fullFileName).Result()
if err == redis.Nil {
err = nil
}
return fid, err
}
func (s *RedisStore) Put(fullFileName string, fid string) (err error) {
_, err = s.Client.Set(fullFileName, fid).Result()
if err == redis.Nil {
err = nil
}
return err
}
// Currently the fid is not returned
func (s *RedisStore) Delete(fullFileName string) (fid string, err error) {
_, err = s.Client.Del(fullFileName).Result()
if err == redis.Nil {
err = nil
}
return "", err
}
func (s *RedisStore) Close() {
if s.Client != nil {
s.Client.Close()
}
}
-46
View File
@@ -1,46 +0,0 @@
package images
import (
"bytes"
"image"
"image/gif"
"image/jpeg"
"image/png"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/disintegration/imaging"
)
func Resized(ext string, data []byte, width, height int) (resized []byte, w int, h int) {
if width == 0 && height == 0 {
return data, 0, 0
}
srcImage, _, err := image.Decode(bytes.NewReader(data))
if err == nil {
bounds := srcImage.Bounds()
var dstImage *image.NRGBA
if bounds.Dx() > width && width != 0 || bounds.Dy() > height && height != 0 {
if width == height && bounds.Dx() != bounds.Dy() {
dstImage = imaging.Thumbnail(srcImage, width, height, imaging.Lanczos)
w, h = width, height
} else {
dstImage = imaging.Resize(srcImage, width, height, imaging.Lanczos)
}
} else {
return data, bounds.Dx(), bounds.Dy()
}
var buf bytes.Buffer
switch ext {
case ".png":
png.Encode(&buf, dstImage)
case ".jpg", ".jpeg":
jpeg.Encode(&buf, dstImage, nil)
case ".gif":
gif.Encode(&buf, dstImage, nil)
}
return buf.Bytes(), dstImage.Bounds().Dx(), dstImage.Bounds().Dy()
} else {
glog.Error(err)
}
return data, 0, 0
}
-48
View File
@@ -1,48 +0,0 @@
package operation
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
)
type AssignResult struct {
Fid string `json:"fid,omitempty"`
Url string `json:"url,omitempty"`
PublicUrl string `json:"publicUrl,omitempty"`
Count uint64 `json:"count,omitempty"`
Error string `json:"error,omitempty"`
}
func Assign(server string, count uint64, replication string, collection string, ttl string) (*AssignResult, error) {
values := make(url.Values)
values.Add("count", strconv.FormatUint(count, 10))
if replication != "" {
values.Add("replication", replication)
}
if collection != "" {
values.Add("collection", collection)
}
if ttl != "" {
values.Add("ttl", ttl)
}
jsonBlob, err := util.Post("http://"+server+"/dir/assign", values)
glog.V(2).Info("assign result :", string(jsonBlob))
if err != nil {
return nil, err
}
var ret AssignResult
err = json.Unmarshal(jsonBlob, &ret)
if err != nil {
return nil, fmt.Errorf("/dir/assign result JSON unmarshal error:%v, json:%s", err, string(jsonBlob))
}
if ret.Count <= 0 {
return nil, errors.New(ret.Error)
}
return &ret, nil
}
-104
View File
@@ -1,104 +0,0 @@
package operation
import (
"encoding/json"
"errors"
"net/url"
"strings"
"sync"
"github.com/chrislusf/weed-fs/go/security"
"github.com/chrislusf/weed-fs/go/util"
)
type DeleteResult struct {
Fid string `json:"fid"`
Size int `json:"size"`
Error string `json:"error,omitempty"`
}
func DeleteFile(master string, fileId string, jwt security.EncodedJwt) error {
fileUrl, err := LookupFileId(master, fileId)
if err != nil {
return err
}
return util.Delete(fileUrl, jwt)
}
func ParseFileId(fid string) (vid string, key_cookie string, err error) {
commaIndex := strings.Index(fid, ",")
if commaIndex <= 0 {
return "", "", errors.New("Wrong fid format.")
}
return fid[:commaIndex], fid[commaIndex+1:], nil
}
type DeleteFilesResult struct {
Errors []string
Results []DeleteResult
}
func DeleteFiles(master string, fileIds []string) (*DeleteFilesResult, error) {
vid_to_fileIds := make(map[string][]string)
ret := &DeleteFilesResult{}
var vids []string
for _, fileId := range fileIds {
vid, _, err := ParseFileId(fileId)
if err != nil {
ret.Results = append(ret.Results, DeleteResult{Fid: vid, Error: err.Error()})
continue
}
if _, ok := vid_to_fileIds[vid]; !ok {
vid_to_fileIds[vid] = make([]string, 0)
vids = append(vids, vid)
}
vid_to_fileIds[vid] = append(vid_to_fileIds[vid], fileId)
}
lookupResults, err := LookupVolumeIds(master, vids)
if err != nil {
return ret, err
}
server_to_fileIds := make(map[string][]string)
for vid, result := range lookupResults {
if result.Error != "" {
ret.Errors = append(ret.Errors, result.Error)
continue
}
for _, location := range result.Locations {
if _, ok := server_to_fileIds[location.Url]; !ok {
server_to_fileIds[location.Url] = make([]string, 0)
}
server_to_fileIds[location.Url] = append(
server_to_fileIds[location.Url], vid_to_fileIds[vid]...)
}
}
var wg sync.WaitGroup
for server, fidList := range server_to_fileIds {
wg.Add(1)
go func(server string, fidList []string) {
defer wg.Done()
values := make(url.Values)
for _, fid := range fidList {
values.Add("fid", fid)
}
jsonBlob, err := util.Post("http://"+server+"/delete", values)
if err != nil {
ret.Errors = append(ret.Errors, err.Error()+" "+string(jsonBlob))
return
}
var result []DeleteResult
err = json.Unmarshal(jsonBlob, &result)
if err != nil {
ret.Errors = append(ret.Errors, err.Error()+" "+string(jsonBlob))
return
}
ret.Results = append(ret.Results, result...)
}(server, fidList)
}
wg.Wait()
return ret, nil
}
-203
View File
@@ -1,203 +0,0 @@
// Code generated by protoc-gen-go.
// source: system_message.proto
// DO NOT EDIT!
/*
Package operation is a generated protocol buffer package.
It is generated from these files:
system_message.proto
It has these top-level messages:
VolumeInformationMessage
JoinMessage
*/
package operation
import proto "github.com/golang/protobuf/proto"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = math.Inf
type VolumeInformationMessage struct {
Id *uint32 `protobuf:"varint,1,req,name=id" json:"id,omitempty"`
Size *uint64 `protobuf:"varint,2,req,name=size" json:"size,omitempty"`
Collection *string `protobuf:"bytes,3,opt,name=collection" json:"collection,omitempty"`
FileCount *uint64 `protobuf:"varint,4,req,name=file_count" json:"file_count,omitempty"`
DeleteCount *uint64 `protobuf:"varint,5,req,name=delete_count" json:"delete_count,omitempty"`
DeletedByteCount *uint64 `protobuf:"varint,6,req,name=deleted_byte_count" json:"deleted_byte_count,omitempty"`
ReadOnly *bool `protobuf:"varint,7,opt,name=read_only" json:"read_only,omitempty"`
ReplicaPlacement *uint32 `protobuf:"varint,8,req,name=replica_placement" json:"replica_placement,omitempty"`
Version *uint32 `protobuf:"varint,9,opt,name=version,def=2" json:"version,omitempty"`
Ttl *uint32 `protobuf:"varint,10,opt,name=ttl" json:"ttl,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *VolumeInformationMessage) Reset() { *m = VolumeInformationMessage{} }
func (m *VolumeInformationMessage) String() string { return proto.CompactTextString(m) }
func (*VolumeInformationMessage) ProtoMessage() {}
const Default_VolumeInformationMessage_Version uint32 = 2
func (m *VolumeInformationMessage) GetId() uint32 {
if m != nil && m.Id != nil {
return *m.Id
}
return 0
}
func (m *VolumeInformationMessage) GetSize() uint64 {
if m != nil && m.Size != nil {
return *m.Size
}
return 0
}
func (m *VolumeInformationMessage) GetCollection() string {
if m != nil && m.Collection != nil {
return *m.Collection
}
return ""
}
func (m *VolumeInformationMessage) GetFileCount() uint64 {
if m != nil && m.FileCount != nil {
return *m.FileCount
}
return 0
}
func (m *VolumeInformationMessage) GetDeleteCount() uint64 {
if m != nil && m.DeleteCount != nil {
return *m.DeleteCount
}
return 0
}
func (m *VolumeInformationMessage) GetDeletedByteCount() uint64 {
if m != nil && m.DeletedByteCount != nil {
return *m.DeletedByteCount
}
return 0
}
func (m *VolumeInformationMessage) GetReadOnly() bool {
if m != nil && m.ReadOnly != nil {
return *m.ReadOnly
}
return false
}
func (m *VolumeInformationMessage) GetReplicaPlacement() uint32 {
if m != nil && m.ReplicaPlacement != nil {
return *m.ReplicaPlacement
}
return 0
}
func (m *VolumeInformationMessage) GetVersion() uint32 {
if m != nil && m.Version != nil {
return *m.Version
}
return Default_VolumeInformationMessage_Version
}
func (m *VolumeInformationMessage) GetTtl() uint32 {
if m != nil && m.Ttl != nil {
return *m.Ttl
}
return 0
}
type JoinMessage struct {
IsInit *bool `protobuf:"varint,1,opt,name=is_init" json:"is_init,omitempty"`
Ip *string `protobuf:"bytes,2,req,name=ip" json:"ip,omitempty"`
Port *uint32 `protobuf:"varint,3,req,name=port" json:"port,omitempty"`
PublicUrl *string `protobuf:"bytes,4,opt,name=public_url" json:"public_url,omitempty"`
MaxVolumeCount *uint32 `protobuf:"varint,5,req,name=max_volume_count" json:"max_volume_count,omitempty"`
MaxFileKey *uint64 `protobuf:"varint,6,req,name=max_file_key" json:"max_file_key,omitempty"`
DataCenter *string `protobuf:"bytes,7,opt,name=data_center" json:"data_center,omitempty"`
Rack *string `protobuf:"bytes,8,opt,name=rack" json:"rack,omitempty"`
Volumes []*VolumeInformationMessage `protobuf:"bytes,9,rep,name=volumes" json:"volumes,omitempty"`
AdminPort *uint32 `protobuf:"varint,10,opt,name=admin_port" json:"admin_port,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *JoinMessage) Reset() { *m = JoinMessage{} }
func (m *JoinMessage) String() string { return proto.CompactTextString(m) }
func (*JoinMessage) ProtoMessage() {}
func (m *JoinMessage) GetIsInit() bool {
if m != nil && m.IsInit != nil {
return *m.IsInit
}
return false
}
func (m *JoinMessage) GetIp() string {
if m != nil && m.Ip != nil {
return *m.Ip
}
return ""
}
func (m *JoinMessage) GetPort() uint32 {
if m != nil && m.Port != nil {
return *m.Port
}
return 0
}
func (m *JoinMessage) GetPublicUrl() string {
if m != nil && m.PublicUrl != nil {
return *m.PublicUrl
}
return ""
}
func (m *JoinMessage) GetMaxVolumeCount() uint32 {
if m != nil && m.MaxVolumeCount != nil {
return *m.MaxVolumeCount
}
return 0
}
func (m *JoinMessage) GetMaxFileKey() uint64 {
if m != nil && m.MaxFileKey != nil {
return *m.MaxFileKey
}
return 0
}
func (m *JoinMessage) GetDataCenter() string {
if m != nil && m.DataCenter != nil {
return *m.DataCenter
}
return ""
}
func (m *JoinMessage) GetRack() string {
if m != nil && m.Rack != nil {
return *m.Rack
}
return ""
}
func (m *JoinMessage) GetVolumes() []*VolumeInformationMessage {
if m != nil {
return m.Volumes
}
return nil
}
func (m *JoinMessage) GetAdminPort() uint32 {
if m != nil && m.AdminPort != nil {
return *m.AdminPort
}
return 0
}
func init() {
}
-59
View File
@@ -1,59 +0,0 @@
package operation
import (
"encoding/json"
"log"
"testing"
"github.com/golang/protobuf/proto"
)
func TestSerialDeserial(t *testing.T) {
volumeMessage := &VolumeInformationMessage{
Id: proto.Uint32(12),
Size: proto.Uint64(2341234),
Collection: proto.String("benchmark"),
FileCount: proto.Uint64(2341234),
DeleteCount: proto.Uint64(234),
DeletedByteCount: proto.Uint64(21234),
ReadOnly: proto.Bool(false),
ReplicaPlacement: proto.Uint32(210),
Version: proto.Uint32(2),
}
var volumeMessages []*VolumeInformationMessage
volumeMessages = append(volumeMessages, volumeMessage)
joinMessage := &JoinMessage{
IsInit: proto.Bool(true),
Ip: proto.String("127.0.3.12"),
Port: proto.Uint32(34546),
PublicUrl: proto.String("localhost:2342"),
MaxVolumeCount: proto.Uint32(210),
MaxFileKey: proto.Uint64(324234423),
DataCenter: proto.String("dc1"),
Rack: proto.String("rack2"),
Volumes: volumeMessages,
}
data, err := proto.Marshal(joinMessage)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newMessage := &JoinMessage{}
err = proto.Unmarshal(data, newMessage)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
log.Println("The pb data size is", len(data))
jsonData, jsonError := json.Marshal(joinMessage)
if jsonError != nil {
log.Fatal("json marshaling error: ", jsonError)
}
log.Println("The json data size is", len(jsonData), string(jsonData))
// Now test and newTest contain the same data.
if *joinMessage.PublicUrl != *newMessage.PublicUrl {
log.Fatalf("data mismatch %q != %q", *joinMessage.PublicUrl, *newMessage.PublicUrl)
}
}
-4
View File
@@ -1,4 +0,0 @@
TARG=../operation
all:
protoc --go_out=$(TARG) system_message.proto
-27
View File
@@ -1,27 +0,0 @@
package operation;
message VolumeInformationMessage {
required uint32 id = 1;
required uint64 size = 2;
optional string collection = 3;
required uint64 file_count = 4;
required uint64 delete_count = 5;
required uint64 deleted_byte_count = 6;
optional bool read_only = 7;
required uint32 replica_placement = 8;
optional uint32 version = 9 [default=2];
optional uint32 ttl = 10;
}
message JoinMessage {
optional bool is_init = 1;
required string ip = 2;
required uint32 port = 3;
optional string public_url = 4;
required uint32 max_volume_count = 5;
required uint64 max_file_key = 6;
optional string data_center = 7;
optional string rack = 8;
repeated VolumeInformationMessage volumes = 9;
optional uint32 admin_port = 10;
}
-14
View File
@@ -1,14 +0,0 @@
package stats
type DiskStatus struct {
Dir string
All uint64
Used uint64
Free uint64
}
func NewDiskStatus(path string) (disk *DiskStatus) {
disk = &DiskStatus{Dir: path}
disk.fillInStatus()
return
}
-7
View File
@@ -1,7 +0,0 @@
// +build windows openbsd netbsd plan9 solaris
package stats
func (disk *DiskStatus) fillInStatus() {
return
}
-28
View File
@@ -1,28 +0,0 @@
package stats
import (
"runtime"
)
type MemStatus struct {
Goroutines int
All uint64
Used uint64
Free uint64
Self uint64
Heap uint64
Stack uint64
}
func MemStat() MemStatus {
mem := MemStatus{}
mem.Goroutines = runtime.NumGoroutine()
memStat := new(runtime.MemStats)
runtime.ReadMemStats(memStat)
mem.Self = memStat.Alloc
mem.Heap = memStat.HeapAlloc
mem.Stack = memStat.StackInuse
mem.fillInStatus()
return mem
}
-7
View File
@@ -1,7 +0,0 @@
// +build !linux
package stats
func (mem *MemStatus) fillInStatus() {
return
}
-188
View File
@@ -1,188 +0,0 @@
package storage
type NeedleValue struct {
Key Key
Offset uint32 `comment:"Volume offset"` //since aligned to 8 bytes, range is 4G*8=32G
Size uint32 `comment:"Size of the data portion"`
}
const (
batch = 100000
)
type Key uint64
type CompactSection struct {
values []NeedleValue
overflow map[Key]NeedleValue
start Key
end Key
counter int
}
func NewCompactSection(start Key) *CompactSection {
return &CompactSection{
values: make([]NeedleValue, batch),
overflow: make(map[Key]NeedleValue),
start: start,
}
}
//return old entry size
func (cs *CompactSection) Set(key Key, offset uint32, size uint32) uint32 {
ret := uint32(0)
if key > cs.end {
cs.end = key
}
if i := cs.binarySearchValues(key); i >= 0 {
ret = cs.values[i].Size
//println("key", key, "old size", ret)
cs.values[i].Offset, cs.values[i].Size = offset, size
} else {
needOverflow := cs.counter >= batch
needOverflow = needOverflow || cs.counter > 0 && cs.values[cs.counter-1].Key > key
if needOverflow {
//println("start", cs.start, "counter", cs.counter, "key", key)
if oldValue, found := cs.overflow[key]; found {
ret = oldValue.Size
}
cs.overflow[key] = NeedleValue{Key: key, Offset: offset, Size: size}
} else {
p := &cs.values[cs.counter]
p.Key, p.Offset, p.Size = key, offset, size
//println("added index", cs.counter, "key", key, cs.values[cs.counter].Key)
cs.counter++
}
}
return ret
}
//return old entry size
func (cs *CompactSection) Delete(key Key) uint32 {
ret := uint32(0)
if i := cs.binarySearchValues(key); i >= 0 {
if cs.values[i].Size > 0 {
ret = cs.values[i].Size
cs.values[i].Size = 0
}
}
if v, found := cs.overflow[key]; found {
delete(cs.overflow, key)
ret = v.Size
}
return ret
}
func (cs *CompactSection) Get(key Key) (*NeedleValue, bool) {
if v, ok := cs.overflow[key]; ok {
return &v, true
}
if i := cs.binarySearchValues(key); i >= 0 {
return &cs.values[i], true
}
return nil, false
}
func (cs *CompactSection) binarySearchValues(key Key) int {
l, h := 0, cs.counter-1
if h >= 0 && cs.values[h].Key < key {
return -2
}
//println("looking for key", key)
for l <= h {
m := (l + h) / 2
//println("mid", m, "key", cs.values[m].Key, cs.values[m].Offset, cs.values[m].Size)
if cs.values[m].Key < key {
l = m + 1
} else if key < cs.values[m].Key {
h = m - 1
} else {
//println("found", m)
return m
}
}
return -1
}
//This map assumes mostly inserting increasing keys
type CompactMap struct {
list []*CompactSection
}
func NewCompactMap() CompactMap {
return CompactMap{}
}
func (cm *CompactMap) Set(key Key, offset uint32, size uint32) uint32 {
x := cm.binarySearchCompactSection(key)
if x < 0 {
//println(x, "creating", len(cm.list), "section, starting", key)
cm.list = append(cm.list, NewCompactSection(key))
x = len(cm.list) - 1
//keep compact section sorted by start
for x > 0 {
if cm.list[x-1].start > cm.list[x].start {
cm.list[x-1], cm.list[x] = cm.list[x], cm.list[x-1]
x = x - 1
} else {
break
}
}
}
return cm.list[x].Set(key, offset, size)
}
func (cm *CompactMap) Delete(key Key) uint32 {
x := cm.binarySearchCompactSection(key)
if x < 0 {
return uint32(0)
}
return cm.list[x].Delete(key)
}
func (cm *CompactMap) Get(key Key) (*NeedleValue, bool) {
x := cm.binarySearchCompactSection(key)
if x < 0 {
return nil, false
}
return cm.list[x].Get(key)
}
func (cm *CompactMap) binarySearchCompactSection(key Key) int {
l, h := 0, len(cm.list)-1
if h < 0 {
return -5
}
if cm.list[h].start <= key {
if cm.list[h].counter < batch || key <= cm.list[h].end {
return h
}
return -4
}
for l <= h {
m := (l + h) / 2
if key < cm.list[m].start {
h = m - 1
} else { // cm.list[m].start <= key
if cm.list[m+1].start <= key {
l = m + 1
} else {
return m
}
}
}
return -3
}
func (cm *CompactMap) Visit(visit func(NeedleValue) error) error {
for _, cs := range cm.list {
for _, v := range cs.overflow {
if err := visit(v); err != nil {
return err
}
}
for _, v := range cs.values {
if _, found := cs.overflow[v.Key]; !found {
if err := visit(v); err != nil {
return err
}
}
}
}
return nil
}
-45
View File
@@ -1,45 +0,0 @@
package storage
import (
"log"
"os"
"testing"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
)
func TestMemoryUsage(t *testing.T) {
indexFile, ie := os.OpenFile("../../test/sample.idx", os.O_RDWR|os.O_RDONLY, 0644)
if ie != nil {
log.Fatalln(ie)
}
LoadNewNeedleMap(indexFile)
}
func LoadNewNeedleMap(file *os.File) CompactMap {
m := NewCompactMap()
bytes := make([]byte, 16*1024)
count, e := file.Read(bytes)
if count > 0 {
fstat, _ := file.Stat()
glog.V(0).Infoln("Loading index file", fstat.Name(), "size", fstat.Size())
}
for count > 0 && e == nil {
for i := 0; i < count; i += 16 {
key := util.BytesToUint64(bytes[i : i+8])
offset := util.BytesToUint32(bytes[i+8 : i+12])
size := util.BytesToUint32(bytes[i+12 : i+16])
if offset > 0 {
m.Set(Key(key), offset, size)
} else {
//delete(m, key)
}
}
count, e = file.Read(bytes)
}
return m
}
-77
View File
@@ -1,77 +0,0 @@
package storage
import (
"testing"
)
func TestIssue52(t *testing.T) {
m := NewCompactMap()
m.Set(Key(10002), 10002, 10002)
if element, ok := m.Get(Key(10002)); ok {
println("key", 10002, "ok", ok, element.Key, element.Offset, element.Size)
}
m.Set(Key(10001), 10001, 10001)
if element, ok := m.Get(Key(10002)); ok {
println("key", 10002, "ok", ok, element.Key, element.Offset, element.Size)
} else {
t.Fatal("key 10002 missing after setting 10001")
}
}
func TestXYZ(t *testing.T) {
m := NewCompactMap()
for i := uint32(0); i < 100*batch; i += 2 {
m.Set(Key(i), i, i)
}
for i := uint32(0); i < 100*batch; i += 37 {
m.Delete(Key(i))
}
for i := uint32(0); i < 10*batch; i += 3 {
m.Set(Key(i), i+11, i+5)
}
// for i := uint32(0); i < 100; i++ {
// if v := m.Get(Key(i)); v != nil {
// glog.V(4).Infoln(i, "=", v.Key, v.Offset, v.Size)
// }
// }
for i := uint32(0); i < 10*batch; i++ {
v, ok := m.Get(Key(i))
if i%3 == 0 {
if !ok {
t.Fatal("key", i, "missing!")
}
if v.Size != i+5 {
t.Fatal("key", i, "size", v.Size)
}
} else if i%37 == 0 {
if ok && v.Size > 0 {
t.Fatal("key", i, "should have been deleted needle value", v)
}
} else if i%2 == 0 {
if v.Size != i {
t.Fatal("key", i, "size", v.Size)
}
}
}
for i := uint32(10 * batch); i < 100*batch; i++ {
v, ok := m.Get(Key(i))
if i%37 == 0 {
if ok && v.Size > 0 {
t.Fatal("key", i, "should have been deleted needle value", v)
}
} else if i%2 == 0 {
if v == nil {
t.Fatal("key", i, "missing")
}
if v.Size != i {
t.Fatal("key", i, "size", v.Size)
}
}
}
}
-43
View File
@@ -1,43 +0,0 @@
package storage
import (
"encoding/hex"
"errors"
"strings"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
)
type FileId struct {
VolumeId VolumeId
Key uint64
Hashcode uint32
}
func NewFileIdFromNeedle(VolumeId VolumeId, n *Needle) *FileId {
return &FileId{VolumeId: VolumeId, Key: n.Id, Hashcode: n.Cookie}
}
func NewFileId(VolumeId VolumeId, Key uint64, Hashcode uint32) *FileId {
return &FileId{VolumeId: VolumeId, Key: Key, Hashcode: Hashcode}
}
func ParseFileId(fid string) (*FileId, error) {
a := strings.Split(fid, ",")
if len(a) != 2 {
glog.V(1).Infoln("Invalid fid ", fid, ", split length ", len(a))
return nil, errors.New("Invalid fid " + fid)
}
vid_string, key_hash_string := a[0], a[1]
volumeId, _ := NewVolumeId(vid_string)
key, hash, e := ParseKeyHash(key_hash_string)
return &FileId{VolumeId: volumeId, Key: key, Hashcode: hash}, e
}
func (n *FileId) String() string {
bytes := make([]byte, 12)
util.Uint64toBytes(bytes[0:8], n.Key)
util.Uint32toBytes(bytes[8:12], n.Hashcode)
nonzero_index := 0
for ; bytes[nonzero_index] == 0; nonzero_index++ {
}
return n.VolumeId.String() + "," + hex.EncodeToString(bytes[nonzero_index:])
}
-217
View File
@@ -1,217 +0,0 @@
package storage
import (
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"mime"
"net/http"
"path"
"strconv"
"strings"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/images"
"github.com/chrislusf/weed-fs/go/util"
)
const (
NeedleHeaderSize = 16 //should never change this
NeedlePaddingSize = 8
NeedleChecksumSize = 4
MaxPossibleVolumeSize = 4 * 1024 * 1024 * 1024 * 8
)
/*
* A Needle means a uploaded and stored file.
* Needle file size is limited to 4GB for now.
*/
type Needle struct {
Cookie uint32 `comment:"random number to mitigate brute force lookups"`
Id uint64 `comment:"needle id"`
Size uint32 `comment:"sum of DataSize,Data,NameSize,Name,MimeSize,Mime"`
DataSize uint32 `comment:"Data size"` //version2
Data []byte `comment:"The actual file data"`
Flags byte `comment:"boolean flags"` //version2
NameSize uint8 //version2
Name []byte `comment:"maximum 256 characters"` //version2
MimeSize uint8 //version2
Mime []byte `comment:"maximum 256 characters"` //version2
LastModified uint64 //only store LastModifiedBytesLength bytes, which is 5 bytes to disk
Ttl *TTL
Checksum CRC `comment:"CRC32 to check integrity"`
Padding []byte `comment:"Aligned to 8 bytes"`
}
func (n *Needle) String() (str string) {
str = fmt.Sprintf("Cookie:%d, Id:%d, Size:%d, DataSize:%d, Name: %s, Mime: %s", n.Cookie, n.Id, n.Size, n.DataSize, n.Name, n.Mime)
return
}
func ParseUpload(r *http.Request) (fileName string, data []byte, mimeType string, isGzipped bool, modifiedTime uint64, ttl *TTL, e error) {
form, fe := r.MultipartReader()
if fe != nil {
glog.V(0).Infoln("MultipartReader [ERROR]", fe)
e = fe
return
}
//first multi-part item
part, fe := form.NextPart()
if fe != nil {
glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
e = fe
return
}
fileName = part.FileName()
if fileName != "" {
fileName = path.Base(fileName)
}
data, e = ioutil.ReadAll(part)
if e != nil {
glog.V(0).Infoln("Reading Content [ERROR]", e)
return
}
//if the filename is empty string, do a search on the other multi-part items
for fileName == "" {
part2, fe := form.NextPart()
if fe != nil {
break // no more or on error, just safely break
}
fName := part2.FileName()
//found the first <file type> multi-part has filename
if fName != "" {
data2, fe2 := ioutil.ReadAll(part2)
if fe2 != nil {
glog.V(0).Infoln("Reading Content [ERROR]", fe2)
e = fe2
return
}
//update
data = data2
fileName = path.Base(fName)
break
}
}
dotIndex := strings.LastIndex(fileName, ".")
ext, mtype := "", ""
if dotIndex > 0 {
ext = strings.ToLower(fileName[dotIndex:])
mtype = mime.TypeByExtension(ext)
}
contentType := part.Header.Get("Content-Type")
if contentType != "" && mtype != contentType {
mimeType = contentType //only return mime type if not deductable
mtype = contentType
}
if part.Header.Get("Content-Encoding") == "gzip" {
isGzipped = true
} else if IsGzippable(ext, mtype) {
if data, e = GzipData(data); e != nil {
return
}
isGzipped = true
}
if ext == ".gz" {
isGzipped = true
}
if strings.HasSuffix(fileName, ".gz") &&
!strings.HasSuffix(fileName, ".tar.gz") {
fileName = fileName[:len(fileName)-3]
}
modifiedTime, _ = strconv.ParseUint(r.FormValue("ts"), 10, 64)
ttl, _ = ReadTTL(r.FormValue("ttl"))
return
}
func NewNeedle(r *http.Request, fixJpgOrientation bool) (n *Needle, e error) {
fname, mimeType, isGzipped := "", "", false
n = new(Needle)
fname, n.Data, mimeType, isGzipped, n.LastModified, n.Ttl, e = ParseUpload(r)
if e != nil {
return
}
if len(fname) < 256 {
n.Name = []byte(fname)
n.SetHasName()
}
if len(mimeType) < 256 {
n.Mime = []byte(mimeType)
n.SetHasMime()
}
if isGzipped {
n.SetGzipped()
}
if n.LastModified == 0 {
n.LastModified = uint64(time.Now().Unix())
}
n.SetHasLastModifiedDate()
if n.Ttl != EMPTY_TTL {
n.SetHasTtl()
}
if fixJpgOrientation {
loweredName := strings.ToLower(fname)
if mimeType == "image/jpeg" || strings.HasSuffix(loweredName, ".jpg") || strings.HasSuffix(loweredName, ".jpeg") {
n.Data = images.FixJpgOrientation(n.Data)
}
}
n.Checksum = NewCRC(n.Data)
commaSep := strings.LastIndex(r.URL.Path, ",")
dotSep := strings.LastIndex(r.URL.Path, ".")
fid := r.URL.Path[commaSep+1:]
if dotSep > 0 {
fid = r.URL.Path[commaSep+1 : dotSep]
}
e = n.ParsePath(fid)
return
}
func (n *Needle) ParsePath(fid string) (err error) {
length := len(fid)
if length <= 8 {
return errors.New("Invalid fid:" + fid)
}
delta := ""
deltaIndex := strings.LastIndex(fid, "_")
if deltaIndex > 0 {
fid, delta = fid[0:deltaIndex], fid[deltaIndex+1:]
}
n.Id, n.Cookie, err = ParseKeyHash(fid)
if err != nil {
return err
}
if delta != "" {
if d, e := strconv.ParseUint(delta, 10, 64); e == nil {
n.Id += d
} else {
return e
}
}
return err
}
func ParseKeyHash(key_hash_string string) (uint64, uint32, error) {
key_hash_bytes, khe := hex.DecodeString(key_hash_string)
key_hash_len := len(key_hash_bytes)
if khe != nil || key_hash_len <= 4 {
glog.V(0).Infoln("Invalid key_hash", key_hash_string, "length:", key_hash_len, "error", khe)
return 0, 0, errors.New("Invalid key and hash:" + key_hash_string)
}
key := util.BytesToUint64(key_hash_bytes[0 : key_hash_len-4])
hash := util.BytesToUint32(key_hash_bytes[key_hash_len-4 : key_hash_len])
return key, hash, nil
}
-68
View File
@@ -1,68 +0,0 @@
package storage
import (
"fmt"
"os"
"github.com/chrislusf/weed-fs/go/util"
)
type NeedleMapType int
const (
NeedleMapInMemory NeedleMapType = iota
NeedleMapLevelDb
NeedleMapBoltDb
)
type NeedleMapper interface {
Put(key uint64, offset uint32, size uint32) error
Get(key uint64) (element *NeedleValue, ok bool)
Delete(key uint64) error
Close()
Destroy() error
ContentSize() uint64
DeletedSize() uint64
FileCount() int
DeletedCount() int
MaxFileKey() uint64
}
type mapMetric struct {
DeletionCounter int `json:"DeletionCounter"`
FileCounter int `json:"FileCounter"`
DeletionByteCounter uint64 `json:"DeletionByteCounter"`
FileByteCounter uint64 `json:"FileByteCounter"`
MaximumFileKey uint64 `json:"MaxFileKey"`
}
func appendToIndexFile(indexFile *os.File,
key uint64, offset uint32, size uint32) error {
bytes := make([]byte, 16)
util.Uint64toBytes(bytes[0:8], key)
util.Uint32toBytes(bytes[8:12], offset)
util.Uint32toBytes(bytes[12:16], size)
if _, err := indexFile.Seek(0, 2); err != nil {
return fmt.Errorf("cannot seek end of indexfile %s: %v",
indexFile.Name(), err)
}
_, err := indexFile.Write(bytes)
return err
}
func (mm *mapMetric) logDelete(deletedByteCount uint32) {
mm.DeletionByteCounter = mm.DeletionByteCounter + uint64(deletedByteCount)
mm.DeletionCounter++
}
func (mm *mapMetric) logPut(key uint64, oldSize uint32, newSize uint32) {
if key > mm.MaximumFileKey {
mm.MaximumFileKey = key
}
mm.FileCounter++
mm.FileByteCounter = mm.FileByteCounter + uint64(newSize)
if oldSize > 0 {
mm.DeletionCounter++
mm.DeletionByteCounter = mm.DeletionByteCounter + uint64(oldSize)
}
}
-126
View File
@@ -1,126 +0,0 @@
package storage
import (
"io"
"os"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
)
type NeedleMap struct {
indexFile *os.File
m CompactMap
mapMetric
}
func NewNeedleMap(file *os.File) *NeedleMap {
nm := &NeedleMap{
m: NewCompactMap(),
indexFile: file,
}
return nm
}
const (
RowsToRead = 1024
)
func LoadNeedleMap(file *os.File) (*NeedleMap, error) {
nm := NewNeedleMap(file)
e := WalkIndexFile(file, func(key uint64, offset, size uint32) error {
if key > nm.MaximumFileKey {
nm.MaximumFileKey = key
}
nm.FileCounter++
nm.FileByteCounter = nm.FileByteCounter + uint64(size)
if offset > 0 {
oldSize := nm.m.Set(Key(key), offset, size)
glog.V(3).Infoln("reading key", key, "offset", offset*NeedlePaddingSize, "size", size, "oldSize", oldSize)
if oldSize > 0 {
nm.DeletionCounter++
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
}
} else {
oldSize := nm.m.Delete(Key(key))
glog.V(3).Infoln("removing key", key, "offset", offset*NeedlePaddingSize, "size", size, "oldSize", oldSize)
nm.DeletionCounter++
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
}
return nil
})
glog.V(1).Infoln("max file key:", nm.MaximumFileKey)
return nm, e
}
// walks through the index file, calls fn function with each key, offset, size
// stops with the error returned by the fn function
func WalkIndexFile(r *os.File, fn func(key uint64, offset, size uint32) error) error {
var readerOffset int64
bytes := make([]byte, 16*RowsToRead)
count, e := r.ReadAt(bytes, readerOffset)
glog.V(3).Infoln("file", r.Name(), "readerOffset", readerOffset, "count", count, "e", e)
readerOffset += int64(count)
var (
key uint64
offset, size uint32
i int
)
for count > 0 && e == nil || e == io.EOF {
for i = 0; i+16 <= count; i += 16 {
key = util.BytesToUint64(bytes[i : i+8])
offset = util.BytesToUint32(bytes[i+8 : i+12])
size = util.BytesToUint32(bytes[i+12 : i+16])
if e = fn(key, offset, size); e != nil {
return e
}
}
if e == io.EOF {
return nil
}
count, e = r.ReadAt(bytes, readerOffset)
glog.V(3).Infoln("file", r.Name(), "readerOffset", readerOffset, "count", count, "e", e)
readerOffset += int64(count)
}
return e
}
func (nm *NeedleMap) Put(key uint64, offset uint32, size uint32) error {
oldSize := nm.m.Set(Key(key), offset, size)
nm.logPut(key, oldSize, size)
return appendToIndexFile(nm.indexFile, key, offset, size)
}
func (nm *NeedleMap) Get(key uint64) (element *NeedleValue, ok bool) {
element, ok = nm.m.Get(Key(key))
return
}
func (nm *NeedleMap) Delete(key uint64) error {
deletedBytes := nm.m.Delete(Key(key))
nm.logDelete(deletedBytes)
return appendToIndexFile(nm.indexFile, key, 0, 0)
}
func (nm *NeedleMap) Close() {
_ = nm.indexFile.Close()
}
func (nm *NeedleMap) Destroy() error {
nm.Close()
return os.Remove(nm.indexFile.Name())
}
func (nm NeedleMap) ContentSize() uint64 {
return nm.FileByteCounter
}
func (nm NeedleMap) DeletedSize() uint64 {
return nm.DeletionByteCounter
}
func (nm NeedleMap) FileCount() int {
return nm.FileCounter
}
func (nm NeedleMap) DeletedCount() int {
return nm.DeletionCounter
}
func (nm NeedleMap) MaxFileKey() uint64 {
return nm.MaximumFileKey
}
-376
View File
@@ -1,376 +0,0 @@
package storage
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"strconv"
"strings"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/security"
"github.com/chrislusf/weed-fs/go/util"
"github.com/golang/protobuf/proto"
)
const (
MAX_TTL_VOLUME_REMOVAL_DELAY = 10 // 10 minutes
)
type DiskLocation struct {
Directory string
MaxVolumeCount int
volumes map[VolumeId]*Volume
}
func (mn *DiskLocation) reset() {
}
type MasterNodes struct {
nodes []string
lastNode int
}
func (mn *MasterNodes) String() string {
return fmt.Sprintf("nodes:%v, lastNode:%d", mn.nodes, mn.lastNode)
}
func NewMasterNodes(bootstrapNode string) (mn *MasterNodes) {
mn = &MasterNodes{nodes: []string{bootstrapNode}, lastNode: -1}
return
}
func (mn *MasterNodes) reset() {
if len(mn.nodes) > 1 && mn.lastNode > 0 {
mn.lastNode = -mn.lastNode
}
}
func (mn *MasterNodes) findMaster() (string, error) {
if len(mn.nodes) == 0 {
return "", errors.New("No master node found!")
}
if mn.lastNode < 0 {
for _, m := range mn.nodes {
if masters, e := operation.ListMasters(m); e == nil {
if len(masters) == 0 {
continue
}
mn.nodes = masters
mn.lastNode = rand.Intn(len(mn.nodes))
glog.V(2).Info("current master node is :", mn.nodes[mn.lastNode])
break
}
}
}
if mn.lastNode < 0 {
return "", errors.New("No master node available!")
}
return mn.nodes[mn.lastNode], nil
}
/*
* A VolumeServer contains one Store
*/
type Store struct {
Ip string
Port int
PublicUrl string
Locations []*DiskLocation
dataCenter string //optional informaton, overwriting master setting if exists
rack string //optional information, overwriting master setting if exists
connected bool
volumeSizeLimit uint64 //read from the master
masterNodes *MasterNodes
}
func (s *Store) String() (str string) {
str = fmt.Sprintf("Ip:%s, Port:%d, PublicUrl:%s, dataCenter:%s, rack:%s, connected:%v, volumeSizeLimit:%d, masterNodes:%s", s.Ip, s.Port, s.PublicUrl, s.dataCenter, s.rack, s.connected, s.volumeSizeLimit, s.masterNodes)
return
}
func NewStore(port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, needleMapKind NeedleMapType) (s *Store) {
s = &Store{Port: port, Ip: ip, PublicUrl: publicUrl}
s.Locations = make([]*DiskLocation, 0)
for i := 0; i < len(dirnames); i++ {
location := &DiskLocation{Directory: dirnames[i], MaxVolumeCount: maxVolumeCounts[i]}
location.volumes = make(map[VolumeId]*Volume)
location.loadExistingVolumes(needleMapKind)
s.Locations = append(s.Locations, location)
}
return
}
func (s *Store) AddVolume(volumeListString string, collection string, needleMapKind NeedleMapType, replicaPlacement string, ttlString string) error {
rt, e := NewReplicaPlacementFromString(replicaPlacement)
if e != nil {
return e
}
ttl, e := ReadTTL(ttlString)
if e != nil {
return e
}
for _, range_string := range strings.Split(volumeListString, ",") {
if strings.Index(range_string, "-") < 0 {
id_string := range_string
id, err := NewVolumeId(id_string)
if err != nil {
return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", id_string)
}
e = s.addVolume(VolumeId(id), collection, needleMapKind, rt, ttl)
} else {
pair := strings.Split(range_string, "-")
start, start_err := strconv.ParseUint(pair[0], 10, 64)
if start_err != nil {
return fmt.Errorf("Volume Start Id %s is not a valid unsigned integer!", pair[0])
}
end, end_err := strconv.ParseUint(pair[1], 10, 64)
if end_err != nil {
return fmt.Errorf("Volume End Id %s is not a valid unsigned integer!", pair[1])
}
for id := start; id <= end; id++ {
if err := s.addVolume(VolumeId(id), collection, needleMapKind, rt, ttl); err != nil {
e = err
}
}
}
}
return e
}
func (s *Store) DeleteCollection(collection string) (e error) {
for _, location := range s.Locations {
for k, v := range location.volumes {
if v.Collection == collection {
e = v.Destroy()
if e != nil {
return
}
delete(location.volumes, k)
}
}
}
return
}
func (s *Store) DeleteVolume(volumes map[VolumeId]*Volume, v *Volume) (e error) {
e = v.Destroy()
if e != nil {
return
}
delete(volumes, v.Id)
return
}
func (s *Store) findVolume(vid VolumeId) *Volume {
for _, location := range s.Locations {
if v, found := location.volumes[vid]; found {
return v
}
}
return nil
}
func (s *Store) findFreeLocation() (ret *DiskLocation) {
max := 0
for _, location := range s.Locations {
currentFreeCount := location.MaxVolumeCount - len(location.volumes)
if currentFreeCount > max {
max = currentFreeCount
ret = location
}
}
return ret
}
func (s *Store) addVolume(vid VolumeId, collection string, needleMapKind NeedleMapType, replicaPlacement *ReplicaPlacement, ttl *TTL) error {
if s.findVolume(vid) != nil {
return fmt.Errorf("Volume Id %d already exists!", vid)
}
if location := s.findFreeLocation(); location != nil {
glog.V(0).Infof("In dir %s adds volume:%v collection:%s replicaPlacement:%v ttl:%v",
location.Directory, vid, collection, replicaPlacement, ttl)
if volume, err := NewVolume(location.Directory, collection, vid, needleMapKind, replicaPlacement, ttl); err == nil {
location.volumes[vid] = volume
return nil
} else {
return err
}
}
return fmt.Errorf("No more free space left")
}
func (l *DiskLocation) loadExistingVolumes(needleMapKind NeedleMapType) {
if dirs, err := ioutil.ReadDir(l.Directory); err == nil {
for _, dir := range dirs {
name := dir.Name()
if !dir.IsDir() && strings.HasSuffix(name, ".dat") {
collection := ""
base := name[:len(name)-len(".dat")]
i := strings.Index(base, "_")
if i > 0 {
collection, base = base[0:i], base[i+1:]
}
if vid, err := NewVolumeId(base); err == nil {
if l.volumes[vid] == nil {
if v, e := NewVolume(l.Directory, collection, vid, needleMapKind, nil, nil); e == nil {
l.volumes[vid] = v
glog.V(0).Infof("data file %s, replicaPlacement=%s v=%d size=%d ttl=%s", l.Directory+"/"+name, v.ReplicaPlacement, v.Version(), v.Size(), v.Ttl.String())
}
}
}
}
}
}
glog.V(0).Infoln("Store started on dir:", l.Directory, "with", len(l.volumes), "volumes", "max", l.MaxVolumeCount)
}
func (s *Store) Status() []*VolumeInfo {
var stats []*VolumeInfo
for _, location := range s.Locations {
for k, v := range location.volumes {
s := &VolumeInfo{Id: VolumeId(k), Size: v.ContentSize(),
Collection: v.Collection,
ReplicaPlacement: v.ReplicaPlacement,
Version: v.Version(),
FileCount: v.nm.FileCount(),
DeleteCount: v.nm.DeletedCount(),
DeletedByteCount: v.nm.DeletedSize(),
ReadOnly: v.readOnly,
Ttl: v.Ttl}
stats = append(stats, s)
}
}
return stats
}
func (s *Store) SetDataCenter(dataCenter string) {
s.dataCenter = dataCenter
}
func (s *Store) SetRack(rack string) {
s.rack = rack
}
func (s *Store) SetBootstrapMaster(bootstrapMaster string) {
s.masterNodes = NewMasterNodes(bootstrapMaster)
}
func (s *Store) SendHeartbeatToMaster() (masterNode string, secretKey security.Secret, e error) {
masterNode, e = s.masterNodes.findMaster()
if e != nil {
return
}
var volumeMessages []*operation.VolumeInformationMessage
maxVolumeCount := 0
var maxFileKey uint64
for _, location := range s.Locations {
maxVolumeCount = maxVolumeCount + location.MaxVolumeCount
for k, v := range location.volumes {
if maxFileKey < v.nm.MaxFileKey() {
maxFileKey = v.nm.MaxFileKey()
}
if !v.expired(s.volumeSizeLimit) {
volumeMessage := &operation.VolumeInformationMessage{
Id: proto.Uint32(uint32(k)),
Size: proto.Uint64(uint64(v.Size())),
Collection: proto.String(v.Collection),
FileCount: proto.Uint64(uint64(v.nm.FileCount())),
DeleteCount: proto.Uint64(uint64(v.nm.DeletedCount())),
DeletedByteCount: proto.Uint64(v.nm.DeletedSize()),
ReadOnly: proto.Bool(v.readOnly),
ReplicaPlacement: proto.Uint32(uint32(v.ReplicaPlacement.Byte())),
Version: proto.Uint32(uint32(v.Version())),
Ttl: proto.Uint32(v.Ttl.ToUint32()),
}
volumeMessages = append(volumeMessages, volumeMessage)
} else {
if v.exiredLongEnough(MAX_TTL_VOLUME_REMOVAL_DELAY) {
s.DeleteVolume(location.volumes, v)
glog.V(0).Infoln("volume", v.Id, "is deleted.")
} else {
glog.V(0).Infoln("volume", v.Id, "is expired.")
}
}
}
}
joinMessage := &operation.JoinMessage{
IsInit: proto.Bool(!s.connected),
Ip: proto.String(s.Ip),
Port: proto.Uint32(uint32(s.Port)),
PublicUrl: proto.String(s.PublicUrl),
MaxVolumeCount: proto.Uint32(uint32(maxVolumeCount)),
MaxFileKey: proto.Uint64(maxFileKey),
DataCenter: proto.String(s.dataCenter),
Rack: proto.String(s.rack),
Volumes: volumeMessages,
}
data, err := proto.Marshal(joinMessage)
if err != nil {
return "", "", err
}
joinUrl := "http://" + masterNode + "/dir/join"
jsonBlob, err := util.PostBytes(joinUrl, data)
if err != nil {
s.masterNodes.reset()
return "", "", err
}
var ret operation.JoinResult
if err := json.Unmarshal(jsonBlob, &ret); err != nil {
glog.V(0).Infof("Failed to join %s with response: %s", joinUrl, string(jsonBlob))
return masterNode, "", err
}
if ret.Error != "" {
return masterNode, "", errors.New(ret.Error)
}
s.volumeSizeLimit = ret.VolumeSizeLimit
secretKey = security.Secret(ret.SecretKey)
s.connected = true
return
}
func (s *Store) Close() {
for _, location := range s.Locations {
for _, v := range location.volumes {
v.Close()
}
}
}
func (s *Store) Write(i VolumeId, n *Needle) (size uint32, err error) {
if v := s.findVolume(i); v != nil {
if v.readOnly {
err = fmt.Errorf("Volume %d is read only", i)
return
}
if MaxPossibleVolumeSize >= v.ContentSize()+uint64(size) {
size, err = v.write(n)
} else {
err = fmt.Errorf("Volume Size Limit %d Exceeded! Current size is %d", s.volumeSizeLimit, v.ContentSize())
}
if s.volumeSizeLimit < v.ContentSize()+3*uint64(size) {
glog.V(0).Infoln("volume", i, "size", v.ContentSize(), "will exceed limit", s.volumeSizeLimit)
if _, _, e := s.SendHeartbeatToMaster(); e != nil {
glog.V(0).Infoln("error when reporting size:", e)
}
}
return
}
glog.V(0).Infoln("volume", i, "not found!")
err = fmt.Errorf("Volume %d not found!", i)
return
}
func (s *Store) Delete(i VolumeId, n *Needle) (uint32, error) {
if v := s.findVolume(i); v != nil && !v.readOnly {
return v.delete(n)
}
return 0, nil
}
func (s *Store) Read(i VolumeId, n *Needle) (int, error) {
if v := s.findVolume(i); v != nil {
return v.read(n)
}
return 0, fmt.Errorf("Volume %v not found!", i)
}
func (s *Store) GetVolume(i VolumeId) *Volume {
return s.findVolume(i)
}
func (s *Store) HasVolume(i VolumeId) bool {
v := s.findVolume(i)
return v != nil
}
-44
View File
@@ -1,44 +0,0 @@
package storage
import (
"fmt"
"strconv"
"github.com/chrislusf/weed-fs/go/glog"
)
func (s *Store) CheckCompactVolume(volumeIdString string, garbageThresholdString string) (error, bool) {
vid, err := NewVolumeId(volumeIdString)
if err != nil {
return fmt.Errorf("Volume Id %s is not a valid unsigned integer", volumeIdString), false
}
garbageThreshold, e := strconv.ParseFloat(garbageThresholdString, 32)
if e != nil {
return fmt.Errorf("garbageThreshold %s is not a valid float number", garbageThresholdString), false
}
if v := s.findVolume(vid); v != nil {
glog.V(3).Infoln(vid, "garbage level is", v.garbageLevel())
return nil, garbageThreshold < v.garbageLevel()
}
return fmt.Errorf("volume id %d is not found during check compact", vid), false
}
func (s *Store) CompactVolume(volumeIdString string) error {
vid, err := NewVolumeId(volumeIdString)
if err != nil {
return fmt.Errorf("Volume Id %s is not a valid unsigned integer", volumeIdString)
}
if v := s.findVolume(vid); v != nil {
return v.Compact()
}
return fmt.Errorf("volume id %d is not found during compact", vid)
}
func (s *Store) CommitCompactVolume(volumeIdString string) error {
vid, err := NewVolumeId(volumeIdString)
if err != nil {
return fmt.Errorf("Volume Id %s is not a valid unsigned integer", volumeIdString)
}
if v := s.findVolume(vid); v != nil {
return v.commitCompact()
}
return fmt.Errorf("volume id %d is not found during commit compact", vid)
}
-392
View File
@@ -1,392 +0,0 @@
package storage
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path"
"sync"
"time"
"github.com/chrislusf/weed-fs/go/glog"
)
type Volume struct {
Id VolumeId
dir string
Collection string
dataFile *os.File
nm NeedleMapper
needleMapKind NeedleMapType
readOnly bool
SuperBlock
accessLock sync.Mutex
lastModifiedTime uint64 //unix time in seconds
}
func NewVolume(dirname string, collection string, id VolumeId, needleMapKind NeedleMapType, replicaPlacement *ReplicaPlacement, ttl *TTL) (v *Volume, e error) {
v = &Volume{dir: dirname, Collection: collection, Id: id}
v.SuperBlock = SuperBlock{ReplicaPlacement: replicaPlacement, Ttl: ttl}
v.needleMapKind = needleMapKind
e = v.load(true, true, needleMapKind)
return
}
func (v *Volume) String() string {
return fmt.Sprintf("Id:%v, dir:%s, Collection:%s, dataFile:%v, nm:%v, readOnly:%v", v.Id, v.dir, v.Collection, v.dataFile, v.nm, v.readOnly)
}
func loadVolumeWithoutIndex(dirname string, collection string, id VolumeId, needleMapKind NeedleMapType) (v *Volume, e error) {
v = &Volume{dir: dirname, Collection: collection, Id: id}
v.SuperBlock = SuperBlock{}
v.needleMapKind = needleMapKind
e = v.load(false, false, needleMapKind)
return
}
func (v *Volume) FileName() (fileName string) {
if v.Collection == "" {
fileName = path.Join(v.dir, v.Id.String())
} else {
fileName = path.Join(v.dir, v.Collection+"_"+v.Id.String())
}
return
}
func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind NeedleMapType) error {
var e error
fileName := v.FileName()
if exists, canRead, canWrite, modifiedTime := checkFile(fileName + ".dat"); exists {
if !canRead {
return fmt.Errorf("cannot read Volume Data file %s.dat", fileName)
}
if canWrite {
v.dataFile, e = os.OpenFile(fileName+".dat", os.O_RDWR|os.O_CREATE, 0644)
v.lastModifiedTime = uint64(modifiedTime.Unix())
} else {
glog.V(0).Infoln("opening " + fileName + ".dat in READONLY mode")
v.dataFile, e = os.Open(fileName + ".dat")
v.readOnly = true
}
} else {
if createDatIfMissing {
v.dataFile, e = os.OpenFile(fileName+".dat", os.O_RDWR|os.O_CREATE, 0644)
} else {
return fmt.Errorf("Volume Data file %s.dat does not exist.", fileName)
}
}
if e != nil {
if !os.IsPermission(e) {
return fmt.Errorf("cannot load Volume Data %s.dat: %v", fileName, e)
}
}
if v.ReplicaPlacement == nil {
e = v.readSuperBlock()
} else {
e = v.maybeWriteSuperBlock()
}
if e == nil && alsoLoadIndex {
var indexFile *os.File
if v.readOnly {
glog.V(1).Infoln("open to read file", fileName+".idx")
if indexFile, e = os.OpenFile(fileName+".idx", os.O_RDONLY, 0644); e != nil {
return fmt.Errorf("cannot read Volume Index %s.idx: %v", fileName, e)
}
} else {
glog.V(1).Infoln("open to write file", fileName+".idx")
if indexFile, e = os.OpenFile(fileName+".idx", os.O_RDWR|os.O_CREATE, 0644); e != nil {
return fmt.Errorf("cannot write Volume Index %s.idx: %v", fileName, e)
}
}
switch needleMapKind {
case NeedleMapInMemory:
glog.V(0).Infoln("loading index file", fileName+".idx", "readonly", v.readOnly)
if v.nm, e = LoadNeedleMap(indexFile); e != nil {
glog.V(0).Infof("loading index %s error: %v", fileName+".idx", e)
}
case NeedleMapLevelDb:
glog.V(0).Infoln("loading leveldb file", fileName+".ldb")
if v.nm, e = NewLevelDbNeedleMap(fileName+".ldb", indexFile); e != nil {
glog.V(0).Infof("loading leveldb %s error: %v", fileName+".ldb", e)
}
case NeedleMapBoltDb:
glog.V(0).Infoln("loading boltdb file", fileName+".bdb")
if v.nm, e = NewBoltDbNeedleMap(fileName+".bdb", indexFile); e != nil {
glog.V(0).Infof("loading boltdb %s error: %v", fileName+".bdb", e)
}
}
}
return e
}
func (v *Volume) Version() Version {
return v.SuperBlock.Version()
}
func (v *Volume) Size() int64 {
stat, e := v.dataFile.Stat()
if e == nil {
return stat.Size()
}
glog.V(0).Infof("Failed to read file size %s %v", v.dataFile.Name(), e)
return -1
}
// Close cleanly shuts down this volume
func (v *Volume) Close() {
v.accessLock.Lock()
defer v.accessLock.Unlock()
v.nm.Close()
_ = v.dataFile.Close()
}
func (v *Volume) NeedToReplicate() bool {
return v.ReplicaPlacement.GetCopyCount() > 1
}
// isFileUnchanged checks whether this needle to write is same as last one.
// It requires serialized access in the same volume.
func (v *Volume) isFileUnchanged(n *Needle) bool {
nv, ok := v.nm.Get(n.Id)
if ok && nv.Offset > 0 {
oldNeedle := new(Needle)
oldNeedle.Read(v.dataFile, int64(nv.Offset)*NeedlePaddingSize, nv.Size, v.Version())
if oldNeedle.Checksum == n.Checksum && bytes.Equal(oldNeedle.Data, n.Data) {
n.DataSize = oldNeedle.DataSize
return true
}
}
return false
}
// Destroy removes everything related to this volume
func (v *Volume) Destroy() (err error) {
if v.readOnly {
err = fmt.Errorf("%s is read-only", v.dataFile.Name())
return
}
v.Close()
err = os.Remove(v.dataFile.Name())
if err != nil {
return
}
err = v.nm.Destroy()
return
}
func (v *Volume) write(n *Needle) (size uint32, err error) {
glog.V(4).Infof("writing needle %s", NewFileIdFromNeedle(v.Id, n).String())
if v.readOnly {
err = fmt.Errorf("%s is read-only", v.dataFile.Name())
return
}
v.accessLock.Lock()
defer v.accessLock.Unlock()
if v.isFileUnchanged(n) {
size = n.DataSize
glog.V(4).Infof("needle is unchanged!")
return
}
var offset int64
if offset, err = v.dataFile.Seek(0, 2); err != nil {
glog.V(0).Infof("failed to seek the end of file: %v", err)
return
}
//ensure file writing starting from aligned positions
if offset%NeedlePaddingSize != 0 {
offset = offset + (NeedlePaddingSize - offset%NeedlePaddingSize)
if offset, err = v.dataFile.Seek(offset, 0); err != nil {
glog.V(0).Infof("failed to align in datafile %s: %v", v.dataFile.Name(), err)
return
}
}
if size, err = n.Append(v.dataFile, v.Version()); err != nil {
if e := v.dataFile.Truncate(offset); e != nil {
err = fmt.Errorf("%s\ncannot truncate %s: %v", err, v.dataFile.Name(), e)
}
return
}
nv, ok := v.nm.Get(n.Id)
if !ok || int64(nv.Offset)*NeedlePaddingSize < offset {
if err = v.nm.Put(n.Id, uint32(offset/NeedlePaddingSize), n.Size); err != nil {
glog.V(4).Infof("failed to save in needle map %d: %v", n.Id, err)
}
}
if v.lastModifiedTime < n.LastModified {
v.lastModifiedTime = n.LastModified
}
return
}
func (v *Volume) delete(n *Needle) (uint32, error) {
glog.V(4).Infof("delete needle %s", NewFileIdFromNeedle(v.Id, n).String())
if v.readOnly {
return 0, fmt.Errorf("%s is read-only", v.dataFile.Name())
}
v.accessLock.Lock()
defer v.accessLock.Unlock()
nv, ok := v.nm.Get(n.Id)
//fmt.Println("key", n.Id, "volume offset", nv.Offset, "data_size", n.Size, "cached size", nv.Size)
if ok {
size := nv.Size
if err := v.nm.Delete(n.Id); err != nil {
return size, err
}
if _, err := v.dataFile.Seek(0, 2); err != nil {
return size, err
}
n.Data = nil
_, err := n.Append(v.dataFile, v.Version())
return size, err
}
return 0, nil
}
func (v *Volume) read(n *Needle) (int, error) {
nv, ok := v.nm.Get(n.Id)
if !ok || nv.Offset == 0 {
return -1, errors.New("Not Found")
}
bytesRead, err := n.Read(v.dataFile, int64(nv.Offset)*NeedlePaddingSize, nv.Size, v.Version())
if err != nil {
return bytesRead, err
}
if !n.HasTtl() {
return bytesRead, err
}
ttlMinutes := n.Ttl.Minutes()
if ttlMinutes == 0 {
return bytesRead, nil
}
if !n.HasLastModifiedDate() {
return bytesRead, nil
}
if uint64(time.Now().Unix()) < n.LastModified+uint64(ttlMinutes*60) {
return bytesRead, nil
}
return -1, errors.New("Not Found")
}
func ScanVolumeFile(dirname string, collection string, id VolumeId,
needleMapKind NeedleMapType,
visitSuperBlock func(SuperBlock) error,
readNeedleBody bool,
visitNeedle func(n *Needle, offset int64) error) (err error) {
var v *Volume
if v, err = loadVolumeWithoutIndex(dirname, collection, id, needleMapKind); err != nil {
return fmt.Errorf("Failed to load volume %d: %v", id, err)
}
if err = visitSuperBlock(v.SuperBlock); err != nil {
return fmt.Errorf("Failed to read volume %d super block: %v", id, err)
}
version := v.Version()
offset := int64(SuperBlockSize)
n, rest, e := ReadNeedleHeader(v.dataFile, version, offset)
if e != nil {
err = fmt.Errorf("cannot read needle header: %v", e)
return
}
for n != nil {
if readNeedleBody {
if err = n.ReadNeedleBody(v.dataFile, version, offset+int64(NeedleHeaderSize), rest); err != nil {
glog.V(0).Infof("cannot read needle body: %v", err)
//err = fmt.Errorf("cannot read needle body: %v", err)
//return
}
if n.DataSize >= n.Size {
// this should come from a bug reported on #87 and #93
// fixed in v0.69
// remove this whole "if" clause later, long after 0.69
oldRest, oldSize := rest, n.Size
padding := NeedlePaddingSize - ((n.Size + NeedleHeaderSize + NeedleChecksumSize) % NeedlePaddingSize)
n.Size = 0
rest = n.Size + NeedleChecksumSize + padding
if rest%NeedlePaddingSize != 0 {
rest += (NeedlePaddingSize - rest%NeedlePaddingSize)
}
glog.V(4).Infof("Adjusting n.Size %d=>0 rest:%d=>%d %+v", oldSize, oldRest, rest, n)
}
}
if err = visitNeedle(n, offset); err != nil {
glog.V(0).Infof("visit needle error: %v", err)
}
offset += int64(NeedleHeaderSize) + int64(rest)
glog.V(4).Infof("==> new entry offset %d", offset)
if n, rest, err = ReadNeedleHeader(v.dataFile, version, offset); err != nil {
if err == io.EOF {
return nil
}
return fmt.Errorf("cannot read needle header: %v", err)
}
glog.V(4).Infof("new entry needle size:%d rest:%d", n.Size, rest)
}
return
}
func (v *Volume) ContentSize() uint64 {
return v.nm.ContentSize()
}
func checkFile(filename string) (exists, canRead, canWrite bool, modTime time.Time) {
exists = true
fi, err := os.Stat(filename)
if os.IsNotExist(err) {
exists = false
return
}
if fi.Mode()&0400 != 0 {
canRead = true
}
if fi.Mode()&0200 != 0 {
canWrite = true
}
modTime = fi.ModTime()
return
}
// volume is expired if modified time + volume ttl < now
// except when volume is empty
// or when the volume does not have a ttl
// or when volumeSizeLimit is 0 when server just starts
func (v *Volume) expired(volumeSizeLimit uint64) bool {
if volumeSizeLimit == 0 {
//skip if we don't know size limit
return false
}
if v.ContentSize() == 0 {
return false
}
if v.Ttl == nil || v.Ttl.Minutes() == 0 {
return false
}
glog.V(0).Infof("now:%v lastModified:%v", time.Now().Unix(), v.lastModifiedTime)
livedMinutes := (time.Now().Unix() - int64(v.lastModifiedTime)) / 60
glog.V(0).Infof("ttl:%v lived:%v", v.Ttl, livedMinutes)
if int64(v.Ttl.Minutes()) < livedMinutes {
return true
}
return false
}
// wait either maxDelayMinutes or 10% of ttl minutes
func (v *Volume) exiredLongEnough(maxDelayMinutes uint32) bool {
if v.Ttl == nil || v.Ttl.Minutes() == 0 {
return false
}
removalDelay := v.Ttl.Minutes() / 10
if removalDelay > maxDelayMinutes {
removalDelay = maxDelayMinutes
}
if uint64(v.Ttl.Minutes()+removalDelay)*60+v.lastModifiedTime < uint64(time.Now().Unix()) {
return true
}
return false
}
-44
View File
@@ -1,44 +0,0 @@
package storage
import (
"fmt"
"github.com/chrislusf/weed-fs/go/operation"
)
type VolumeInfo struct {
Id VolumeId
Size uint64
ReplicaPlacement *ReplicaPlacement
Ttl *TTL
Collection string
Version Version
FileCount int
DeleteCount int
DeletedByteCount uint64
ReadOnly bool
}
func NewVolumeInfo(m *operation.VolumeInformationMessage) (vi VolumeInfo, err error) {
vi = VolumeInfo{
Id: VolumeId(*m.Id),
Size: *m.Size,
Collection: *m.Collection,
FileCount: int(*m.FileCount),
DeleteCount: int(*m.DeleteCount),
DeletedByteCount: *m.DeletedByteCount,
ReadOnly: *m.ReadOnly,
Version: Version(*m.Version),
}
rp, e := NewReplicaPlacementFromByte(byte(*m.ReplicaPlacement))
if e != nil {
return vi, e
}
vi.ReplicaPlacement = rp
vi.Ttl = LoadTTLFromUint32(*m.Ttl)
return vi, nil
}
func (vi VolumeInfo) String() string {
return fmt.Sprintf("Id:%d, Size:%d, ReplicaPlacement:%s, Collection:%s, Version:%v, FileCount:%d, DeleteCount:%d, DeletedByteCount:%d, ReadOnly:%v",
vi.Id, vi.Size, vi.ReplicaPlacement, vi.Collection, vi.Version, vi.FileCount, vi.DeleteCount, vi.DeletedByteCount, vi.ReadOnly)
}
-76
View File
@@ -1,76 +0,0 @@
package storage
import (
"fmt"
"os"
"github.com/chrislusf/weed-fs/go/glog"
)
const (
SuperBlockSize = 8
)
/*
* Super block currently has 8 bytes allocated for each volume.
* Byte 0: version, 1 or 2
* Byte 1: Replica Placement strategy, 000, 001, 002, 010, etc
* Byte 2 and byte 3: Time to live. See TTL for definition
* Rest bytes: Reserved
*/
type SuperBlock struct {
version Version
ReplicaPlacement *ReplicaPlacement
Ttl *TTL
}
func (s *SuperBlock) Version() Version {
return s.version
}
func (s *SuperBlock) Bytes() []byte {
header := make([]byte, SuperBlockSize)
header[0] = byte(s.version)
header[1] = s.ReplicaPlacement.Byte()
s.Ttl.ToBytes(header[2:4])
return header
}
func (v *Volume) maybeWriteSuperBlock() error {
stat, e := v.dataFile.Stat()
if e != nil {
glog.V(0).Infof("failed to stat datafile %s: %v", v.dataFile, e)
return e
}
if stat.Size() == 0 {
v.SuperBlock.version = CurrentVersion
_, e = v.dataFile.Write(v.SuperBlock.Bytes())
if e != nil && os.IsPermission(e) {
//read-only, but zero length - recreate it!
if v.dataFile, e = os.Create(v.dataFile.Name()); e == nil {
if _, e = v.dataFile.Write(v.SuperBlock.Bytes()); e == nil {
v.readOnly = false
}
}
}
}
return e
}
func (v *Volume) readSuperBlock() (err error) {
if _, err = v.dataFile.Seek(0, 0); err != nil {
return fmt.Errorf("cannot seek to the beginning of %s: %v", v.dataFile.Name(), err)
}
header := make([]byte, SuperBlockSize)
if _, e := v.dataFile.Read(header); e != nil {
return fmt.Errorf("cannot read volume %d super block: %v", v.Id, e)
}
v.SuperBlock, err = ParseSuperBlock(header)
return err
}
func ParseSuperBlock(header []byte) (superBlock SuperBlock, err error) {
superBlock.version = Version(header[0])
if superBlock.ReplicaPlacement, err = NewReplicaPlacementFromByte(header[1]); err != nil {
err = fmt.Errorf("cannot read replica type: %s", err.Error())
}
superBlock.Ttl = LoadTTLFromBytes(header[2:4])
return
}
-90
View File
@@ -1,90 +0,0 @@
package storage
import (
"fmt"
"os"
"time"
"github.com/chrislusf/weed-fs/go/glog"
)
func (v *Volume) garbageLevel() float64 {
return float64(v.nm.DeletedSize()) / float64(v.ContentSize())
}
func (v *Volume) Compact() error {
glog.V(3).Infof("Compacting ...")
//no need to lock for copy on write
//v.accessLock.Lock()
//defer v.accessLock.Unlock()
//glog.V(3).Infof("Got Compaction lock...")
filePath := v.FileName()
glog.V(3).Infof("creating copies for volume %d ...", v.Id)
return v.copyDataAndGenerateIndexFile(filePath+".cpd", filePath+".cpx")
}
func (v *Volume) commitCompact() error {
glog.V(3).Infof("Committing vacuuming...")
v.accessLock.Lock()
defer v.accessLock.Unlock()
glog.V(3).Infof("Got Committing lock...")
_ = v.dataFile.Close()
var e error
if e = os.Rename(v.FileName()+".cpd", v.FileName()+".dat"); e != nil {
return e
}
if e = os.Rename(v.FileName()+".cpx", v.FileName()+".idx"); e != nil {
return e
}
//glog.V(3).Infof("Pretending to be vacuuming...")
//time.Sleep(20 * time.Second)
if e = v.load(true, false, v.needleMapKind); e != nil {
return e
}
return nil
}
func (v *Volume) copyDataAndGenerateIndexFile(dstName, idxName string) (err error) {
var (
dst, idx *os.File
)
if dst, err = os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
defer dst.Close()
if idx, err = os.OpenFile(idxName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
defer idx.Close()
nm := NewNeedleMap(idx)
new_offset := int64(SuperBlockSize)
now := uint64(time.Now().Unix())
err = ScanVolumeFile(v.dir, v.Collection, v.Id, v.needleMapKind,
func(superBlock SuperBlock) error {
_, err = dst.Write(superBlock.Bytes())
return err
}, true, func(n *Needle, offset int64) error {
if n.HasTtl() && now >= n.LastModified+uint64(v.Ttl.Minutes()*60) {
return nil
}
nv, ok := v.nm.Get(n.Id)
glog.V(4).Infoln("needle expected offset ", offset, "ok", ok, "nv", nv)
if ok && int64(nv.Offset)*NeedlePaddingSize == offset && nv.Size > 0 {
if err = nm.Put(n.Id, uint32(new_offset/NeedlePaddingSize), n.Size); err != nil {
return fmt.Errorf("cannot put needle: %s", err)
}
if _, err = n.Append(dst, v.Version()); err != nil {
return fmt.Errorf("cannot append needle: %s", err)
}
new_offset += n.DiskSize()
glog.V(3).Infoln("saving key", n.Id, "volume offset", offset, "=>", new_offset, "data_size", n.Size)
}
return nil
})
return
}
-35
View File
@@ -1,35 +0,0 @@
package topology
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/util"
)
type AllocateVolumeResult struct {
Error string
}
func AllocateVolume(dn *DataNode, vid storage.VolumeId, option *VolumeGrowOption) error {
values := make(url.Values)
values.Add("volume", vid.String())
values.Add("collection", option.Collection)
values.Add("replication", option.ReplicaPlacement.String())
values.Add("ttl", option.Ttl.String())
jsonBlob, err := util.Post("http://"+dn.Url()+"/admin/assign_volume", values)
if err != nil {
return err
}
var ret AllocateVolumeResult
if err := json.Unmarshal(jsonBlob, &ret); err != nil {
return fmt.Errorf("Invalid JSON result for %s: %s", "/admin/assign_volum", string(jsonBlob))
}
if ret.Error != "" {
return errors.New(ret.Error)
}
return nil
}
-42
View File
@@ -1,42 +0,0 @@
package topology
import (
"fmt"
"testing"
)
func TestLoadConfiguration(t *testing.T) {
confContent := `
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<Topology>
<DataCenter name="dc1">
<Rack name="rack1">
<Ip>192.168.1.1</Ip>
</Rack>
</DataCenter>
<DataCenter name="dc2">
<Rack name="rack1">
<Ip>192.168.1.2</Ip>
</Rack>
<Rack name="rack2">
<Ip>192.168.1.3</Ip>
<Ip>192.168.1.4</Ip>
</Rack>
</DataCenter>
</Topology>
</Configuration>
`
c, err := NewConfiguration([]byte(confContent))
fmt.Printf("%s\n", c)
if err != nil {
t.Fatalf("unmarshal error:%v", err)
}
if len(c.Topo.DataCenters) <= 0 || c.Topo.DataCenters[0].Name != "dc1" {
t.Fatalf("unmarshal error:%s", c)
}
}
-103
View File
@@ -1,103 +0,0 @@
package topology
import (
"bytes"
"net/http"
"strconv"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/security"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/util"
)
func ReplicatedWrite(masterNode string, s *storage.Store,
volumeId storage.VolumeId, needle *storage.Needle,
r *http.Request) (size uint32, errorStatus string) {
//check JWT
jwt := security.GetJwt(r)
ret, err := s.Write(volumeId, needle)
needToReplicate := !s.HasVolume(volumeId)
if err != nil {
errorStatus = "Failed to write to local disk (" + err.Error() + ")"
} else if ret > 0 {
needToReplicate = needToReplicate || s.GetVolume(volumeId).NeedToReplicate()
} else {
errorStatus = "Failed to write to local disk"
}
if !needToReplicate && ret > 0 {
needToReplicate = s.GetVolume(volumeId).NeedToReplicate()
}
if needToReplicate { //send to other replica locations
if r.FormValue("type") != "replicate" {
if !distributedOperation(masterNode, s, volumeId, func(location operation.Location) bool {
_, err := operation.Upload(
"http://"+location.Url+r.URL.Path+"?type=replicate&ts="+strconv.FormatUint(needle.LastModified, 10),
string(needle.Name), bytes.NewReader(needle.Data), needle.IsGzipped(), string(needle.Mime),
jwt)
return err == nil
}) {
ret = 0
errorStatus = "Failed to write to replicas for volume " + volumeId.String()
}
}
}
size = ret
return
}
func ReplicatedDelete(masterNode string, store *storage.Store,
volumeId storage.VolumeId, n *storage.Needle,
r *http.Request) (ret uint32) {
//check JWT
jwt := security.GetJwt(r)
ret, err := store.Delete(volumeId, n)
if err != nil {
glog.V(0).Infoln("delete error:", err)
return
}
needToReplicate := !store.HasVolume(volumeId)
if !needToReplicate && ret > 0 {
needToReplicate = store.GetVolume(volumeId).NeedToReplicate()
}
if needToReplicate { //send to other replica locations
if r.FormValue("type") != "replicate" {
if !distributedOperation(masterNode, store, volumeId, func(location operation.Location) bool {
return nil == util.Delete("http://"+location.Url+r.URL.Path+"?type=replicate", jwt)
}) {
ret = 0
}
}
}
return
}
func distributedOperation(masterNode string, store *storage.Store, volumeId storage.VolumeId, op func(location operation.Location) bool) bool {
if lookupResult, lookupErr := operation.Lookup(masterNode, volumeId.String()); lookupErr == nil {
length := 0
selfUrl := (store.Ip + ":" + strconv.Itoa(store.Port))
results := make(chan bool)
for _, location := range lookupResult.Locations {
if location.Url != selfUrl {
length++
go func(location operation.Location, results chan bool) {
results <- op(location)
}(location, results)
}
}
ret := true
for i := 0; i < length; i++ {
ret = ret && <-results
}
return ret
} else {
glog.V(0).Infoln("Failed to lookup for", volumeId, lookupErr.Error())
}
return false
}
-17
View File
@@ -1,17 +0,0 @@
package topology
import (
"testing"
)
func TestRemoveDataCenter(t *testing.T) {
topo := setup(topologyLayout)
topo.UnlinkChildNode(NodeId("dc2"))
if topo.GetActiveVolumeCount() != 15 {
t.Fail()
}
topo.UnlinkChildNode(NodeId("dc3"))
if topo.GetActiveVolumeCount() != 12 {
t.Fail()
}
}
-155
View File
@@ -1,155 +0,0 @@
package topology
import (
"encoding/json"
"errors"
"net/url"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/util"
)
func batchVacuumVolumeCheck(vl *VolumeLayout, vid storage.VolumeId, locationlist *VolumeLocationList, garbageThreshold string) bool {
ch := make(chan bool, locationlist.Length())
for index, dn := range locationlist.list {
go func(index int, url string, vid storage.VolumeId) {
//glog.V(0).Infoln(index, "Check vacuuming", vid, "on", dn.Url())
if e, ret := vacuumVolume_Check(url, vid, garbageThreshold); e != nil {
//glog.V(0).Infoln(index, "Error when checking vacuuming", vid, "on", url, e)
ch <- false
} else {
//glog.V(0).Infoln(index, "Checked vacuuming", vid, "on", url, "needVacuum", ret)
ch <- ret
}
}(index, dn.Url(), vid)
}
isCheckSuccess := true
for _ = range locationlist.list {
select {
case canVacuum := <-ch:
isCheckSuccess = isCheckSuccess && canVacuum
case <-time.After(30 * time.Minute):
isCheckSuccess = false
break
}
}
return isCheckSuccess
}
func batchVacuumVolumeCompact(vl *VolumeLayout, vid storage.VolumeId, locationlist *VolumeLocationList) bool {
vl.removeFromWritable(vid)
ch := make(chan bool, locationlist.Length())
for index, dn := range locationlist.list {
go func(index int, url string, vid storage.VolumeId) {
glog.V(0).Infoln(index, "Start vacuuming", vid, "on", url)
if e := vacuumVolume_Compact(url, vid); e != nil {
glog.V(0).Infoln(index, "Error when vacuuming", vid, "on", url, e)
ch <- false
} else {
glog.V(0).Infoln(index, "Complete vacuuming", vid, "on", url)
ch <- true
}
}(index, dn.Url(), vid)
}
isVacuumSuccess := true
for _ = range locationlist.list {
select {
case _ = <-ch:
case <-time.After(30 * time.Minute):
isVacuumSuccess = false
break
}
}
return isVacuumSuccess
}
func batchVacuumVolumeCommit(vl *VolumeLayout, vid storage.VolumeId, locationlist *VolumeLocationList) bool {
isCommitSuccess := true
for _, dn := range locationlist.list {
glog.V(0).Infoln("Start Commiting vacuum", vid, "on", dn.Url())
if e := vacuumVolume_Commit(dn.Url(), vid); e != nil {
glog.V(0).Infoln("Error when committing vacuum", vid, "on", dn.Url(), e)
isCommitSuccess = false
} else {
glog.V(0).Infoln("Complete Commiting vacuum", vid, "on", dn.Url())
}
if isCommitSuccess {
vl.SetVolumeAvailable(dn, vid)
}
}
return isCommitSuccess
}
func (t *Topology) Vacuum(garbageThreshold string) int {
for _, col := range t.collectionMap.Items {
c := col.(*Collection)
for _, vl := range c.storageType2VolumeLayout.Items {
if vl != nil {
volumeLayout := vl.(*VolumeLayout)
for vid, locationlist := range volumeLayout.vid2location {
if batchVacuumVolumeCheck(volumeLayout, vid, locationlist, garbageThreshold) {
if batchVacuumVolumeCompact(volumeLayout, vid, locationlist) {
batchVacuumVolumeCommit(volumeLayout, vid, locationlist)
}
}
}
}
}
}
return 0
}
type VacuumVolumeResult struct {
Result bool
Error string
}
func vacuumVolume_Check(urlLocation string, vid storage.VolumeId, garbageThreshold string) (error, bool) {
values := make(url.Values)
values.Add("volume", vid.String())
values.Add("garbageThreshold", garbageThreshold)
jsonBlob, err := util.Post("http://"+urlLocation+"/admin/vacuum_volume_check", values)
if err != nil {
glog.V(0).Infoln("parameters:", values)
return err, false
}
var ret VacuumVolumeResult
if err := json.Unmarshal(jsonBlob, &ret); err != nil {
return err, false
}
if ret.Error != "" {
return errors.New(ret.Error), false
}
return nil, ret.Result
}
func vacuumVolume_Compact(urlLocation string, vid storage.VolumeId) error {
values := make(url.Values)
values.Add("volume", vid.String())
jsonBlob, err := util.Post("http://"+urlLocation+"/admin/vacuum_volume_compact", values)
if err != nil {
return err
}
var ret VacuumVolumeResult
if err := json.Unmarshal(jsonBlob, &ret); err != nil {
return err
}
if ret.Error != "" {
return errors.New(ret.Error)
}
return nil
}
func vacuumVolume_Commit(urlLocation string, vid storage.VolumeId) error {
values := make(url.Values)
values.Add("volume", vid.String())
jsonBlob, err := util.Post("http://"+urlLocation+"/admin/vacuum_volume_commit", values)
if err != nil {
return err
}
var ret VacuumVolumeResult
if err := json.Unmarshal(jsonBlob, &ret); err != nil {
return err
}
if ret.Error != "" {
return errors.New(ret.Error)
}
return nil
}
-38
View File
@@ -1,38 +0,0 @@
package util
import (
"sync"
)
// A mostly for read map, which can thread-safely
// initialize the map entries.
type ConcurrentReadMap struct {
rmutex sync.RWMutex
mutex sync.Mutex
Items map[string]interface{}
}
func NewConcurrentReadMap() *ConcurrentReadMap {
return &ConcurrentReadMap{Items: make(map[string]interface{})}
}
func (m *ConcurrentReadMap) initMapEntry(key string, newEntry func() interface{}) (value interface{}) {
m.mutex.Lock()
defer m.mutex.Unlock()
if value, ok := m.Items[key]; ok {
return value
}
value = newEntry()
m.Items[key] = value
return value
}
func (m *ConcurrentReadMap) Get(key string, newEntry func() interface{}) interface{} {
m.rmutex.RLock()
if value, ok := m.Items[key]; ok {
m.rmutex.RUnlock()
return value
}
m.rmutex.RUnlock()
return m.initMapEntry(key, newEntry)
}
-130
View File
@@ -1,130 +0,0 @@
package util
// Copyright 2011 Numerotron Inc.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.
//
// Developed at www.stathat.com by Patrick Crosby
// Contact us on twitter with any questions: twitter.com/stat_hat
// The jconfig package provides a simple, basic configuration file parser using JSON.
import (
"bytes"
"encoding/json"
"os"
"github.com/chrislusf/weed-fs/go/glog"
)
type Config struct {
data map[string]interface{}
filename string
}
func newConfig() *Config {
result := new(Config)
result.data = make(map[string]interface{})
return result
}
// Loads config information from a JSON file
func LoadConfig(filename string) *Config {
result := newConfig()
result.filename = filename
err := result.parse()
if err != nil {
glog.Fatalf("error loading config file %s: %s", filename, err)
}
return result
}
// Loads config information from a JSON string
func LoadConfigString(s string) *Config {
result := newConfig()
err := json.Unmarshal([]byte(s), &result.data)
if err != nil {
glog.Fatalf("error parsing config string %s: %s", s, err)
}
return result
}
func (c *Config) StringMerge(s string) {
next := LoadConfigString(s)
c.merge(next.data)
}
func (c *Config) LoadMerge(filename string) {
next := LoadConfig(filename)
c.merge(next.data)
}
func (c *Config) merge(ndata map[string]interface{}) {
for k, v := range ndata {
c.data[k] = v
}
}
func (c *Config) parse() error {
f, err := os.Open(c.filename)
if err != nil {
return err
}
defer f.Close()
b := new(bytes.Buffer)
_, err = b.ReadFrom(f)
if err != nil {
return err
}
err = json.Unmarshal(b.Bytes(), &c.data)
if err != nil {
return err
}
return nil
}
// Returns a string for the config variable key
func (c *Config) GetString(key string) string {
result, present := c.data[key]
if !present {
return ""
}
return result.(string)
}
// Returns an int for the config variable key
func (c *Config) GetInt(key string) int {
x, ok := c.data[key]
if !ok {
return -1
}
return int(x.(float64))
}
// Returns a float for the config variable key
func (c *Config) GetFloat(key string) float64 {
x, ok := c.data[key]
if !ok {
return -1
}
return x.(float64)
}
// Returns a bool for the config variable key
func (c *Config) GetBool(key string) bool {
x, ok := c.data[key]
if !ok {
return false
}
return x.(bool)
}
// Returns an array for the config variable key
func (c *Config) GetArray(key string) []interface{} {
result, present := c.data[key]
if !present {
return []interface{}(nil)
}
return result.([]interface{})
}
-5
View File
@@ -1,5 +0,0 @@
package util
const (
VERSION = "0.69 20150413"
)
-106
View File
@@ -1,106 +0,0 @@
package util
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/chrislusf/weed-fs/go/security"
)
var (
client *http.Client
Transport *http.Transport
)
func init() {
Transport = &http.Transport{
MaxIdleConnsPerHost: 1024,
}
client = &http.Client{Transport: Transport}
}
func PostBytes(url string, body []byte) ([]byte, error) {
r, err := client.Post(url, "application/octet-stream", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("Post to %s: %v", url, err)
}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, fmt.Errorf("Read response body: %v", err)
}
return b, nil
}
func Post(url string, values url.Values) ([]byte, error) {
r, err := client.PostForm(url, values)
if err != nil {
return nil, err
}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
return b, nil
}
func Get(url string) ([]byte, error) {
r, err := client.Get(url)
if err != nil {
return nil, err
}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if r.StatusCode != 200 {
return nil, fmt.Errorf("%s: %s", url, r.Status)
}
if err != nil {
return nil, err
}
return b, nil
}
func Delete(url string, jwt security.EncodedJwt) error {
req, err := http.NewRequest("DELETE", url, nil)
if jwt != "" {
req.Header.Set("Authorization", "BEARER "+string(jwt))
}
if err != nil {
return err
}
resp, e := client.Do(req)
if e != nil {
return e
}
defer resp.Body.Close()
if _, err := ioutil.ReadAll(resp.Body); err != nil {
return err
}
return nil
}
func DownloadUrl(fileUrl string) (filename string, content []byte, e error) {
response, err := client.Get(fileUrl)
if err != nil {
return "", nil, err
}
defer response.Body.Close()
contentDisposition := response.Header["Content-Disposition"]
if len(contentDisposition) > 0 {
if strings.HasPrefix(contentDisposition[0], "filename=") {
filename = contentDisposition[0][len("filename="):]
filename = strings.Trim(filename, "\"")
}
}
content, e = ioutil.ReadAll(response.Body)
return
}
func Do(req *http.Request) (resp *http.Response, err error) {
return client.Do(req)
}
-46
View File
@@ -1,46 +0,0 @@
package main
import (
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/storage"
)
func init() {
cmdCompact.Run = runCompact // break init cycle
cmdCompact.IsDebug = cmdCompact.Flag.Bool("debug", false, "enable debug mode")
}
var cmdCompact = &Command{
UsageLine: "compact -dir=/tmp -volumeId=234",
Short: "run weed tool compact on volume file",
Long: `Force an compaction to remove deleted files from volume files.
The compacted .dat file is stored as .cpd file.
The compacted .idx file is stored as .cpx file.
`,
}
var (
compactVolumePath = cmdCompact.Flag.String("dir", "/tmp", "data directory to store files")
compactVolumeCollection = cmdCompact.Flag.String("collection", "", "volume collection name")
compactVolumeId = cmdCompact.Flag.Int("volumeId", -1, "a volume id. The volume should already exist in the dir.")
)
func runCompact(cmd *Command, args []string) bool {
if *compactVolumeId == -1 {
return false
}
vid := storage.VolumeId(*compactVolumeId)
v, err := storage.NewVolume(*compactVolumePath, *compactVolumeCollection, vid,
storage.NeedleMapInMemory, nil, nil)
if err != nil {
glog.Fatalf("Load Volume [ERROR] %s\n", err)
}
if err = v.Compact(); err != nil {
glog.Fatalf("Compact Volume [ERROR] %s\n", err)
}
return true
}
-477
View File
@@ -1,477 +0,0 @@
mode: set
github.com/chrislusf/weed-fs/go/weed/weed.go:41.27,43.20 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:46.2,46.17 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:43.20,45.3 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:49.13,56.19 6 0
github.com/chrislusf/weed-fs/go/weed/weed.go:60.2,60.23 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:71.2,71.31 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:88.2,90.8 3 0
github.com/chrislusf/weed-fs/go/weed/weed.go:56.19,58.3 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:60.23,62.32 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:68.3,68.9 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:62.32,63.65 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:63.65,66.5 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:71.31,72.46 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:72.46,73.28 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:74.4,77.27 4 0
github.com/chrislusf/weed-fs/go/weed/weed.go:83.4,84.10 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:73.28,73.43 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:77.27,82.5 4 0
github.com/chrislusf/weed-fs/go/weed/weed.go:114.55,118.43 4 0
github.com/chrislusf/weed-fs/go/weed/weed.go:118.43,120.3 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:123.34,124.13 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:127.2,128.43 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:124.13,126.3 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:131.30,133.2 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:135.14,140.2 4 0
github.com/chrislusf/weed-fs/go/weed/weed.go:143.26,144.20 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:149.2,149.20 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:154.2,156.31 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:164.2,165.12 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:144.20,148.3 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:149.20,152.3 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:156.31,157.24 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:157.24,161.4 2 0
github.com/chrislusf/weed-fs/go/weed/weed.go:170.23,172.2 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:174.13,175.32 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:178.2,178.21 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:175.32,177.3 1 0
github.com/chrislusf/weed-fs/go/weed/weed.go:181.35,183.2 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:17.13,19.2 1 1
github.com/chrislusf/weed-fs/go/weed/master.go:50.50,51.18 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:54.2,55.61 2 0
github.com/chrislusf/weed-fs/go/weed/master.go:58.2,58.34 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:62.2,73.14 6 0
github.com/chrislusf/weed-fs/go/weed/master.go:77.2,77.12 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:91.2,91.44 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:94.2,94.13 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:51.18,53.3 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:55.61,57.3 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:58.34,60.3 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:73.14,75.3 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:77.12,80.24 3 0
github.com/chrislusf/weed-fs/go/weed/master.go:83.3,84.25 2 0
github.com/chrislusf/weed-fs/go/weed/master.go:87.3,88.31 2 0
github.com/chrislusf/weed-fs/go/weed/master.go:80.24,82.4 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:84.25,86.4 1 0
github.com/chrislusf/weed-fs/go/weed/master.go:91.44,93.3 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:11.13,13.2 1 1
github.com/chrislusf/weed-fs/go/weed/shell.go:25.53,29.19 4 0
github.com/chrislusf/weed-fs/go/weed/shell.go:38.2,38.28 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:46.2,46.34 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:55.2,56.6 2 0
github.com/chrislusf/weed-fs/go/weed/shell.go:29.19,31.47 2 0
github.com/chrislusf/weed-fs/go/weed/shell.go:34.3,34.34 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:31.47,33.4 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:34.34,36.4 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:38.28,40.17 2 0
github.com/chrislusf/weed-fs/go/weed/shell.go:44.3,44.13 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:40.17,43.4 2 0
github.com/chrislusf/weed-fs/go/weed/shell.go:46.34,47.16 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:52.3,52.11 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:47.16,48.48 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:48.48,50.5 1 0
github.com/chrislusf/weed-fs/go/weed/shell.go:56.6,60.3 3 0
github.com/chrislusf/weed-fs/go/weed/upload.go:23.13,34.2 10 1
github.com/chrislusf/weed-fs/go/weed/upload.go:59.50,61.37 2 0
github.com/chrislusf/weed-fs/go/weed/upload.go:102.2,102.13 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:61.37,62.23 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:65.3,65.82 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:62.23,64.4 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:65.82,66.18 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:89.4,89.14 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:66.18,67.22 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:67.22,68.24 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:73.6,74.18 2 0
github.com/chrislusf/weed-fs/go/weed/upload.go:77.6,82.18 4 0
github.com/chrislusf/weed-fs/go/weed/upload.go:68.24,69.70 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:69.70,71.8 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:74.18,76.7 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:82.18,84.7 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:86.5,88.5 1 0
github.com/chrislusf/weed-fs/go/weed/upload.go:91.3,93.15 2 0
github.com/chrislusf/weed-fs/go/weed/upload.go:96.3,100.29 3 0
github.com/chrislusf/weed-fs/go/weed/upload.go:93.15,95.4 1 0
github.com/chrislusf/weed-fs/go/weed/signal_handling.go:11.29,22.12 3 0
github.com/chrislusf/weed-fs/go/weed/signal_handling.go:22.12,23.28 1 0
github.com/chrislusf/weed-fs/go/weed/signal_handling.go:23.28,26.4 2 0
github.com/chrislusf/weed-fs/go/weed/volume.go:38.13,52.2 13 1
github.com/chrislusf/weed-fs/go/weed/volume.go:68.50,69.19 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:72.2,77.44 4 0
github.com/chrislusf/weed-fs/go/weed/volume.go:84.2,84.46 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:87.2,87.35 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:94.2,94.34 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:98.2,98.17 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:101.2,101.24 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:105.2,105.24 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:108.2,112.27 4 0
github.com/chrislusf/weed-fs/go/weed/volume.go:116.2,127.14 5 0
github.com/chrislusf/weed-fs/go/weed/volume.go:130.2,130.27 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:144.2,144.21 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:148.2,148.52 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:151.2,151.13 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:69.19,71.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:77.44,78.50 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:78.50,80.4 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:80.4,82.4 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:84.46,86.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:87.35,88.57 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:88.57,90.4 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:94.34,96.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:98.17,100.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:101.24,103.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:105.24,107.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:112.27,114.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:127.14,129.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:130.27,134.15 4 0
github.com/chrislusf/weed-fs/go/weed/volume.go:137.3,137.13 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:134.15,136.4 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:137.13,138.66 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:138.66,140.5 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:144.21,146.3 1 0
github.com/chrislusf/weed-fs/go/weed/volume.go:148.52,150.3 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:20.13,25.2 4 1
github.com/chrislusf/weed-fs/go/weed/download.go:41.52,42.27 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:78.2,78.13 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:42.27,44.15 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:48.3,48.21 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:51.3,51.43 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:44.15,46.12 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:48.21,50.4 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:51.43,55.18 4 0
github.com/chrislusf/weed-fs/go/weed/download.go:59.4,60.32 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:55.18,57.13 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:60.32,63.19 3 0
github.com/chrislusf/weed-fs/go/weed/download.go:66.5,66.36 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:69.5,69.19 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:63.19,65.6 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:66.36,68.6 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:69.19,71.11 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:74.4,76.4 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:81.91,83.24 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:86.2,87.8 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:83.24,85.3 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:90.70,92.16 2 0
github.com/chrislusf/weed-fs/go/weed/download.go:95.2,97.33 3 0
github.com/chrislusf/weed-fs/go/weed/download.go:100.2,100.12 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:92.16,94.3 1 0
github.com/chrislusf/weed-fs/go/weed/download.go:97.33,99.3 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:12.13,14.2 1 1
github.com/chrislusf/weed-fs/go/weed/fix.go:30.47,32.24 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:36.2,37.32 2 0
github.com/chrislusf/weed-fs/go/weed/fix.go:40.2,41.16 2 0
github.com/chrislusf/weed-fs/go/weed/fix.go:44.2,50.116 5 0
github.com/chrislusf/weed-fs/go/weed/fix.go:63.2,63.16 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:67.2,67.13 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:32.24,34.3 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:37.32,39.3 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:41.16,43.3 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:50.116,52.3 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:52.56,54.17 2 0
github.com/chrislusf/weed-fs/go/weed/fix.go:61.3,61.13 1 0
github.com/chrislusf/weed-fs/go/weed/fix.go:54.17,57.4 2 0
github.com/chrislusf/weed-fs/go/weed/fix.go:57.4,60.4 2 0
github.com/chrislusf/weed-fs/go/weed/fix.go:63.16,65.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:28.13,30.2 1 1
github.com/chrislusf/weed-fs/go/weed/server.go:77.13,89.2 11 1
github.com/chrislusf/weed-fs/go/weed/server.go:91.50,93.37 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:102.2,102.34 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:106.2,108.49 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:112.2,112.28 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:116.2,116.23 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:119.2,124.44 5 0
github.com/chrislusf/weed-fs/go/weed/server.go:131.2,131.36 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:134.2,134.33 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:140.2,140.29 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:143.2,143.22 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:152.2,152.67 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:156.2,156.34 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:160.2,160.22 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:186.2,192.12 5 0
github.com/chrislusf/weed-fs/go/weed/server.go:224.2,226.28 3 0
github.com/chrislusf/weed-fs/go/weed/server.go:229.2,232.27 4 0
github.com/chrislusf/weed-fs/go/weed/server.go:235.2,247.20 4 0
github.com/chrislusf/weed-fs/go/weed/server.go:250.2,250.27 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:264.2,264.21 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:269.2,269.58 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:273.2,273.13 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:93.37,95.17 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:98.3,99.31 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:95.17,97.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:102.34,104.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:108.49,110.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:112.28,114.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:116.23,118.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:124.44,125.50 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:125.50,127.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:127.4,129.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:131.36,133.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:134.33,135.57 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:135.57,137.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:140.29,142.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:143.22,144.30 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:148.3,148.68 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:144.30,147.4 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:148.68,150.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:152.67,154.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:156.34,158.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:160.22,161.13 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:161.13,169.22 3 0
github.com/chrislusf/weed-fs/go/weed/server.go:172.4,177.16 3 0
github.com/chrislusf/weed-fs/go/weed/server.go:180.4,180.51 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:169.22,171.5 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:177.16,179.5 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:180.51,182.5 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:192.12,201.15 5 0
github.com/chrislusf/weed-fs/go/weed/server.go:205.3,205.13 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:218.3,219.51 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:201.15,203.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:205.13,210.26 5 0
github.com/chrislusf/weed-fs/go/weed/server.go:213.4,215.21 3 0
github.com/chrislusf/weed-fs/go/weed/server.go:210.26,212.5 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:219.51,221.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:226.28,228.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:232.27,234.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:247.20,249.3 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:250.27,254.15 4 0
github.com/chrislusf/weed-fs/go/weed/server.go:257.3,257.13 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:254.15,256.4 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:257.13,258.66 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:258.66,260.5 1 0
github.com/chrislusf/weed-fs/go/weed/server.go:264.21,267.3 2 0
github.com/chrislusf/weed-fs/go/weed/server.go:269.58,271.3 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:32.13,46.2 12 1
github.com/chrislusf/weed-fs/go/weed/filer.go:70.49,72.56 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:76.2,83.20 3 0
github.com/chrislusf/weed-fs/go/weed/filer.go:86.2,91.14 3 0
github.com/chrislusf/weed-fs/go/weed/filer.go:94.2,94.49 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:98.2,98.13 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:72.56,74.3 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:83.20,85.3 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:91.14,93.3 1 0
github.com/chrislusf/weed-fs/go/weed/filer.go:94.49,96.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:46.13,64.2 17 1
github.com/chrislusf/weed-fs/go/weed/benchmark.go:104.53,106.19 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:109.2,110.25 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:119.2,119.14 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:123.2,123.13 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:127.2,127.13 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:106.19,108.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:110.25,112.17 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:115.3,116.31 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:112.17,114.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:119.14,121.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:123.13,125.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:130.20,136.38 6 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:140.2,143.40 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:146.2,154.25 9 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:136.38,139.3 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:143.40,145.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:157.19,165.38 8 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:169.2,175.24 7 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:165.38,168.3 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:183.71,189.25 5 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:207.2,207.25 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:235.2,236.25 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:189.25,191.13 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:191.13,193.38 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:193.38,194.39 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:197.5,198.52 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:194.39,196.6 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:198.52,200.6 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:200.6,202.6 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:207.25,211.93 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:211.93,213.61 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:226.4,227.29 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:213.61,214.45 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:220.5,221.30 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:214.45,217.6 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:217.6,219.6 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:222.5,225.5 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:227.29,229.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:230.4,233.4 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:239.53,242.34 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:242.34,243.20 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:246.3,246.20 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:249.3,249.28 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:252.3,255.43 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:267.3,267.42 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:243.20,244.12 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:246.20,247.12 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:249.28,251.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:255.43,257.47 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:265.4,265.21 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:257.47,258.65 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:258.65,259.32 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:259.32,262.7 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:267.42,269.51 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:269.51,273.5 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:273.5,276.5 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:277.4,280.4 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:284.86,286.16 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:289.2,291.6 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:286.16,288.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:291.6,292.10 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:293.3,295.10 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:296.3,298.28 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:303.63,305.16 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:308.2,311.23 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:335.2,335.23 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:305.16,307.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:311.23,312.7 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:312.7,313.42 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:313.42,315.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:315.5,316.10 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:319.3,321.7 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:328.3,328.21 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:321.7,322.42 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:322.42,324.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:324.5,325.10 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:328.21,329.41 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:329.41,331.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:361.29,367.2 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:369.44,371.15 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:371.15,373.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:373.3,373.32 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:373.32,375.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:375.3,377.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:380.70,384.6 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:384.6,385.10 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:386.3,388.10 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:389.3,391.43 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:396.4,401.72 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:391.43,395.5 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:406.30,408.41 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:414.2,424.35 11 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:436.2,437.39 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:446.2,448.35 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:454.2,454.39 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:458.2,465.40 7 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:468.2,471.35 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:481.2,482.39 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:408.41,413.3 4 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:424.35,427.20 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:427.20,428.15 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:431.4,431.15 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:428.15,430.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:431.15,433.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:437.39,439.26 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:442.3,442.26 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:439.26,441.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:442.26,444.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:448.35,449.20 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:449.20,452.4 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:454.39,457.3 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:465.40,467.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:471.35,473.104 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:473.104,476.89 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:476.89,478.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:482.39,484.87 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:484.87,487.89 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:487.89,489.5 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:500.56,501.17 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:504.2,504.28 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:509.2,509.12 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:514.2,515.8 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:501.17,503.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:504.28,506.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:506.3,508.3 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:509.12,510.26 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:510.26,512.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:518.64,521.15 3 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:532.2,532.20 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:521.15,523.24 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:526.3,527.15 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:530.3,530.16 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:523.24,525.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:527.15,529.4 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:535.46,541.29 2 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:545.2,545.16 1 0
github.com/chrislusf/weed-fs/go/weed/benchmark.go:541.29,544.3 2 0
github.com/chrislusf/weed-fs/go/weed/command.go:32.33,35.12 3 0
github.com/chrislusf/weed-fs/go/weed/command.go:38.2,38.13 1 0
github.com/chrislusf/weed-fs/go/weed/command.go:35.12,37.3 1 0
github.com/chrislusf/weed-fs/go/weed/command.go:41.27,48.2 6 0
github.com/chrislusf/weed-fs/go/weed/command.go:52.35,54.2 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:18.13,20.2 1 1
github.com/chrislusf/weed-fs/go/weed/export.go:48.50,50.27 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:54.2,55.17 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:84.2,85.29 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:88.2,90.16 3 0
github.com/chrislusf/weed-fs/go/weed/export.go:93.2,96.16 3 0
github.com/chrislusf/weed-fs/go/weed/export.go:100.2,102.116 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:120.2,120.16 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:123.2,123.13 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:50.27,52.3 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:55.17,56.56 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:61.3,61.68 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:66.3,67.19 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:74.3,81.33 5 0
github.com/chrislusf/weed-fs/go/weed/export.go:56.56,59.4 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:61.68,64.4 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:67.19,69.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:69.4,70.46 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:70.46,72.5 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:85.29,87.3 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:90.16,92.3 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:96.16,98.3 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:102.116,105.3 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:105.55,109.56 3 0
github.com/chrislusf/weed-fs/go/weed/export.go:118.3,118.13 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:109.56,111.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:111.4,112.11 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:112.11,114.5 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:114.5,116.5 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:120.16,122.3 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:133.91,135.18 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:170.2,170.8 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:135.18,143.17 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:146.3,148.45 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:152.3,153.54 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:156.3,156.31 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:143.17,145.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:148.45,150.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:153.54,155.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:157.3,159.34 2 0
github.com/chrislusf/weed-fs/go/weed/export.go:162.3,168.4 1 0
github.com/chrislusf/weed-fs/go/weed/export.go:159.34,161.4 1 0
github.com/chrislusf/weed-fs/go/weed/version.go:17.51,18.20 1 0
github.com/chrislusf/weed-fs/go/weed/version.go:22.2,23.13 2 0
github.com/chrislusf/weed-fs/go/weed/version.go:18.20,20.3 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:8.13,11.2 2 1
github.com/chrislusf/weed-fs/go/weed/compact.go:29.51,31.28 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:35.2,37.16 3 0
github.com/chrislusf/weed-fs/go/weed/compact.go:40.2,40.35 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:44.2,44.13 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:31.28,33.3 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:37.16,39.3 1 0
github.com/chrislusf/weed-fs/go/weed/compact.go:40.35,42.3 1 0
github.com/chrislusf/weed-fs/go/weed/mount.go:12.13,17.2 4 1
github.com/chrislusf/weed-fs/go/weed/mount_std.go:19.49,21.29 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:26.2,27.16 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:32.2,32.21 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:37.2,38.16 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:43.2,44.37 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:48.2,48.13 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:21.29,24.3 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:27.16,30.3 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:32.21,35.3 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:38.16,40.3 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:44.37,46.3 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:56.30,58.2 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:59.58,61.2 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:68.33,70.2 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:72.74,74.14 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:77.2,77.33 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:80.2,80.55 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:74.14,76.3 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:77.33,79.3 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:85.36,87.2 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:89.69,91.79 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:97.2,97.78 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:106.2,106.17 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:91.79,92.38 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:92.38,95.4 2 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:97.78,98.33 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:98.33,99.64 1 0
github.com/chrislusf/weed-fs/go/weed/mount_std.go:99.64,102.5 2 0
-101
View File
@@ -1,101 +0,0 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/util"
)
var (
downloadReplication *string
downloadDir *string
)
func init() {
cmdDownload.Run = runDownload // break init cycle
cmdDownload.IsDebug = cmdDownload.Flag.Bool("debug", false, "verbose debug information")
server = cmdDownload.Flag.String("server", "localhost:9333", "weedfs master location")
downloadDir = cmdDownload.Flag.String("dir", ".", "Download the whole folder recursively if specified.")
}
var cmdDownload = &Command{
UsageLine: "download -server=localhost:9333 -dir=one_directory fid1 [fid2 fid3 ...]",
Short: "download files by file id",
Long: `download files by file id.
Usually you just need to use curl to lookup the file's volume server, and then download them directly.
This download tool combine the two steps into one.
What's more, if you use "weed upload -maxMB=..." option to upload a big file divided into chunks, you can
use this tool to download the chunks and merge them automatically.
`,
}
func runDownload(cmd *Command, args []string) bool {
for _, fid := range args {
filename, content, e := fetchFileId(*server, fid)
if e != nil {
fmt.Println("Fetch Error:", e)
continue
}
if filename == "" {
filename = fid
}
if strings.HasSuffix(filename, "-list") {
filename = filename[0 : len(filename)-len("-list")]
fids := strings.Split(string(content), "\n")
f, err := os.OpenFile(path.Join(*downloadDir, filename), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
fmt.Println("File Creation Error:", e)
continue
}
defer f.Close()
for _, partId := range fids {
var n int
_, part, err := fetchFileId(*server, partId)
if err == nil {
n, err = f.Write(part)
}
if err == nil && n < len(part) {
err = io.ErrShortWrite
}
if err != nil {
fmt.Println("File Write Error:", err)
break
}
}
} else {
ioutil.WriteFile(path.Join(*downloadDir, filename), content, os.ModePerm)
}
}
return true
}
func fetchFileId(server string, fileId string) (filename string, content []byte, e error) {
fileUrl, lookupError := operation.LookupFileId(server, fileId)
if lookupError != nil {
return "", nil, lookupError
}
filename, content, e = util.DownloadUrl(fileUrl)
return
}
func WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
f.Close()
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
return err
}
-172
View File
@@ -1,172 +0,0 @@
package main
import (
"archive/tar"
"bytes"
"fmt"
"os"
"path"
"strconv"
"strings"
"text/template"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/storage"
)
func init() {
cmdExport.Run = runExport // break init cycle
}
const (
defaultFnFormat = `{{.Mime}}/{{.Id}}:{{.Name}}`
)
var cmdExport = &Command{
UsageLine: "export -dir=/tmp -volumeId=234 -o=/dir/name.tar -fileNameFormat={{.Name}}",
Short: "list or export files from one volume data file",
Long: `List all files in a volume, or Export all files in a volume to a tar file if the output is specified.
The format of file name in the tar file can be customized. Default is {{.Mime}}/{{.Id}}:{{.Name}}. Also available is {{.Key}}.
`,
}
var (
exportVolumePath = cmdExport.Flag.String("dir", "/tmp", "input data directory to store volume data files")
exportCollection = cmdExport.Flag.String("collection", "", "the volume collection name")
exportVolumeId = cmdExport.Flag.Int("volumeId", -1, "a volume id. The volume .dat and .idx files should already exist in the dir.")
dest = cmdExport.Flag.String("o", "", "output tar file name, must ends with .tar, or just a \"-\" for stdout")
format = cmdExport.Flag.String("fileNameFormat", defaultFnFormat, "filename format, default to {{.Mime}}/{{.Id}}:{{.Name}}")
tarFh *tar.Writer
tarHeader tar.Header
fnTmpl *template.Template
fnTmplBuf = bytes.NewBuffer(nil)
)
func runExport(cmd *Command, args []string) bool {
if *exportVolumeId == -1 {
return false
}
var err error
if *dest != "" {
if *dest != "-" && !strings.HasSuffix(*dest, ".tar") {
fmt.Println("the output file", *dest, "should be '-' or end with .tar")
return false
}
if fnTmpl, err = template.New("name").Parse(*format); err != nil {
fmt.Println("cannot parse format " + *format + ": " + err.Error())
return false
}
var fh *os.File
if *dest == "-" {
fh = os.Stdout
} else {
if fh, err = os.Create(*dest); err != nil {
glog.Fatalf("cannot open output tar %s: %s", *dest, err)
}
}
defer fh.Close()
tarFh = tar.NewWriter(fh)
defer tarFh.Close()
t := time.Now()
tarHeader = tar.Header{Mode: 0644,
ModTime: t, Uid: os.Getuid(), Gid: os.Getgid(),
Typeflag: tar.TypeReg,
AccessTime: t, ChangeTime: t}
}
fileName := strconv.Itoa(*exportVolumeId)
if *exportCollection != "" {
fileName = *exportCollection + "_" + fileName
}
vid := storage.VolumeId(*exportVolumeId)
indexFile, err := os.OpenFile(path.Join(*exportVolumePath, fileName+".idx"), os.O_RDONLY, 0644)
if err != nil {
glog.Fatalf("Create Volume Index [ERROR] %s\n", err)
}
defer indexFile.Close()
nm, err := storage.LoadNeedleMap(indexFile)
if err != nil {
glog.Fatalf("cannot load needle map from %s: %s", indexFile.Name(), err)
}
var version storage.Version
err = storage.ScanVolumeFile(*exportVolumePath, *exportCollection, vid,
storage.NeedleMapInMemory,
func(superBlock storage.SuperBlock) error {
version = superBlock.Version()
return nil
}, true, func(n *storage.Needle, offset int64) error {
nv, ok := nm.Get(n.Id)
glog.V(3).Infof("key %d offset %d size %d disk_size %d gzip %v ok %v nv %+v",
n.Id, offset, n.Size, n.DiskSize(), n.IsGzipped(), ok, nv)
if ok && nv.Size > 0 && int64(nv.Offset)*8 == offset {
return walker(vid, n, version)
}
if !ok {
glog.V(2).Infof("This seems deleted %d size %d", n.Id, n.Size)
} else {
glog.V(2).Infof("Skipping later-updated Id %d size %d", n.Id, n.Size)
}
return nil
})
if err != nil {
glog.Fatalf("Export Volume File [ERROR] %s\n", err)
}
return true
}
type nameParams struct {
Name string
Id uint64
Mime string
Key string
}
func walker(vid storage.VolumeId, n *storage.Needle, version storage.Version) (err error) {
key := storage.NewFileIdFromNeedle(vid, n).String()
if tarFh != nil {
fnTmplBuf.Reset()
if err = fnTmpl.Execute(fnTmplBuf,
nameParams{Name: string(n.Name),
Id: n.Id,
Mime: string(n.Mime),
Key: key,
},
); err != nil {
return err
}
nm := fnTmplBuf.String()
if n.IsGzipped() && path.Ext(nm) != ".gz" {
nm = nm + ".gz"
}
tarHeader.Name, tarHeader.Size = nm, int64(len(n.Data))
if err = tarFh.WriteHeader(&tarHeader); err != nil {
return err
}
_, err = tarFh.Write(n.Data)
} else {
size := n.DataSize
if version == storage.Version1 {
size = n.Size
}
fmt.Printf("key=%s Name=%s Size=%d gzip=%t mime=%s\n",
key,
n.Name,
size,
n.IsGzipped(),
n.Mime,
)
}
return
}
-101
View File
@@ -1,101 +0,0 @@
package main
import (
"net/http"
"os"
"strconv"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
"github.com/chrislusf/weed-fs/go/weed/weed_server"
)
var (
f FilerOptions
)
type FilerOptions struct {
master *string
port *int
collection *string
defaultReplicaPlacement *string
dir *string
redirectOnRead *bool
disableDirListing *bool
secretKey *string
cassandra_server *string
cassandra_keyspace *string
redis_server *string
redis_database *int
}
func init() {
cmdFiler.Run = runFiler // break init cycle
f.master = cmdFiler.Flag.String("master", "localhost:9333", "master server location")
f.collection = cmdFiler.Flag.String("collection", "", "all data will be stored in this collection")
f.port = cmdFiler.Flag.Int("port", 8888, "filer server http listen port")
f.dir = cmdFiler.Flag.String("dir", os.TempDir(), "directory to store meta data")
f.defaultReplicaPlacement = cmdFiler.Flag.String("defaultReplicaPlacement", "000", "default replication type if not specified")
f.redirectOnRead = cmdFiler.Flag.Bool("redirectOnRead", false, "whether proxy or redirect to volume server during file GET request")
f.disableDirListing = cmdFiler.Flag.Bool("disableDirListing", false, "turn off directory listing")
f.cassandra_server = cmdFiler.Flag.String("cassandra.server", "", "host[:port] of the cassandra server")
f.cassandra_keyspace = cmdFiler.Flag.String("cassandra.keyspace", "seaweed", "keyspace of the cassandra server")
f.redis_server = cmdFiler.Flag.String("redis.server", "", "host:port of the redis server, e.g., 127.0.0.1:6379")
f.redis_database = cmdFiler.Flag.Int("redis.database", 0, "the database on the redis server")
f.secretKey = cmdFiler.Flag.String("secure.secret", "", "secret to encrypt Json Web Token(JWT)")
}
var cmdFiler = &Command{
UsageLine: "filer -port=8888 -dir=/tmp -master=<ip:port>",
Short: "start a file server that points to a master server",
Long: `start a file server which accepts REST operation for any files.
//create or overwrite the file, the directories /path/to will be automatically created
POST /path/to/file
//get the file content
GET /path/to/file
//create or overwrite the file, the filename in the multipart request will be used
POST /path/to/
//return a json format subdirectory and files listing
GET /path/to/
Current <fullpath~fileid> mapping metadata store is local embedded leveldb.
It should be highly scalable to hundreds of millions of files on a modest machine.
Future we will ensure it can avoid of being SPOF.
`,
}
func runFiler(cmd *Command, args []string) bool {
if err := util.TestFolderWritable(*f.dir); err != nil {
glog.Fatalf("Check Meta Folder (-dir) Writable %s : %s", *f.dir, err)
}
r := http.NewServeMux()
_, nfs_err := weed_server.NewFilerServer(r, *f.port, *f.master, *f.dir, *f.collection,
*f.defaultReplicaPlacement, *f.redirectOnRead, *f.disableDirListing,
*f.secretKey,
*f.cassandra_server, *f.cassandra_keyspace,
*f.redis_server, *f.redis_database,
)
if nfs_err != nil {
glog.Fatalf("Filer startup error: %v", nfs_err)
}
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*f.port))
filerListener, e := util.NewListener(
":"+strconv.Itoa(*f.port),
time.Duration(10)*time.Second,
)
if e != nil {
glog.Fatalf("Filer listener error: %v", e)
}
if e := http.Serve(filerListener, r); e != nil {
glog.Fatalf("Filer Fail to serve: %v", e)
}
return true
}
-95
View File
@@ -1,95 +0,0 @@
package main
import (
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/util"
"github.com/chrislusf/weed-fs/go/weed/weed_server"
"github.com/gorilla/mux"
)
func init() {
cmdMaster.Run = runMaster // break init cycle
}
var cmdMaster = &Command{
UsageLine: "master -port=9333",
Short: "start a master server",
Long: `start a master server to provide volume=>location mapping service
and sequence number of file ids
`,
}
var (
mport = cmdMaster.Flag.Int("port", 9333, "http listen port")
masterIp = cmdMaster.Flag.String("ip", "localhost", "master <ip>|<server> address")
masterBindIp = cmdMaster.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
mPublicUrl = cmdMaster.Flag.String("publicUrl", "", "peer accessible <ip>|<server_name>:port")
metaFolder = cmdMaster.Flag.String("mdir", os.TempDir(), "data directory to store meta data")
masterPeers = cmdMaster.Flag.String("peers", "", "other master nodes in comma separated ip:port list")
volumeSizeLimitMB = cmdMaster.Flag.Uint("volumeSizeLimitMB", 30*1000, "Master stops directing writes to oversized volumes.")
mpulse = cmdMaster.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats")
confFile = cmdMaster.Flag.String("conf", "/etc/weedfs/weedfs.conf", "Deprecating! xml configuration file")
defaultReplicaPlacement = cmdMaster.Flag.String("defaultReplication", "000", "Default replication type if not specified.")
mTimeout = cmdMaster.Flag.Int("idleTimeout", 10, "connection idle seconds")
mMaxCpu = cmdMaster.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
garbageThreshold = cmdMaster.Flag.String("garbageThreshold", "0.3", "threshold to vacuum and reclaim spaces")
masterWhiteListOption = cmdMaster.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
masterSecureKey = cmdMaster.Flag.String("secure.secret", "", "secret to encrypt Json Web Token(JWT)")
masterWhiteList []string
)
func runMaster(cmd *Command, args []string) bool {
if *mMaxCpu < 1 {
*mMaxCpu = runtime.NumCPU()
}
runtime.GOMAXPROCS(*mMaxCpu)
if err := util.TestFolderWritable(*metaFolder); err != nil {
glog.Fatalf("Check Meta Folder (-mdir) Writable %s : %s", *metaFolder, err)
}
if *masterWhiteListOption != "" {
masterWhiteList = strings.Split(*masterWhiteListOption, ",")
}
r := mux.NewRouter()
ms := weed_server.NewMasterServer(r, *mport, *metaFolder,
*volumeSizeLimitMB, *mpulse, *confFile, *defaultReplicaPlacement, *garbageThreshold,
masterWhiteList, *masterSecureKey,
)
listeningAddress := *masterBindIp + ":" + strconv.Itoa(*mport)
glog.V(0).Infoln("Start Seaweed Master", util.VERSION, "at", listeningAddress)
listener, e := util.NewListener(listeningAddress, time.Duration(*mTimeout)*time.Second)
if e != nil {
glog.Fatalf("Master startup error: %v", e)
}
go func() {
time.Sleep(100 * time.Millisecond)
myMasterAddress := *masterIp + ":" + strconv.Itoa(*mport)
if *mPublicUrl != "" {
myMasterAddress = *mPublicUrl
}
var peers []string
if *masterPeers != "" {
peers = strings.Split(*masterPeers, ",")
}
raftServer := weed_server.NewRaftServer(r, peers, myMasterAddress, *metaFolder, ms.Topo, *mpulse)
ms.SetRaftServer(raftServer)
}()
if e := http.Serve(listener, r); e != nil {
glog.Fatalf("Fail to serve: %v", e)
}
return true
}
-35
View File
@@ -1,35 +0,0 @@
package main
type MountOptions struct {
filer *string
dir *string
}
var (
mountOptions MountOptions
)
func init() {
cmdMount.Run = runMount // break init cycle
cmdMount.IsDebug = cmdMount.Flag.Bool("debug", false, "verbose debug information")
mountOptions.filer = cmdMount.Flag.String("filer", "localhost:8888", "weedfs filer location")
mountOptions.dir = cmdMount.Flag.String("dir", "", "mount weed filer to this directory")
}
var cmdMount = &Command{
UsageLine: "mount -filer=localhost:8888 -dir=/some/dir",
Short: "mount weed filer to a directory as file system in userspace(FUSE)",
Long: `mount weed file system to userspace.
Pre-requisites:
1) have a weed file system running
2) have a "weed filer" running
These 2 requirements can be achieved with one command "weed server -filer=true"
This uses bazil.org/fuse, whichenables writing FUSE file systems on
Linux, and OS X.
On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
`,
}
-104
View File
@@ -1,104 +0,0 @@
// +build linux darwin
package main
import (
"fmt"
"runtime"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"github.com/chrislusf/weed-fs/go/filer"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/util"
"golang.org/x/net/context"
)
func runMount(cmd *Command, args []string) bool {
fmt.Printf("This is Seaweed File System version %s %s %s\n", util.VERSION, runtime.GOOS, runtime.GOARCH)
if *mountOptions.dir == "" {
fmt.Printf("Please specify the mount directory via \"-dir\"")
return false
}
c, err := fuse.Mount(*mountOptions.dir)
if err != nil {
glog.Fatal(err)
return false
}
OnInterrupt(func() {
fuse.Unmount(*mountOptions.dir)
c.Close()
})
err = fs.Serve(c, WFS{})
if err != nil {
fuse.Unmount(*mountOptions.dir)
}
// check if the mount process has an error to report
<-c.Ready
if err := c.MountError; err != nil {
glog.Fatal(err)
}
return true
}
type File struct {
FileId filer.FileId
Name string
}
func (File) Attr(attr *fuse.Attr) {
}
func (File) ReadAll(ctx context.Context) ([]byte, error) {
return []byte("hello, world\n"), nil
}
type Dir struct {
Path string
Id uint64
}
func (dir Dir) Attr(attr *fuse.Attr) {
}
func (dir Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
files_result, e := filer.ListFiles(*mountOptions.filer, dir.Path, name)
if e != nil {
return nil, fuse.ENOENT
}
if len(files_result.Files) > 0 {
return File{files_result.Files[0].Id, files_result.Files[0].Name}, nil
}
return nil, fmt.Errorf("File Not Found for %s", name)
}
type WFS struct{}
func (WFS) Root() (fs.Node, error) {
return Dir{}, nil
}
func (dir *Dir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
var ret []fuse.Dirent
if dirs, e := filer.ListDirectories(*mountOptions.filer, dir.Path); e == nil {
for _, d := range dirs.Directories {
dirId := uint64(d.Id)
ret = append(ret, fuse.Dirent{Inode: dirId, Name: d.Name, Type: fuse.DT_Dir})
}
}
if files, e := filer.ListFiles(*mountOptions.filer, dir.Path, ""); e == nil {
for _, f := range files.Files {
if fileId, e := storage.ParseFileId(string(f.Id)); e == nil {
fileInode := uint64(fileId.VolumeId)<<48 + fileId.Key
ret = append(ret, fuse.Dirent{Inode: fileInode, Name: f.Name, Type: fuse.DT_File})
}
}
}
return ret, nil
}
-289
View File
@@ -1,289 +0,0 @@
package main
import (
"net/http"
"os"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"sync"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/util"
"github.com/chrislusf/weed-fs/go/weed/weed_server"
"github.com/gorilla/mux"
)
type ServerOptions struct {
cpuprofile *string
}
var (
serverOptions ServerOptions
filerOptions FilerOptions
)
func init() {
cmdServer.Run = runServer // break init cycle
}
var cmdServer = &Command{
UsageLine: "server -port=8080 -dir=/tmp -volume.max=5 -ip=server_name",
Short: "start a server, including volume server, and automatically elect a master server",
Long: `start both a volume server to provide storage spaces
and a master server to provide volume=>location mapping service and sequence number of file ids
This is provided as a convenient way to start both volume server and master server.
The servers are exactly the same as starting them separately.
So other volume servers can use this embedded master server also.
Optionally, one filer server can be started. Logically, filer servers should not be in a cluster.
They run with meta data on disk, not shared. So each filer server is different.
`,
}
var (
serverIp = cmdServer.Flag.String("ip", "localhost", "ip or server name")
serverPublicUrl = cmdServer.Flag.String("publicUrl", "", "publicly accessible address")
serverBindIp = cmdServer.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
serverMaxCpu = cmdServer.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
serverTimeout = cmdServer.Flag.Int("idleTimeout", 10, "connection idle seconds")
serverDataCenter = cmdServer.Flag.String("dataCenter", "", "current volume server's data center name")
serverRack = cmdServer.Flag.String("rack", "", "current volume server's rack name")
serverWhiteListOption = cmdServer.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
serverPeers = cmdServer.Flag.String("master.peers", "", "other master nodes in comma separated ip:masterPort list")
serverSecureKey = cmdServer.Flag.String("secure.secret", "", "secret to encrypt Json Web Token(JWT)")
serverGarbageThreshold = cmdServer.Flag.String("garbageThreshold", "0.3", "threshold to vacuum and reclaim spaces")
masterPort = cmdServer.Flag.Int("master.port", 9333, "master server http listen port")
masterMetaFolder = cmdServer.Flag.String("master.dir", "", "data directory to store meta data, default to same as -dir specified")
masterVolumeSizeLimitMB = cmdServer.Flag.Uint("master.volumeSizeLimitMB", 30*1000, "Master stops directing writes to oversized volumes.")
masterConfFile = cmdServer.Flag.String("master.conf", "/etc/weedfs/weedfs.conf", "xml configuration file")
masterDefaultReplicaPlacement = cmdServer.Flag.String("master.defaultReplicaPlacement", "000", "Default replication type if not specified.")
volumePort = cmdServer.Flag.Int("volume.port", 8080, "volume server http listen port")
volumePublicPort = cmdServer.Flag.Int("volume.port.public", 0, "volume server public port")
volumeDataFolders = cmdServer.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
volumeMaxDataVolumeCounts = cmdServer.Flag.String("volume.max", "7", "maximum numbers of volumes, count[,count]...")
volumePulse = cmdServer.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats")
volumeIndexType = cmdServer.Flag.String("volume.index", "memory", "Choose [memory|leveldb|boltdb] mode for memory~performance balance.")
volumeFixJpgOrientation = cmdServer.Flag.Bool("volume.images.fix.orientation", true, "Adjust jpg orientation when uploading.")
isStartingFiler = cmdServer.Flag.Bool("filer", false, "whether to start filer")
serverWhiteList []string
)
func init() {
serverOptions.cpuprofile = cmdServer.Flag.String("cpuprofile", "", "cpu profile output file")
filerOptions.master = cmdServer.Flag.String("filer.master", "", "default to current master server")
filerOptions.collection = cmdServer.Flag.String("filer.collection", "", "all data will be stored in this collection")
filerOptions.port = cmdServer.Flag.Int("filer.port", 8888, "filer server http listen port")
filerOptions.dir = cmdServer.Flag.String("filer.dir", "", "directory to store meta data, default to a 'filer' sub directory of what -mdir is specified")
filerOptions.defaultReplicaPlacement = cmdServer.Flag.String("filer.defaultReplicaPlacement", "", "Default replication type if not specified during runtime.")
filerOptions.redirectOnRead = cmdServer.Flag.Bool("filer.redirectOnRead", false, "whether proxy or redirect to volume server during file GET request")
filerOptions.disableDirListing = cmdServer.Flag.Bool("filer.disableDirListing", false, "turn off directory listing")
filerOptions.cassandra_server = cmdServer.Flag.String("filer.cassandra.server", "", "host[:port] of the cassandra server")
filerOptions.cassandra_keyspace = cmdServer.Flag.String("filer.cassandra.keyspace", "seaweed", "keyspace of the cassandra server")
filerOptions.redis_server = cmdServer.Flag.String("filer.redis.server", "", "host:port of the redis server, e.g., 127.0.0.1:6379")
filerOptions.redis_database = cmdServer.Flag.Int("filer.redis.database", 0, "the database on the redis server")
}
func runServer(cmd *Command, args []string) bool {
filerOptions.secretKey = serverSecureKey
if *serverOptions.cpuprofile != "" {
f, err := os.Create(*serverOptions.cpuprofile)
if err != nil {
glog.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if *filerOptions.redirectOnRead {
*isStartingFiler = true
}
*filerOptions.master = *serverIp + ":" + strconv.Itoa(*masterPort)
if *filerOptions.defaultReplicaPlacement == "" {
*filerOptions.defaultReplicaPlacement = *masterDefaultReplicaPlacement
}
if *volumePublicPort == 0 {
*volumePublicPort = *volumePort
}
if *serverMaxCpu < 1 {
*serverMaxCpu = runtime.NumCPU()
}
runtime.GOMAXPROCS(*serverMaxCpu)
folders := strings.Split(*volumeDataFolders, ",")
maxCountStrings := strings.Split(*volumeMaxDataVolumeCounts, ",")
var maxCounts []int
for _, maxString := range maxCountStrings {
if max, e := strconv.Atoi(maxString); e == nil {
maxCounts = append(maxCounts, max)
} else {
glog.Fatalf("The max specified in -max not a valid number %s", maxString)
}
}
if len(folders) != len(maxCounts) {
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(folders), len(maxCounts))
}
for _, folder := range folders {
if err := util.TestFolderWritable(folder); err != nil {
glog.Fatalf("Check Data Folder(-dir) Writable %s : %s", folder, err)
}
}
if *masterMetaFolder == "" {
*masterMetaFolder = folders[0]
}
if *isStartingFiler {
if *filerOptions.dir == "" {
*filerOptions.dir = *masterMetaFolder + "/filer"
os.MkdirAll(*filerOptions.dir, 0700)
}
if err := util.TestFolderWritable(*filerOptions.dir); err != nil {
glog.Fatalf("Check Mapping Meta Folder (-filer.dir=\"%s\") Writable: %s", *filerOptions.dir, err)
}
}
if err := util.TestFolderWritable(*masterMetaFolder); err != nil {
glog.Fatalf("Check Meta Folder (-mdir=\"%s\") Writable: %s", *masterMetaFolder, err)
}
if *serverWhiteListOption != "" {
serverWhiteList = strings.Split(*serverWhiteListOption, ",")
}
if *isStartingFiler {
go func() {
r := http.NewServeMux()
_, nfs_err := weed_server.NewFilerServer(r, *filerOptions.port, *filerOptions.master, *filerOptions.dir, *filerOptions.collection,
*filerOptions.defaultReplicaPlacement,
*filerOptions.redirectOnRead, *filerOptions.disableDirListing,
*filerOptions.secretKey,
"", "",
"", 0,
)
if nfs_err != nil {
glog.Fatalf("Filer startup error: %v", nfs_err)
}
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*filerOptions.port))
filerListener, e := util.NewListener(
":"+strconv.Itoa(*filerOptions.port),
time.Duration(10)*time.Second,
)
if e != nil {
glog.Fatalf("Filer listener error: %v", e)
}
if e := http.Serve(filerListener, r); e != nil {
glog.Fatalf("Filer Fail to serve: %v", e)
}
}()
}
var raftWaitForMaster sync.WaitGroup
var volumeWait sync.WaitGroup
raftWaitForMaster.Add(1)
volumeWait.Add(1)
go func() {
r := mux.NewRouter()
ms := weed_server.NewMasterServer(r, *masterPort, *masterMetaFolder,
*masterVolumeSizeLimitMB, *volumePulse, *masterConfFile, *masterDefaultReplicaPlacement, *serverGarbageThreshold,
serverWhiteList, *serverSecureKey,
)
glog.V(0).Infoln("Start Seaweed Master", util.VERSION, "at", *serverIp+":"+strconv.Itoa(*masterPort))
masterListener, e := util.NewListener(*serverBindIp+":"+strconv.Itoa(*masterPort), time.Duration(*serverTimeout)*time.Second)
if e != nil {
glog.Fatalf("Master startup error: %v", e)
}
go func() {
raftWaitForMaster.Wait()
time.Sleep(100 * time.Millisecond)
myAddress := *serverIp + ":" + strconv.Itoa(*masterPort)
var peers []string
if *serverPeers != "" {
peers = strings.Split(*serverPeers, ",")
}
raftServer := weed_server.NewRaftServer(r, peers, myAddress, *masterMetaFolder, ms.Topo, *volumePulse)
ms.SetRaftServer(raftServer)
volumeWait.Done()
}()
raftWaitForMaster.Done()
if e := http.Serve(masterListener, r); e != nil {
glog.Fatalf("Master Fail to serve:%s", e.Error())
}
}()
volumeWait.Wait()
time.Sleep(100 * time.Millisecond)
if *volumePublicPort == 0 {
*volumePublicPort = *volumePort
}
if *serverPublicUrl == "" {
*serverPublicUrl = *serverIp + ":" + strconv.Itoa(*volumePublicPort)
}
isSeperatedPublicPort := *volumePublicPort != *volumePort
volumeMux := http.NewServeMux()
publicVolumeMux := volumeMux
if isSeperatedPublicPort {
publicVolumeMux = http.NewServeMux()
}
volumeNeedleMapKind := storage.NeedleMapInMemory
switch *volumeIndexType {
case "leveldb":
volumeNeedleMapKind = storage.NeedleMapLevelDb
case "boltdb":
volumeNeedleMapKind = storage.NeedleMapBoltDb
}
volumeServer := weed_server.NewVolumeServer(volumeMux, publicVolumeMux,
*serverIp, *volumePort, *serverPublicUrl,
folders, maxCounts,
volumeNeedleMapKind,
*serverIp+":"+strconv.Itoa(*masterPort), *volumePulse, *serverDataCenter, *serverRack,
serverWhiteList, *volumeFixJpgOrientation,
)
glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "at", *serverIp+":"+strconv.Itoa(*volumePort))
volumeListener, eListen := util.NewListener(
*serverBindIp+":"+strconv.Itoa(*volumePort),
time.Duration(*serverTimeout)*time.Second,
)
if eListen != nil {
glog.Fatalf("Volume server listener error: %v", eListen)
}
if isSeperatedPublicPort {
publicListeningAddress := *serverIp + ":" + strconv.Itoa(*volumePublicPort)
glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "public at", publicListeningAddress)
publicListener, e := util.NewListener(publicListeningAddress, time.Duration(*serverTimeout)*time.Second)
if e != nil {
glog.Fatalf("Volume server listener error:%v", e)
}
go func() {
if e := http.Serve(publicListener, publicVolumeMux); e != nil {
glog.Fatalf("Volume server fail to serve public: %v", e)
}
}()
}
OnInterrupt(func() {
volumeServer.Shutdown()
pprof.StopCPUProfile()
})
if e := http.Serve(volumeListener, volumeMux); e != nil {
glog.Fatalf("Volume server fail to serve:%v", e)
}
return true
}
-28
View File
@@ -1,28 +0,0 @@
// +build !plan9
package main
import (
"os"
"os/signal"
"syscall"
)
func OnInterrupt(fn func()) {
// deal with control+c,etc
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan,
os.Interrupt,
os.Kill,
syscall.SIGALRM,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
go func() {
for _ = range signalChan {
fn()
os.Exit(0)
}
}()
}
-103
View File
@@ -1,103 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/security"
)
var (
uploadReplication *string
uploadCollection *string
uploadDir *string
uploadTtl *string
include *string
uploadSecretKey *string
maxMB *int
)
func init() {
cmdUpload.Run = runUpload // break init cycle
cmdUpload.IsDebug = cmdUpload.Flag.Bool("debug", false, "verbose debug information")
server = cmdUpload.Flag.String("server", "localhost:9333", "weedfs master location")
uploadDir = cmdUpload.Flag.String("dir", "", "Upload the whole folder recursively if specified.")
include = cmdUpload.Flag.String("include", "", "pattens of files to upload, e.g., *.pdf, *.html, ab?d.txt, works together with -dir")
uploadReplication = cmdUpload.Flag.String("replication", "", "replication type")
uploadCollection = cmdUpload.Flag.String("collection", "", "optional collection name")
uploadTtl = cmdUpload.Flag.String("ttl", "", "time to live, e.g.: 1m, 1h, 1d, 1M, 1y")
maxMB = cmdUpload.Flag.Int("maxMB", 0, "split files larger than the limit")
uploadSecretKey = cmdUpload.Flag.String("secure.secret", "", "secret to encrypt Json Web Token(JWT)")
}
var cmdUpload = &Command{
UsageLine: "upload -server=localhost:9333 file1 [file2 file3]\n upload -server=localhost:9333 -dir=one_directory -include=*.pdf",
Short: "upload one or a list of files",
Long: `upload one or a list of files, or batch upload one whole folder recursively.
If uploading a list of files:
It uses consecutive file keys for the list of files.
e.g. If the file1 uses key k, file2 can be read via k_1
If uploading a whole folder recursively:
All files under the folder and subfolders will be uploaded, each with its own file key.
Optional parameter "-include" allows you to specify the file name patterns.
If any file has a ".gz" extension, the content are considered gzipped already, and will be stored as is.
This can save volume server's gzipped processing and allow customizable gzip compression level.
The file name will strip out ".gz" and stored. For example, "jquery.js.gz" will be stored as "jquery.js".
If "maxMB" is set to a positive number, files larger than it would be split into chunks and uploaded separatedly.
The list of file ids of those chunks would be stored in an additional chunk, and this additional chunk's file id would be returned.
`,
}
func runUpload(cmd *Command, args []string) bool {
secret := security.Secret(*uploadSecretKey)
if len(cmdUpload.Flag.Args()) == 0 {
if *uploadDir == "" {
return false
}
filepath.Walk(*uploadDir, func(path string, info os.FileInfo, err error) error {
if err == nil {
if !info.IsDir() {
if *include != "" {
if ok, _ := filepath.Match(*include, filepath.Base(path)); !ok {
return nil
}
}
parts, e := operation.NewFileParts([]string{path})
if e != nil {
return e
}
results, e := operation.SubmitFiles(*server, parts,
*uploadReplication, *uploadCollection,
*uploadTtl, *maxMB, secret)
bytes, _ := json.Marshal(results)
fmt.Println(string(bytes))
if e != nil {
return e
}
}
} else {
fmt.Println(err)
}
return err
})
} else {
parts, e := operation.NewFileParts(args)
if e != nil {
fmt.Println(e.Error())
}
results, _ := operation.SubmitFiles(*server, parts,
*uploadReplication, *uploadCollection,
*uploadTtl, *maxMB, secret)
bytes, _ := json.Marshal(results)
fmt.Println(string(bytes))
}
return true
}
-67
View File
@@ -1,67 +0,0 @@
package weed_server
import (
"net/http"
"strconv"
"github.com/chrislusf/weed-fs/go/filer"
"github.com/chrislusf/weed-fs/go/filer/cassandra_store"
"github.com/chrislusf/weed-fs/go/filer/embedded_filer"
"github.com/chrislusf/weed-fs/go/filer/flat_namespace"
"github.com/chrislusf/weed-fs/go/filer/redis_store"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/security"
)
type FilerServer struct {
port string
master string
collection string
defaultReplication string
redirectOnRead bool
disableDirListing bool
secret security.Secret
filer filer.Filer
}
func NewFilerServer(r *http.ServeMux, port int, master string, dir string, collection string,
replication string, redirectOnRead bool, disableDirListing bool,
secret string,
cassandra_server string, cassandra_keyspace string,
redis_server string, redis_database int,
) (fs *FilerServer, err error) {
fs = &FilerServer{
master: master,
collection: collection,
defaultReplication: replication,
redirectOnRead: redirectOnRead,
disableDirListing: disableDirListing,
port: ":" + strconv.Itoa(port),
}
if cassandra_server != "" {
cassandra_store, err := cassandra_store.NewCassandraStore(cassandra_keyspace, cassandra_server)
if err != nil {
glog.Fatalf("Can not connect to cassandra server %s with keyspace %s: %v", cassandra_server, cassandra_keyspace, err)
}
fs.filer = flat_namespace.NewFlatNamesapceFiler(master, cassandra_store)
} else if redis_server != "" {
redis_store := redis_store.NewRedisStore(redis_server, redis_database)
fs.filer = flat_namespace.NewFlatNamesapceFiler(master, redis_store)
} else {
if fs.filer, err = embedded_filer.NewFilerEmbedded(master, dir); err != nil {
glog.Fatalf("Can not start filer in dir %s : %v", dir, err)
return
}
r.HandleFunc("/admin/mv", fs.moveHandler)
}
r.HandleFunc("/", fs.filerHandler)
return fs, nil
}
func (fs *FilerServer) jwt(fileId string) security.EncodedJwt {
return security.GenJwt(fs.secret, fileId)
}
@@ -1,215 +0,0 @@
package weed_server
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/util"
"github.com/syndtr/goleveldb/leveldb"
)
func (fs *FilerServer) filerHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
fs.GetOrHeadHandler(w, r, true)
case "HEAD":
fs.GetOrHeadHandler(w, r, false)
case "DELETE":
fs.DeleteHandler(w, r)
case "PUT":
fs.PostHandler(w, r)
case "POST":
fs.PostHandler(w, r)
}
}
func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, "/") {
return
}
dirlist, err := fs.filer.ListDirectories(r.URL.Path)
if err == leveldb.ErrNotFound {
glog.V(3).Infoln("Directory Not Found in db", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
m := make(map[string]interface{})
m["Directory"] = r.URL.Path
m["Subdirectories"] = dirlist
lastFileName := r.FormValue("lastFileName")
limit, limit_err := strconv.Atoi(r.FormValue("limit"))
if limit_err != nil {
limit = 100
}
m["Files"], _ = fs.filer.ListFiles(r.URL.Path, lastFileName, limit)
writeJsonQuiet(w, r, http.StatusOK, m)
}
func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, isGetMethod bool) {
if strings.HasSuffix(r.URL.Path, "/") {
if fs.disableDirListing {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
fs.listDirectoryHandler(w, r)
return
}
fileId, err := fs.filer.FindFile(r.URL.Path)
if err == leveldb.ErrNotFound {
glog.V(3).Infoln("Not found in db", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
parts := strings.Split(fileId, ",")
if len(parts) != 2 {
glog.V(1).Infoln("Invalid fileId", fileId)
w.WriteHeader(http.StatusNotFound)
return
}
lookup, lookupError := operation.Lookup(fs.master, parts[0])
if lookupError != nil {
glog.V(1).Infoln("Invalid lookup", lookupError.Error())
w.WriteHeader(http.StatusNotFound)
return
}
if len(lookup.Locations) == 0 {
glog.V(1).Infoln("Can not find location for volume", parts[0])
w.WriteHeader(http.StatusNotFound)
return
}
urlLocation := lookup.Locations[rand.Intn(len(lookup.Locations))].Url
urlString := "http://" + urlLocation + "/" + fileId
if fs.redirectOnRead {
http.Redirect(w, r, urlString, http.StatusFound)
return
}
u, _ := url.Parse(urlString)
request := &http.Request{
Method: r.Method,
URL: u,
Proto: r.Proto,
ProtoMajor: r.ProtoMajor,
ProtoMinor: r.ProtoMinor,
Header: r.Header,
Body: r.Body,
Host: r.Host,
ContentLength: r.ContentLength,
}
glog.V(3).Infoln("retrieving from", u)
resp, do_err := util.Do(request)
if do_err != nil {
glog.V(0).Infoln("failing to connect to volume server", do_err.Error())
writeJsonError(w, r, http.StatusInternalServerError, do_err)
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func (fs *FilerServer) PostHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
replication := query.Get("replication")
if replication == "" {
replication = fs.defaultReplication
}
assignResult, ae := operation.Assign(fs.master, 1, replication, fs.collection, query.Get("ttl"))
if ae != nil {
glog.V(0).Infoln("failing to assign a file id", ae.Error())
writeJsonError(w, r, http.StatusInternalServerError, ae)
return
}
u, _ := url.Parse("http://" + assignResult.Url + "/" + assignResult.Fid)
glog.V(4).Infoln("post to", u)
request := &http.Request{
Method: r.Method,
URL: u,
Proto: r.Proto,
ProtoMajor: r.ProtoMajor,
ProtoMinor: r.ProtoMinor,
Header: r.Header,
Body: r.Body,
Host: r.Host,
ContentLength: r.ContentLength,
}
resp, do_err := util.Do(request)
if do_err != nil {
glog.V(0).Infoln("failing to connect to volume server", r.RequestURI, do_err.Error())
writeJsonError(w, r, http.StatusInternalServerError, do_err)
return
}
defer resp.Body.Close()
resp_body, ra_err := ioutil.ReadAll(resp.Body)
if ra_err != nil {
glog.V(0).Infoln("failing to upload to volume server", r.RequestURI, ra_err.Error())
writeJsonError(w, r, http.StatusInternalServerError, ra_err)
return
}
glog.V(4).Infoln("post result", string(resp_body))
var ret operation.UploadResult
unmarshal_err := json.Unmarshal(resp_body, &ret)
if unmarshal_err != nil {
glog.V(0).Infoln("failing to read upload resonse", r.RequestURI, string(resp_body))
writeJsonError(w, r, http.StatusInternalServerError, unmarshal_err)
return
}
if ret.Error != "" {
glog.V(0).Infoln("failing to post to volume server", r.RequestURI, ret.Error)
writeJsonError(w, r, http.StatusInternalServerError, errors.New(ret.Error))
return
}
path := r.URL.Path
if strings.HasSuffix(path, "/") {
if ret.Name != "" {
path += ret.Name
} else {
operation.DeleteFile(fs.master, assignResult.Fid, fs.jwt(assignResult.Fid)) //clean up
glog.V(0).Infoln("Can not to write to folder", path, "without a file name!")
writeJsonError(w, r, http.StatusInternalServerError,
errors.New("Can not to write to folder "+path+" without a file name"))
return
}
}
glog.V(4).Infoln("saving", path, "=>", assignResult.Fid)
if db_err := fs.filer.CreateFile(path, assignResult.Fid); db_err != nil {
operation.DeleteFile(fs.master, assignResult.Fid, fs.jwt(assignResult.Fid)) //clean up
glog.V(0).Infof("failing to write %s to filer server : %v", path, db_err)
writeJsonError(w, r, http.StatusInternalServerError, db_err)
return
}
w.WriteHeader(http.StatusCreated)
w.Write(resp_body)
}
// curl -X DELETE http://localhost:8888/path/to
// curl -X DELETE http://localhost:8888/path/to?recursive=true
func (fs *FilerServer) DeleteHandler(w http.ResponseWriter, r *http.Request) {
var err error
var fid string
if strings.HasSuffix(r.URL.Path, "/") {
isRecursive := r.FormValue("recursive") == "true"
err = fs.filer.DeleteDirectory(r.URL.Path, isRecursive)
} else {
fid, err = fs.filer.DeleteFile(r.URL.Path)
if err == nil {
err = operation.DeleteFile(fs.master, fid, fs.jwt(fid))
}
}
if err == nil {
writeJsonQuiet(w, r, http.StatusAccepted, map[string]string{"error": ""})
} else {
glog.V(4).Infoln("deleting", r.URL.Path, ":", err.Error())
writeJsonError(w, r, http.StatusInternalServerError, err)
}
}
@@ -1,29 +0,0 @@
package weed_server
import (
"net/http"
"github.com/chrislusf/weed-fs/go/glog"
)
/*
Move a folder or a file, with 4 Use cases:
mv fromDir toNewDir
mv fromDir toOldDir
mv fromFile toDir
mv fromFile toFile
Wildcard is not supported.
*/
func (fs *FilerServer) moveHandler(w http.ResponseWriter, r *http.Request) {
from := r.FormValue("from")
to := r.FormValue("to")
err := fs.filer.Move(from, to)
if err != nil {
glog.V(4).Infoln("moving", from, "->", to, err.Error())
writeJsonError(w, r, http.StatusInternalServerError, err)
} else {
w.WriteHeader(http.StatusOK)
}
}
@@ -1,30 +0,0 @@
package weed_server
import (
"net/http"
"github.com/chrislusf/weed-fs/go/stats"
"github.com/chrislusf/weed-fs/go/util"
ui "github.com/chrislusf/weed-fs/go/weed/weed_server/master_ui"
)
func (ms *MasterServer) uiStatusHandler(w http.ResponseWriter, r *http.Request) {
infos := make(map[string]interface{})
infos["Version"] = util.VERSION
args := struct {
Version string
Topology interface{}
Leader string
Peers interface{}
Stats map[string]interface{}
Counters *stats.ServerStats
}{
util.VERSION,
ms.Topo.ToMap(),
ms.Topo.RaftServer.Leader(),
ms.Topo.RaftServer.Peers(),
infos,
serverStats,
}
ui.StatusTpl.Execute(w, args)
}
-185
View File
@@ -1,185 +0,0 @@
package weed_server
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/topology"
"github.com/goraft/raft"
"github.com/gorilla/mux"
)
type RaftServer struct {
peers []string // initial peers to join with
raftServer raft.Server
dataDir string
httpAddr string
router *mux.Router
topo *topology.Topology
}
func NewRaftServer(r *mux.Router, peers []string, httpAddr string, dataDir string, topo *topology.Topology, pulseSeconds int) *RaftServer {
s := &RaftServer{
peers: peers,
httpAddr: httpAddr,
dataDir: dataDir,
router: r,
topo: topo,
}
if glog.V(4) {
raft.SetLogLevel(2)
}
raft.RegisterCommand(&topology.MaxVolumeIdCommand{})
var err error
transporter := raft.NewHTTPTransporter("/cluster", 0)
transporter.Transport.MaxIdleConnsPerHost = 1024
glog.V(1).Infof("Starting RaftServer with IP:%v:", httpAddr)
// Clear old cluster configurations if peers are set
if len(s.peers) > 0 {
os.RemoveAll(path.Join(s.dataDir, "conf"))
os.RemoveAll(path.Join(s.dataDir, "log"))
os.RemoveAll(path.Join(s.dataDir, "snapshot"))
}
s.raftServer, err = raft.NewServer(s.httpAddr, s.dataDir, transporter, nil, topo, "")
if err != nil {
glog.V(0).Infoln(err)
return nil
}
transporter.Install(s.raftServer, s)
s.raftServer.SetHeartbeatInterval(1 * time.Second)
s.raftServer.SetElectionTimeout(time.Duration(pulseSeconds) * 3450 * time.Millisecond)
s.raftServer.Start()
s.router.HandleFunc("/cluster/join", s.joinHandler).Methods("POST")
s.router.HandleFunc("/cluster/status", s.statusHandler).Methods("GET")
if len(s.peers) > 0 {
// Join to leader if specified.
glog.V(0).Infoln("Joining cluster:", strings.Join(s.peers, ","))
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
firstJoinError := s.Join(s.peers)
if firstJoinError != nil {
glog.V(0).Infoln("No existing server found. Starting as leader in the new cluster.")
_, err := s.raftServer.Do(&raft.DefaultJoinCommand{
Name: s.raftServer.Name(),
ConnectionString: "http://" + s.httpAddr,
})
if err != nil {
glog.V(0).Infoln(err)
return nil
}
}
} else if s.raftServer.IsLogEmpty() {
// Initialize the server by joining itself.
glog.V(0).Infoln("Initializing new cluster")
_, err := s.raftServer.Do(&raft.DefaultJoinCommand{
Name: s.raftServer.Name(),
ConnectionString: "http://" + s.httpAddr,
})
if err != nil {
glog.V(0).Infoln(err)
return nil
}
} else {
glog.V(0).Infoln("Old conf,log,snapshot should have been removed.")
}
return s
}
func (s *RaftServer) Peers() (members []string) {
peers := s.raftServer.Peers()
for _, p := range peers {
members = append(members, strings.TrimPrefix(p.ConnectionString, "http://"))
}
return
}
// Join joins an existing cluster.
func (s *RaftServer) Join(peers []string) error {
command := &raft.DefaultJoinCommand{
Name: s.raftServer.Name(),
ConnectionString: "http://" + s.httpAddr,
}
var err error
var b bytes.Buffer
json.NewEncoder(&b).Encode(command)
for _, m := range peers {
if m == s.httpAddr {
continue
}
target := fmt.Sprintf("http://%s/cluster/join", strings.TrimSpace(m))
glog.V(0).Infoln("Attempting to connect to:", target)
err = postFollowingOneRedirect(target, "application/json", &b)
if err != nil {
glog.V(0).Infoln("Post returned error: ", err.Error())
if _, ok := err.(*url.Error); ok {
// If we receive a network error try the next member
continue
}
} else {
return nil
}
}
return errors.New("Could not connect to any cluster peers")
}
// a workaround because http POST following redirection misses request body
func postFollowingOneRedirect(target string, contentType string, b *bytes.Buffer) error {
backupReader := bytes.NewReader(b.Bytes())
resp, err := http.Post(target, contentType, b)
if err != nil {
return err
}
defer resp.Body.Close()
reply, _ := ioutil.ReadAll(resp.Body)
statusCode := resp.StatusCode
if statusCode == http.StatusMovedPermanently {
var urlStr string
if urlStr = resp.Header.Get("Location"); urlStr == "" {
return fmt.Errorf("%d response missing Location header", resp.StatusCode)
}
glog.V(0).Infoln("Post redirected to ", urlStr)
resp2, err2 := http.Post(urlStr, contentType, backupReader)
if err2 != nil {
return err2
}
defer resp2.Body.Close()
reply, _ = ioutil.ReadAll(resp2.Body)
statusCode = resp2.StatusCode
}
glog.V(0).Infoln("Post returned status: ", statusCode, string(reply))
if statusCode != http.StatusOK {
return errors.New(string(reply))
}
return nil
}
@@ -1,64 +0,0 @@
package weed_server
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/goraft/raft"
)
// Handles incoming RAFT joins.
func (s *RaftServer) joinHandler(w http.ResponseWriter, req *http.Request) {
glog.V(0).Infoln("Processing incoming join. Current Leader", s.raftServer.Leader(), "Self", s.raftServer.Name(), "Peers", s.raftServer.Peers())
command := &raft.DefaultJoinCommand{}
commandText, _ := ioutil.ReadAll(req.Body)
glog.V(0).Info("Command:", string(commandText))
if err := json.NewDecoder(strings.NewReader(string(commandText))).Decode(&command); err != nil {
glog.V(0).Infoln("Error decoding json message:", err, string(commandText))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
glog.V(0).Infoln("join command from Name", command.Name, "Connection", command.ConnectionString)
if _, err := s.raftServer.Do(command); err != nil {
switch err {
case raft.NotLeaderError:
s.redirectToLeader(w, req)
default:
glog.V(0).Infoln("Error processing join:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func (s *RaftServer) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
s.router.HandleFunc(pattern, handler)
}
func (s *RaftServer) redirectToLeader(w http.ResponseWriter, req *http.Request) {
if leader, e := s.topo.Leader(); e == nil {
//http.StatusMovedPermanently does not cause http POST following redirection
glog.V(0).Infoln("Redirecting to", http.StatusMovedPermanently, "http://"+leader+req.URL.Path)
http.Redirect(w, req, "http://"+leader+req.URL.Path, http.StatusMovedPermanently)
} else {
glog.V(0).Infoln("Error: Leader Unknown")
http.Error(w, "Leader unknown", http.StatusInternalServerError)
}
}
func (s *RaftServer) statusHandler(w http.ResponseWriter, r *http.Request) {
ret := operation.ClusterStatusResult{
IsLeader: s.topo.IsLeader(),
Peers: s.Peers(),
}
if leader, e := s.topo.Leader(); e == nil {
ret.Leader = leader
}
writeJsonQuiet(w, r, http.StatusOK, ret)
}
-116
View File
@@ -1,116 +0,0 @@
package weed_server
import (
"math/rand"
"net/http"
"sync"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/security"
"github.com/chrislusf/weed-fs/go/storage"
)
type VolumeServer struct {
masterNode string
mnLock sync.RWMutex
pulseSeconds int
dataCenter string
rack string
store *storage.Store
guard *security.Guard
needleMapKind storage.NeedleMapType
FixJpgOrientation bool
}
func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
port int, publicUrl string,
folders []string, maxCounts []int,
needleMapKind storage.NeedleMapType,
masterNode string, pulseSeconds int,
dataCenter string, rack string,
whiteList []string,
fixJpgOrientation bool) *VolumeServer {
vs := &VolumeServer{
pulseSeconds: pulseSeconds,
dataCenter: dataCenter,
rack: rack,
needleMapKind: needleMapKind,
FixJpgOrientation: fixJpgOrientation,
}
vs.SetMasterNode(masterNode)
vs.store = storage.NewStore(port, ip, publicUrl, folders, maxCounts, vs.needleMapKind)
vs.guard = security.NewGuard(whiteList, "")
adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler)
adminMux.HandleFunc("/status", vs.guard.WhiteList(vs.statusHandler))
adminMux.HandleFunc("/admin/assign_volume", vs.guard.WhiteList(vs.assignVolumeHandler))
adminMux.HandleFunc("/admin/vacuum_volume_check", vs.guard.WhiteList(vs.vacuumVolumeCheckHandler))
adminMux.HandleFunc("/admin/vacuum_volume_compact", vs.guard.WhiteList(vs.vacuumVolumeCompactHandler))
adminMux.HandleFunc("/admin/vacuum_volume_commit", vs.guard.WhiteList(vs.vacuumVolumeCommitHandler))
adminMux.HandleFunc("/admin/delete_collection", vs.guard.WhiteList(vs.deleteCollectionHandler))
adminMux.HandleFunc("/stats/counter", vs.guard.WhiteList(statsCounterHandler))
adminMux.HandleFunc("/stats/memory", vs.guard.WhiteList(statsMemoryHandler))
adminMux.HandleFunc("/stats/disk", vs.guard.WhiteList(vs.statsDiskHandler))
adminMux.HandleFunc("/delete", vs.guard.WhiteList(vs.batchDeleteHandler))
adminMux.HandleFunc("/", vs.privateStoreHandler)
if publicMux != adminMux {
// separated admin and public port
publicMux.HandleFunc("/", vs.publicReadOnlyHandler)
}
go func() {
connected := true
vs.store.SetBootstrapMaster(vs.GetMasterNode())
vs.store.SetDataCenter(vs.dataCenter)
vs.store.SetRack(vs.rack)
for {
master, secretKey, err := vs.store.SendHeartbeatToMaster()
if err == nil {
if !connected {
connected = true
vs.SetMasterNode(master)
vs.guard.SecretKey = secretKey
glog.V(0).Infoln("Volume Server Connected with master at", master)
}
} else {
glog.V(1).Infof("Volume Server Failed to talk with master %+v: %v", vs, err)
if connected {
connected = false
}
}
if connected {
time.Sleep(time.Duration(float32(vs.pulseSeconds*1e3)*(1+rand.Float32())) * time.Millisecond)
} else {
time.Sleep(time.Duration(float32(vs.pulseSeconds*1e3)*0.25) * time.Millisecond)
}
}
}()
return vs
}
func (vs *VolumeServer) GetMasterNode() string {
vs.mnLock.RLock()
defer vs.mnLock.RUnlock()
return vs.masterNode
}
func (vs *VolumeServer) SetMasterNode(masterNode string) {
vs.mnLock.Lock()
defer vs.mnLock.Unlock()
vs.masterNode = masterNode
}
func (vs *VolumeServer) Shutdown() {
glog.V(0).Infoln("Shutting down volume server...")
vs.store.Close()
glog.V(0).Infoln("Shut down successfully!")
}
func (vs *VolumeServer) jwt(fileId string) security.EncodedJwt {
return security.GenJwt(vs.guard.SecretKey, fileId)
}
@@ -1,54 +0,0 @@
package weed_server
import (
"net/http"
"path/filepath"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/stats"
"github.com/chrislusf/weed-fs/go/util"
)
func (vs *VolumeServer) statusHandler(w http.ResponseWriter, r *http.Request) {
m := make(map[string]interface{})
m["Version"] = util.VERSION
m["Volumes"] = vs.store.Status()
writeJsonQuiet(w, r, http.StatusOK, m)
}
func (vs *VolumeServer) assignVolumeHandler(w http.ResponseWriter, r *http.Request) {
err := vs.store.AddVolume(r.FormValue("volume"), r.FormValue("collection"), vs.needleMapKind, r.FormValue("replication"), r.FormValue("ttl"))
if err == nil {
writeJsonQuiet(w, r, http.StatusAccepted, map[string]string{"error": ""})
} else {
writeJsonError(w, r, http.StatusNotAcceptable, err)
}
glog.V(2).Infoln("assign volume =", r.FormValue("volume"), ", collection =", r.FormValue("collection"), ", replication =", r.FormValue("replication"), ", error =", err)
}
func (vs *VolumeServer) deleteCollectionHandler(w http.ResponseWriter, r *http.Request) {
if "benchmark" != r.FormValue("collection") {
glog.V(0).Infoln("deleting collection =", r.FormValue("collection"), "!!!")
return
}
err := vs.store.DeleteCollection(r.FormValue("collection"))
if err == nil {
writeJsonQuiet(w, r, http.StatusOK, map[string]string{"error": ""})
} else {
writeJsonError(w, r, http.StatusInternalServerError, err)
}
glog.V(2).Infoln("deleting collection =", r.FormValue("collection"), ", error =", err)
}
func (vs *VolumeServer) statsDiskHandler(w http.ResponseWriter, r *http.Request) {
m := make(map[string]interface{})
m["Version"] = util.VERSION
var ds []*stats.DiskStatus
for _, loc := range vs.store.Locations {
if dir, e := filepath.Abs(loc.Directory); e == nil {
ds = append(ds, stats.NewDiskStatus(dir))
}
}
m["DiskStatuses"] = ds
writeJsonQuiet(w, r, http.StatusOK, m)
}
@@ -1,211 +0,0 @@
package weed_server
import (
"io"
"mime"
"mime/multipart"
"net/http"
"strconv"
"strings"
"time"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/images"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/storage"
)
var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")
func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
n := new(storage.Needle)
vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
volumeId, err := storage.NewVolumeId(vid)
if err != nil {
glog.V(2).Infoln("parsing error:", err, r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
return
}
err = n.ParsePath(fid)
if err != nil {
glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
return
}
glog.V(4).Infoln("volume", volumeId, "reading", n)
if !vs.store.HasVolume(volumeId) {
lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
if err == nil && len(lookupResult.Locations) > 0 {
http.Redirect(w, r, "http://"+lookupResult.Locations[0].Url+r.URL.Path, http.StatusMovedPermanently)
} else {
glog.V(2).Infoln("lookup error:", err, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
return
}
cookie := n.Cookie
count, e := vs.store.Read(volumeId, n)
glog.V(4).Infoln("read bytes", count, "error", e)
if e != nil || count <= 0 {
glog.V(0).Infoln("read error:", e, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
if n.Cookie != cookie {
glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
w.WriteHeader(http.StatusNotFound)
return
}
if n.LastModified != 0 {
w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
if r.Header.Get("If-Modified-Since") != "" {
if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
if t.Unix() >= int64(n.LastModified) {
w.WriteHeader(http.StatusNotModified)
return
}
}
}
}
etag := n.Etag()
if inm := r.Header.Get("If-None-Match"); inm == etag {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Set("Etag", etag)
if n.NameSize > 0 && filename == "" {
filename = string(n.Name)
dotIndex := strings.LastIndex(filename, ".")
if dotIndex > 0 {
ext = filename[dotIndex:]
}
}
mtype := ""
if ext != "" {
mtype = mime.TypeByExtension(ext)
}
if n.MimeSize > 0 {
mt := string(n.Mime)
if mt != "application/octet-stream" {
mtype = mt
}
}
if mtype != "" {
w.Header().Set("Content-Type", mtype)
}
if filename != "" {
w.Header().Set("Content-Disposition", "filename=\""+fileNameEscaper.Replace(filename)+"\"")
}
if ext != ".gz" {
if n.IsGzipped() {
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
} else {
if n.Data, err = storage.UnGzipData(n.Data); err != nil {
glog.V(0).Infoln("lookup error:", err, r.URL.Path)
}
}
}
}
if ext == ".png" || ext == ".jpg" || ext == ".gif" {
width, height := 0, 0
if r.FormValue("width") != "" {
width, _ = strconv.Atoi(r.FormValue("width"))
}
if r.FormValue("height") != "" {
height, _ = strconv.Atoi(r.FormValue("height"))
}
n.Data, _, _ = images.Resized(ext, n.Data, width, height)
}
w.Header().Set("Accept-Ranges", "bytes")
if r.Method == "HEAD" {
w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
return
}
rangeReq := r.Header.Get("Range")
if rangeReq == "" {
w.Header().Set("Content-Length", strconv.Itoa(len(n.Data)))
if _, e = w.Write(n.Data); e != nil {
glog.V(0).Infoln("response write error:", e)
}
return
}
//the rest is dealing with partial content request
//mostly copy from src/pkg/net/http/fs.go
size := int64(len(n.Data))
ranges, err := parseRange(rangeReq, size)
if err != nil {
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
return
}
if sumRangesSize(ranges) > size {
// The total number of bytes in all the ranges
// is larger than the size of the file by
// itself, so this is probably an attack, or a
// dumb client. Ignore the range request.
ranges = nil
return
}
if len(ranges) == 0 {
return
}
if len(ranges) == 1 {
// RFC 2616, Section 14.16:
// "When an HTTP message includes the content of a single
// range (for example, a response to a request for a
// single range, or to a request for a set of ranges
// that overlap without any holes), this content is
// transmitted with a Content-Range header, and a
// Content-Length header showing the number of bytes
// actually transferred.
// ...
// A response to a request for a single range MUST NOT
// be sent using the multipart/byteranges media type."
ra := ranges[0]
w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
w.Header().Set("Content-Range", ra.contentRange(size))
w.WriteHeader(http.StatusPartialContent)
if _, e = w.Write(n.Data[ra.start : ra.start+ra.length]); e != nil {
glog.V(0).Infoln("response write error:", e)
}
return
}
// process mulitple ranges
for _, ra := range ranges {
if ra.start > size {
http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
return
}
}
sendSize := rangesMIMESize(ranges, mtype, size)
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
sendContent := pr
defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
go func() {
for _, ra := range ranges {
part, err := mw.CreatePart(ra.mimeHeader(mtype, size))
if err != nil {
pw.CloseWithError(err)
return
}
if _, err = part.Write(n.Data[ra.start : ra.start+ra.length]); err != nil {
pw.CloseWithError(err)
return
}
}
mw.Close()
pw.Close()
}()
if w.Header().Get("Content-Encoding") == "" {
w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
}
w.WriteHeader(http.StatusPartialContent)
io.CopyN(w, sendContent, sendSize)
}
@@ -1,35 +0,0 @@
package weed_server
import (
"net/http"
"github.com/chrislusf/weed-fs/go/glog"
)
func (vs *VolumeServer) vacuumVolumeCheckHandler(w http.ResponseWriter, r *http.Request) {
err, ret := vs.store.CheckCompactVolume(r.FormValue("volume"), r.FormValue("garbageThreshold"))
if err == nil {
writeJsonQuiet(w, r, http.StatusOK, map[string]interface{}{"error": "", "result": ret})
} else {
writeJsonQuiet(w, r, http.StatusInternalServerError, map[string]interface{}{"error": err.Error(), "result": false})
}
glog.V(2).Infoln("checked compacting volume =", r.FormValue("volume"), "garbageThreshold =", r.FormValue("garbageThreshold"), "vacuum =", ret)
}
func (vs *VolumeServer) vacuumVolumeCompactHandler(w http.ResponseWriter, r *http.Request) {
err := vs.store.CompactVolume(r.FormValue("volume"))
if err == nil {
writeJsonQuiet(w, r, http.StatusOK, map[string]string{"error": ""})
} else {
writeJsonError(w, r, http.StatusInternalServerError, err)
}
glog.V(2).Infoln("compacted volume =", r.FormValue("volume"), ", error =", err)
}
func (vs *VolumeServer) vacuumVolumeCommitHandler(w http.ResponseWriter, r *http.Request) {
err := vs.store.CommitCompactVolume(r.FormValue("volume"))
if err == nil {
writeJsonQuiet(w, r, http.StatusOK, map[string]string{"error": ""})
} else {
writeJsonError(w, r, http.StatusInternalServerError, err)
}
glog.V(2).Infoln("commit compact volume =", r.FormValue("volume"), ", error =", err)
}
@@ -1,114 +0,0 @@
package weed_server
import (
"errors"
"net/http"
"github.com/chrislusf/weed-fs/go/glog"
"github.com/chrislusf/weed-fs/go/operation"
"github.com/chrislusf/weed-fs/go/storage"
"github.com/chrislusf/weed-fs/go/topology"
)
func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
if e := r.ParseForm(); e != nil {
glog.V(0).Infoln("form parse error:", e)
writeJsonError(w, r, http.StatusBadRequest, e)
return
}
vid, _, _, _, _ := parseURLPath(r.URL.Path)
volumeId, ve := storage.NewVolumeId(vid)
if ve != nil {
glog.V(0).Infoln("NewVolumeId error:", ve)
writeJsonError(w, r, http.StatusBadRequest, ve)
return
}
needle, ne := storage.NewNeedle(r, vs.FixJpgOrientation)
if ne != nil {
writeJsonError(w, r, http.StatusBadRequest, ne)
return
}
ret := operation.UploadResult{}
size, errorStatus := topology.ReplicatedWrite(vs.GetMasterNode(),
vs.store, volumeId, needle, r)
httpStatus := http.StatusCreated
if errorStatus != "" {
httpStatus = http.StatusInternalServerError
ret.Error = errorStatus
}
if needle.HasName() {
ret.Name = string(needle.Name)
}
ret.Size = size
writeJsonQuiet(w, r, httpStatus, ret)
}
func (vs *VolumeServer) DeleteHandler(w http.ResponseWriter, r *http.Request) {
n := new(storage.Needle)
vid, fid, _, _, _ := parseURLPath(r.URL.Path)
volumeId, _ := storage.NewVolumeId(vid)
n.ParsePath(fid)
glog.V(2).Infoln("deleting", n)
cookie := n.Cookie
count, ok := vs.store.Read(volumeId, n)
if ok != nil {
m := make(map[string]uint32)
m["size"] = 0
writeJsonQuiet(w, r, http.StatusNotFound, m)
return
}
if n.Cookie != cookie {
glog.V(0).Infoln("delete", r.URL.Path, "with unmaching cookie from ", r.RemoteAddr, "agent", r.UserAgent())
return
}
ret := topology.ReplicatedDelete(vs.GetMasterNode(), vs.store, volumeId, n, r)
if ret != 0 {
m := make(map[string]uint32)
m["size"] = uint32(count)
writeJsonQuiet(w, r, http.StatusAccepted, m)
} else {
writeJsonError(w, r, http.StatusInternalServerError, errors.New("Deletion Failed."))
}
}
//Experts only: takes multiple fid parameters. This function does not propagate deletes to replicas.
func (vs *VolumeServer) batchDeleteHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
var ret []operation.DeleteResult
for _, fid := range r.Form["fid"] {
vid, id_cookie, err := operation.ParseFileId(fid)
if err != nil {
ret = append(ret, operation.DeleteResult{Fid: fid, Error: err.Error()})
continue
}
n := new(storage.Needle)
volumeId, _ := storage.NewVolumeId(vid)
n.ParsePath(id_cookie)
glog.V(4).Infoln("batch deleting", n)
cookie := n.Cookie
if _, err := vs.store.Read(volumeId, n); err != nil {
ret = append(ret, operation.DeleteResult{Fid: fid, Error: err.Error()})
continue
}
if n.Cookie != cookie {
ret = append(ret, operation.DeleteResult{Fid: fid, Error: "File Random Cookie does not match."})
glog.V(0).Infoln("deleting", fid, "with unmaching cookie from ", r.RemoteAddr, "agent", r.UserAgent())
return
}
if size, err := vs.store.Delete(volumeId, n); err != nil {
ret = append(ret, operation.DeleteResult{Fid: fid, Error: err.Error()})
} else {
ret = append(ret, operation.DeleteResult{Fid: fid, Size: int(size)})
}
}
writeJsonQuiet(w, r, http.StatusAccepted, ret)
}
-279
View File
@@ -1,279 +0,0 @@
64 32G volumes consumes 10G memory
Each volume has 25M index, so each cost 160MB memory
Things happened when I use lots of threads ( almost 120 ) keeping read file from Weed-FS.
But I'm not so familiar with linux so I can't tell you exactly what happened.
Next I'll show you things I know , if you need more info , contact me
My weed-fs version is about 0.12
1. top
top - 12:07:37 up 1 day, 3:17, 2 users, load average: 0.00, 0.00, 0.00
Tasks: 152 total, 1 running, 151 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 99.8%id, 0.1%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 16269880k total, 16192364k used, 77516k free, 58172k buffers
Swap: 2064376k total, 12324k used, 2052052k free, 2827520k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1499 root 20 0 11.6g 10g 1424 S 0.0 65.6 7:32.53 weedvolume
1498 root 20 0 3204m 2.1g 1428 S 0.0 13.5 4:36.59 weedvolume
1737 root 20 0 98868 4932 2920 S 0.0 0.0 0:00.56 sshd
1497 root 20 0 151m 4404 1152 S 0.0 0.0 1:21.40 weedmaster
1335 root 20 0 97816 3044 2896 S 0.0 0.0 0:00.76 sshd
After system became steady , weedvolume used 65.6% memory .
2. free -m
total used free shared buffers cached
Mem: 15888 15809 79 0 56 2758
-/+ buffers/cache: 12994 2894
Swap: 2015 12 2003
3. startup cmd
screen -d -m /opt/weed/weedmaster -mdir /data/weeddata/ > /data/logs/weed/master.log &
screen -d -m /opt/weed/weedvolume -volumes=0-64 -dir /data/weeddata/ -pulseSeconds=20 -publicUrl="x.y.z:9334" -port 9334 > /data/logs/weed/s01.log &
screen -d -m /opt/weed/weedvolume -volumes=65-107 -dir /data/weeddata/ -pulseSeconds=20 -publicUrl="x.y.z:9335" -port 9335 > /data/logs/weed/s02.log &
4. du -sh .
32G 0.dat
26M 0.idx
8.2G 100.dat
6.8M 100.idx
8.2G 101.dat
6.9M 101.idx
8.2G 102.dat
6.8M 102.idx
8.2G 103.dat
6.8M 103.idx
8.2G 104.dat
6.8M 104.idx
8.2G 105.dat
6.9M 105.idx
8.2G 106.dat
6.9M 106.idx
8.2G 107.dat
6.9M 107.idx
32G 10.dat
25M 10.idx
32G 11.dat
25M 11.idx
32G 12.dat
25M 12.idx
32G 13.dat
25M 13.idx
32G 14.dat
25M 14.idx
32G 15.dat
25M 15.idx
32G 16.dat
25M 16.idx
32G 17.dat
25M 17.idx
32G 18.dat
25M 18.idx
32G 19.dat
25M 19.idx
32G 1.dat
26M 1.idx
32G 20.dat
25M 20.idx
32G 21.dat
25M 21.idx
32G 22.dat
25M 22.idx
32G 23.dat
25M 23.idx
32G 24.dat
25M 24.idx
32G 25.dat
25M 25.idx
32G 26.dat
25M 26.idx
32G 27.dat
25M 27.idx
32G 28.dat
25M 28.idx
32G 29.dat
25M 29.idx
32G 2.dat
26M 2.idx
32G 30.dat
25M 30.idx
32G 31.dat
25M 31.idx
32G 32.dat
25M 32.idx
32G 33.dat
25M 33.idx
32G 34.dat
25M 34.idx
32G 35.dat
25M 35.idx
32G 36.dat
25M 36.idx
32G 37.dat
25M 37.idx
32G 38.dat
25M 38.idx
32G 39.dat
25M 39.idx
32G 3.dat
26M 3.idx
32G 40.dat
25M 40.idx
32G 41.dat
25M 41.idx
32G 42.dat
25M 42.idx
32G 43.dat
25M 43.idx
32G 44.dat
25M 44.idx
32G 45.dat
25M 45.idx
32G 46.dat
25M 46.idx
32G 47.dat
25M 47.idx
32G 48.dat
25M 48.idx
32G 49.dat
25M 49.idx
32G 4.dat
26M 4.idx
32G 50.dat
25M 50.idx
32G 51.dat
25M 51.idx
32G 52.dat
25M 52.idx
32G 53.dat
25M 53.idx
32G 54.dat
25M 54.idx
32G 55.dat
25M 55.idx
32G 56.dat
25M 56.idx
32G 57.dat
25M 57.idx
32G 58.dat
25M 58.idx
32G 59.dat
25M 59.idx
32G 5.dat
26M 5.idx
32G 60.dat
25M 60.idx
32G 61.dat
25M 61.idx
32G 62.dat
25M 62.idx
32G 63.dat
25M 63.idx
32G 64.dat
25M 64.idx
8.2G 65.dat
6.9M 65.idx
8.2G 66.dat
6.9M 66.idx
8.2G 67.dat
6.9M 67.idx
8.2G 68.dat
6.8M 68.idx
8.2G 69.dat
6.9M 69.idx
32G 6.dat
25M 6.idx
8.2G 70.dat
6.8M 70.idx
8.2G 71.dat
6.9M 71.idx
8.2G 72.dat
6.9M 72.idx
8.2G 73.dat
6.9M 73.idx
8.2G 74.dat
6.9M 74.idx
8.2G 75.dat
6.9M 75.idx
8.1G 76.dat
6.8M 76.idx
8.2G 77.dat
6.8M 77.idx
8.2G 78.dat
6.8M 78.idx
8.1G 79.dat
6.8M 79.idx
32G 7.dat
25M 7.idx
8.2G 80.dat
6.8M 80.idx
8.2G 81.dat
6.9M 81.idx
8.2G 82.dat
6.9M 82.idx
8.2G 83.dat
6.9M 83.idx
8.2G 84.dat
6.9M 84.idx
8.2G 85.dat
6.8M 85.idx
8.2G 86.dat
6.9M 86.idx
8.2G 87.dat
6.9M 87.idx
8.2G 88.dat
6.9M 88.idx
8.2G 89.dat
6.8M 89.idx
32G 8.dat
25M 8.idx
8.2G 90.dat
6.9M 90.idx
8.1G 91.dat
6.8M 91.idx
8.1G 92.dat
6.8M 92.idx
8.1G 93.dat
6.8M 93.idx
8.2G 94.dat
6.9M 94.idx
8.2G 95.dat
6.9M 95.idx
8.2G 96.dat
6.9M 96.idx
8.2G 97.dat
6.9M 97.idx
8.2G 98.dat
6.9M 98.idx
8.2G 99.dat
6.9M 99.idx
32G 9.dat
25M 9.idx
4.0K directory.seq
You can see the volume 1-64 is now full.
5. more log
see logs.zip
In messages you can see these lines: (Line 51095)
Sep 26 06:14:31 wedb-01 kernel: auditd: page allocation failure. order:0, mode:0x20
Sep 26 06:14:31 wedb-01 kernel: Pid: 1009, comm: auditd Not tainted 2.6.32-220.el6.x86_64 #1
Sep 26 06:14:31 wedb-01 kernel: Call Trace:
After those lines , the system deny any new network connect request
6. /dir/status
{"Machines":[{"Server":{"Url":"127.0.0.1:9335","PublicUrl":"x.y.z:9335"},"Volumes":[{"Id":106,"Size":8728909632},{"Id":66,"Size":8729852744},{"Id":90,"Size":8747834896},{"Id":103,"Size":8718106024},{"Id":87,"Size":8732133512},{"Id":96,"Size":8737251904},{"Id":80,"Size":8704130712},{"Id":77,"Size":8717989496},{"Id":70,"Size":8731474744},{"Id":94,"Size":8758656144},{"Id":107,"Size":8729599232},{"Id":67,"Size":8736848088},{"Id":91,"Size":8665847760},{"Id":100,"Size":8703272552},{"Id":84,"Size":8745121528},{"Id":97,"Size":8713031744},{"Id":81,"Size":8726088872},{"Id":74,"Size":8738588152},{"Id":71,"Size":8729349920},{"Id":95,"Size":8741526896},{"Id":104,"Size":8699374736},{"Id":88,"Size":8740362880},{"Id":101,"Size":8711832992},{"Id":85,"Size":8723479552},{"Id":78,"Size":8700345400},{"Id":75,"Size":8727796912},{"Id":68,"Size":8698607440},{"Id":92,"Size":8682683056},{"Id":105,"Size":8741226152},{"Id":65,"Size":8725365752},{"Id":89,"Size":8703062600},{"Id":98,"Size":8742331560},{"Id":82,"Size":8762554952},{"Id":79,"Size":8696300376},{"Id":72,"Size":8708217304},{"Id":69,"Size":8740268144},{"Id":93,"Size":8685060320},{"Id":102,"Size":8708695352},{"Id":86,"Size":8783247776},{"Id":99,"Size":8753463608},{"Id":83,"Size":8725963952},{"Id":76,"Size":8694693536},{"Id":73,"Size":8733560832}]},{"Server":{"Url":"127.0.0.1:9334","PublicUrl":"x.y.z:9334"},"Volumes":[{"Id":34,"Size":33415706800},{"Id":58,"Size":33569224784},{"Id":18,"Size":33474649968},{"Id":55,"Size":33542422680},{"Id":15,"Size":33517247576},{"Id":48,"Size":33574860328},{"Id":8,"Size":33511257144},{"Id":45,"Size":33463948408},{"Id":5,"Size":34317702920},{"Id":29,"Size":33465695776},{"Id":38,"Size":33553119624},{"Id":62,"Size":33448316736},{"Id":22,"Size":33566586296},{"Id":35,"Size":33493733728},{"Id":59,"Size":33498554904},{"Id":19,"Size":33493313784},{"Id":52,"Size":33552978448},{"Id":12,"Size":33505183752},{"Id":49,"Size":33603029896},{"Id":9,"Size":33515778064},{"Id":42,"Size":33500402248},{"Id":2,"Size":34223232992},{"Id":26,"Size":33526519600},{"Id":39,"Size":33580414336},{"Id":63,"Size":33476332456},{"Id":23,"Size":33543872592},{"Id":32,"Size":33515290168},{"Id":56,"Size":33499171184},{"Id":16,"Size":33556591168},{"Id":64,"Size":33495148616},{"Id":53,"Size":33467738560},{"Id":13,"Size":33596873960},{"Id":46,"Size":33508120448},{"Id":6,"Size":33417470256},{"Id":30,"Size":33532933992},{"Id":43,"Size":33591802008},{"Id":3,"Size":34270682080},{"Id":27,"Size":33525736944},{"Id":36,"Size":33443597824},{"Id":60,"Size":33427931336},{"Id":20,"Size":33499083096},{"Id":33,"Size":33531396280},{"Id":57,"Size":33578015104},{"Id":17,"Size":33510525480},{"Id":50,"Size":33503123704},{"Id":10,"Size":33502391608},{"Id":47,"Size":33521868568},{"Id":7,"Size":33497101664},{"Id":31,"Size":33426905232},{"Id":40,"Size":33472978696},{"Id":0,"Size":34337344304},{"Id":24,"Size":33550157192},{"Id":37,"Size":33477162720},{"Id":61,"Size":33537175080},{"Id":21,"Size":33517192456},{"Id":54,"Size":33480720288},{"Id":14,"Size":33513192896},{"Id":51,"Size":33531336080},{"Id":11,"Size":33562385088},{"Id":44,"Size":33554479104},{"Id":4,"Size":34333127520},{"Id":28,"Size":33510503000},{"Id":41,"Size":33574922928},{"Id":1,"Size":34307181368},{"Id":25,"Size":33542834568}]}],"Writers":[106,66,90,103,87,96,80,77,70,94,107,67,91,100,84,97,81,74,71,95,104,88,101,85,78,75,68,92,105,65,89,98,82,79,72,69,93,102,86,99,83,76,73,34,58,18,55,15,48,8,45,5,29,38,62,22,35,59,19,52,12,49,9,42,2,26,39,63,23,32,56,16,64,53,13,46,6,30,43,3,27,36,60,20,33,57,17,50,10,47,7,31,40,0,24,37,61,21,54,14,51,11,44,4,28,41,1,25],"FileIdSequence":110250000}
-109
View File
@@ -1,109 +0,0 @@
1. each file can choose the replication factor
2. replication granularity is in volume level
3. if not enough spaces, we can automatically decrease some volume's the replication factor, especially for cold data
4. plan to support migrating data to cheaper storage
5. plan to manual volume placement, access-based volume placement, auction based volume placement
When a new volume server is started, it reports
1. how many volumes it can hold
2. current list of existing volumes and each volume's replication type
Each volume server remembers:
1. current volume ids
2. replica locations are read from the master
The master assign volume ids based on
1. replication factor
data center, rack
2. concurrent write support
On master, stores the replication configuration
{
replication:{
{type:"00", min_volume_count:3, weight:10},
{type:"01", min_volume_count:2, weight:20},
{type:"10", min_volume_count:2, weight:20},
{type:"11", min_volume_count:3, weight:30},
{type:"20", min_volume_count:2, weight:20}
},
port:9333,
}
Or manually via command line
1. add volume with specified replication factor
2. add volume with specified volume id
If duplicated volume ids are reported from different volume servers,
the master determines the replication factor of the volume,
if less than the replication factor, the volume is in readonly mode
if more than the replication factor, the volume will purge the smallest/oldest volume
if equal, the volume will function as usual
Use cases:
on volume server
1. weed volume -mserver="xx.xx.xx.xx:9333" -publicUrl="good.com:8080" -dir="/tmp" -volumes=50
on weed master
1. weed master -port=9333
generate a default json configuration file if doesn't exist
Bootstrap
1. at the very beginning, the system has no volumes at all.
When data node starts:
1. each data node send to master its existing volumes and max volume blocks
2. master remembers the topology/data_center/rack/data_node/volumes
for each replication level, stores
volume id ~ data node
writable volume ids
If any "assign" request comes in
1. find a writable volume with the right replicationLevel
2. if not found, grow the volumes with the right replication level
3. return a writable volume to the user
for data node:
0. detect existing volumes DONE
1. onStartUp, and periodically, send existing volumes and maxVolumeCount store.Join(), DONE
2. accept command to grow a volume( id + replication level) DONE
/admin/assign_volume?volume=some_id&replicationType=01
3. accept setting volumeLocationList DONE
/admin/set_volume_locations_list?volumeLocationsList=[{Vid:xxx,Locations:[loc1,loc2,loc3]}]
4. for each write, pass the write to the next location, (Step 2)
POST method should accept an index, like ttl, get decremented every hop
for master:
1. accept data node's report of existing volumes and maxVolumeCount ALREADY EXISTS /dir/join
2. periodically refresh for active data nodes, and adjust writable volumes
3. send command to grow a volume(id + replication level) DONE
5. accept lookup for volume locations ALREADY EXISTS /dir/lookup
6. read topology/datacenter/rack layout
An algorithm to allocate volumes evenly, but may be inefficient if free volumes are plenty:
input: replication=xyz
algorithm:
ret_dcs = []
foreach dc that has y+z+1 volumes{
ret_racks = []
foreach rack with z+1 volumes{
ret = select z+1 servers with 1 volume
if ret.size()==z+1 {
ret_racks.append(ret)
}
}
randomly pick one rack from ret_racks
ret += select y racks with 1 volume each
if ret.size()==y+z+1{
ret_dcs.append(ret)
}
}
randomly pick one dc from ret_dcs
ret += select x data centers with 1 volume each
A simple replica placement algorithm, but may fail when free volume slots are not plenty:
ret := []volumes
dc = randomly pick 1 data center with y+z+1 volumes
rack = randomly pick 1 rack with z+1 volumes
ret = ret.append(randomly pick z+1 volumes)
ret = ret.append(randomly pick y racks with 1 volume)
ret = ret.append(randomly pick x data centers with 1 volume)
TODO:
1. replicate content to the other server if the replication type needs replicas
-51
View File
@@ -1,51 +0,0 @@
Design for Seaweed-FS security
Design Objectives
Security can mean many different things. The original vision is that: if you have one machine lying around
somewhere with some disk space, it should be able to join your file system to contribute some disk space and
network bandwidth, securely!
To achieve this purpose, the security should be able to:
1. Secure the inter-server communication. Only real cluster servers can join and communicate.
2. allow clients to securely write to volume servers
Non Objective
Multi-tenant support. Avoid filers or clients cross-updating files.
User specific access control.
Design Architect
master, and volume servers all talk securely via 2-way SSL for admin operations.
upon joining, master gives its secret key to volume servers.
filer or clients talk to master to get secret key, and use the key to generate JWT to write on volume server.
A side benefit:
a time limited read feature?
4. volume server needs to expose https ports
HTTP Connections
clear http
filer~>master, need to get a JWT from master
filer~>volume
2-way https
master~ssl~>volume
volume~ssl~>master
file uploading:
when volume server starts, it asks master for the secret key to decode JWT
when filer/clients wants to upload, master generate a JWT
filer~>volume(public port)
master~>volume(public port)
Currently, volume server has 2 ip addresses: ip and publicUrl.
The ip is for admin purpose, and master talk to volume server this way.
The publicUrl is for clients to access the server, via http GET/POST/DELETE etc.
The write operations are secured by JWT.
clients talk to master also via https? possible. Decide on this later.
Dev plan:
1. volume server separate admin from public GET/POST/DELETE handlers
The step 1 may be good enough for most use cases.
If 2-way ssl are still needed
2. volume server add ssl support
3. https connections to operate on volume servers
-46
View File
@@ -1,46 +0,0 @@
How to submit a content
1. Find physical volumes
1.c Create a hash value
1.d find a write logic volume id, and return [logic volume id, {physical volume ids}]
2. submit to physical volumes
2.c
generate the cookie
generate a unique id as key
choose the right altKey
send bytes to physical volumes
2.s each
save bytes
store map[key uint64, altKey uint32]<offset, size>
for updated entry, set old entry's offset to zero
3.c
wait for all physical volumes to finish
store the /<logic volume id>/<key>_<cookie>_<altKey>.<ext>
How to retrieve a content
1.c
send logic volume id
1.d
find least busy volume's id
2.c
send URI /<physical volume id>/<key>_<cookie>_<altKey>.<ext>
How to submit a content
1. send bytes to weedfs, got <volume id, key uint64, cookie code>
store <key uint64, volume id uint32, cookie code uint32, ext>, and other information
To read a content
2. use logic volume id to lookup a <machine id>
render url as /<machine id>/<volume id>/<key>/<cookie>.ext
The directory server
0.init
load and collect <logic volume id, machine ids> mapping
1.on submit content
find a free logic volume id, start sending content to 3 machines
if all of them finishes, return <logic volume id, key, cookie code>
2.on read content
based on logic volume id, pick a machine with less load,
return <machine id>
+142
View File
@@ -0,0 +1,142 @@
<?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>com.github.chrislusf</groupId>
<artifactId>seaweedfs-client</artifactId>
<version>1.0.2</version>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>
<properties>
<protobuf.version>3.5.1</protobuf.version>
<grpc.version>1.16.1</grpc.version>
<guava.version>26.0-jre</guava.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,207 @@
package seaweedfs.client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class FilerClient {
private static final Logger LOG = LoggerFactory.getLogger(FilerClient.class);
private FilerGrpcClient filerGrpcClient;
public FilerClient(String host, int grpcPort) {
filerGrpcClient = new FilerGrpcClient(host, grpcPort);
}
public FilerClient(FilerGrpcClient filerGrpcClient) {
this.filerGrpcClient = filerGrpcClient;
}
public boolean mkdirs(String path, int mode) {
String currentUser = System.getProperty("user.name");
return mkdirs(path, mode, 0, 0, currentUser, new String[]{});
}
public boolean mkdirs(String path, int mode, String userName, String[] groupNames) {
return mkdirs(path, mode, 0, 0, userName, groupNames);
}
public boolean mkdirs(String path, int mode, int uid, int gid, String userName, String[] groupNames) {
Path pathObject = Paths.get(path);
String parent = pathObject.getParent().toString();
String name = pathObject.getFileName().toString();
if ("/".equals(parent)) {
return true;
}
mkdirs(parent, mode, uid, gid, userName, groupNames);
FilerProto.Entry existingEntry = lookupEntry(parent, name);
if (existingEntry != null) {
return true;
}
return createEntry(
parent,
newDirectoryEntry(name, mode, uid, gid, userName, groupNames).build()
);
}
public boolean rm(String path, boolean isRecursive) {
Path pathObject = Paths.get(path);
String parent = pathObject.getParent().toString();
String name = pathObject.getFileName().toString();
return deleteEntry(
parent,
name,
true,
isRecursive);
}
public boolean touch(String path, int mode) {
String currentUser = System.getProperty("user.name");
return touch(path, mode, 0, 0, currentUser, new String[]{});
}
public boolean touch(String path, int mode, int uid, int gid, String userName, String[] groupNames) {
Path pathObject = Paths.get(path);
String parent = pathObject.getParent().toString();
String name = pathObject.getFileName().toString();
FilerProto.Entry entry = lookupEntry(parent, name);
if (entry == null) {
return createEntry(
parent,
newFileEntry(name, mode, uid, gid, userName, groupNames).build()
);
}
long now = System.currentTimeMillis() / 1000L;
FilerProto.FuseAttributes.Builder attr = entry.getAttributes().toBuilder()
.setMtime(now)
.setUid(uid)
.setGid(gid)
.setUserName(userName)
.clearGroupName()
.addAllGroupName(Arrays.asList(groupNames));
return updateEntry(parent, entry.toBuilder().setAttributes(attr).build());
}
public FilerProto.Entry.Builder newDirectoryEntry(String name, int mode,
int uid, int gid, String userName, String[] groupNames) {
long now = System.currentTimeMillis() / 1000L;
return FilerProto.Entry.newBuilder()
.setName(name)
.setIsDirectory(true)
.setAttributes(FilerProto.FuseAttributes.newBuilder()
.setMtime(now)
.setCrtime(now)
.setUid(uid)
.setGid(gid)
.setFileMode(mode | 1 << 31)
.setUserName(userName)
.clearGroupName()
.addAllGroupName(Arrays.asList(groupNames)));
}
public FilerProto.Entry.Builder newFileEntry(String name, int mode,
int uid, int gid, String userName, String[] groupNames) {
long now = System.currentTimeMillis() / 1000L;
return FilerProto.Entry.newBuilder()
.setName(name)
.setIsDirectory(false)
.setAttributes(FilerProto.FuseAttributes.newBuilder()
.setMtime(now)
.setCrtime(now)
.setUid(uid)
.setGid(gid)
.setFileMode(mode)
.setUserName(userName)
.clearGroupName()
.addAllGroupName(Arrays.asList(groupNames)));
}
public List<FilerProto.Entry> listEntries(String path) {
return listEntries(path, "", "", 100000);
}
public List<FilerProto.Entry> listEntries(String path, String entryPrefix, String lastEntryName, int limit) {
return filerGrpcClient.getBlockingStub().listEntries(FilerProto.ListEntriesRequest.newBuilder()
.setDirectory(path)
.setPrefix(entryPrefix)
.setStartFromFileName(lastEntryName)
.setLimit(limit)
.build()).getEntriesList();
}
public FilerProto.Entry lookupEntry(String directory, String entryName) {
try {
return filerGrpcClient.getBlockingStub().lookupDirectoryEntry(
FilerProto.LookupDirectoryEntryRequest.newBuilder()
.setDirectory(directory)
.setName(entryName)
.build()).getEntry();
} catch (Exception e) {
LOG.warn("lookupEntry {}/{}: {}", directory, entryName, e);
return null;
}
}
public boolean createEntry(String parent, FilerProto.Entry entry) {
try {
filerGrpcClient.getBlockingStub().createEntry(FilerProto.CreateEntryRequest.newBuilder()
.setDirectory(parent)
.setEntry(entry)
.build());
} catch (Exception e) {
LOG.warn("createEntry {}/{}: {}", parent, entry.getName(), e);
return false;
}
return true;
}
public boolean updateEntry(String parent, FilerProto.Entry entry) {
try {
filerGrpcClient.getBlockingStub().updateEntry(FilerProto.UpdateEntryRequest.newBuilder()
.setDirectory(parent)
.setEntry(entry)
.build());
} catch (Exception e) {
LOG.warn("createEntry {}/{}: {}", parent, entry.getName(), e);
return false;
}
return true;
}
public boolean deleteEntry(String parent, String entryName, boolean isDeleteFileChunk, boolean isRecursive) {
try {
filerGrpcClient.getBlockingStub().deleteEntry(FilerProto.DeleteEntryRequest.newBuilder()
.setDirectory(parent)
.setName(entryName)
.setIsDeleteData(isDeleteFileChunk)
.setIsRecursive(isRecursive)
.build());
} catch (Exception e) {
LOG.warn("deleteEntry {}/{}: {}", parent, entryName, e);
return false;
}
return true;
}
}
@@ -0,0 +1,45 @@
package seaweedfs.client;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class FilerGrpcClient {
private static final Logger logger = Logger.getLogger(FilerGrpcClient.class.getName());
private final ManagedChannel channel;
private final SeaweedFilerGrpc.SeaweedFilerBlockingStub blockingStub;
private final SeaweedFilerGrpc.SeaweedFilerStub asyncStub;
private final SeaweedFilerGrpc.SeaweedFilerFutureStub futureStub;
public FilerGrpcClient(String host, int grpcPort) {
this(ManagedChannelBuilder.forAddress(host, grpcPort).usePlaintext());
}
public FilerGrpcClient(ManagedChannelBuilder<?> channelBuilder) {
channel = channelBuilder.build();
blockingStub = SeaweedFilerGrpc.newBlockingStub(channel);
asyncStub = SeaweedFilerGrpc.newStub(channel);
futureStub = SeaweedFilerGrpc.newFutureStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public SeaweedFilerGrpc.SeaweedFilerBlockingStub getBlockingStub() {
return blockingStub;
}
public SeaweedFilerGrpc.SeaweedFilerStub getAsyncStub() {
return asyncStub;
}
public SeaweedFilerGrpc.SeaweedFilerFutureStub getFutureStub() {
return futureStub;
}
}
@@ -0,0 +1,178 @@
syntax = "proto3";
package filer_pb;
option java_package = "seaweedfs.client";
option java_outer_classname = "FilerProto";
//////////////////////////////////////////////////
service SeaweedFiler {
rpc LookupDirectoryEntry (LookupDirectoryEntryRequest) returns (LookupDirectoryEntryResponse) {
}
rpc ListEntries (ListEntriesRequest) returns (ListEntriesResponse) {
}
rpc CreateEntry (CreateEntryRequest) returns (CreateEntryResponse) {
}
rpc UpdateEntry (UpdateEntryRequest) returns (UpdateEntryResponse) {
}
rpc DeleteEntry (DeleteEntryRequest) returns (DeleteEntryResponse) {
}
rpc AssignVolume (AssignVolumeRequest) returns (AssignVolumeResponse) {
}
rpc LookupVolume (LookupVolumeRequest) returns (LookupVolumeResponse) {
}
rpc DeleteCollection (DeleteCollectionRequest) returns (DeleteCollectionResponse) {
}
rpc Statistics (StatisticsRequest) returns (StatisticsResponse) {
}
}
//////////////////////////////////////////////////
message LookupDirectoryEntryRequest {
string directory = 1;
string name = 2;
}
message LookupDirectoryEntryResponse {
Entry entry = 1;
}
message ListEntriesRequest {
string directory = 1;
string prefix = 2;
string startFromFileName = 3;
bool inclusiveStartFrom = 4;
uint32 limit = 5;
}
message ListEntriesResponse {
repeated Entry entries = 1;
}
message Entry {
string name = 1;
bool is_directory = 2;
repeated FileChunk chunks = 3;
FuseAttributes attributes = 4;
map<string, bytes> extended = 5;
}
message EventNotification {
Entry old_entry = 1;
Entry new_entry = 2;
bool delete_chunks = 3;
}
message FileChunk {
string file_id = 1;
int64 offset = 2;
uint64 size = 3;
int64 mtime = 4;
string e_tag = 5;
string source_file_id = 6;
}
message FuseAttributes {
uint64 file_size = 1;
int64 mtime = 2; // unix time in seconds
uint32 file_mode = 3;
uint32 uid = 4;
uint32 gid = 5;
int64 crtime = 6; // unix time in seconds
string mime = 7;
string replication = 8;
string collection = 9;
int32 ttl_sec = 10;
string user_name = 11; // for hdfs
repeated string group_name = 12; // for hdfs
}
message CreateEntryRequest {
string directory = 1;
Entry entry = 2;
}
message CreateEntryResponse {
}
message UpdateEntryRequest {
string directory = 1;
Entry entry = 2;
}
message UpdateEntryResponse {
}
message DeleteEntryRequest {
string directory = 1;
string name = 2;
// bool is_directory = 3;
bool is_delete_data = 4;
bool is_recursive = 5;
}
message DeleteEntryResponse {
}
message AssignVolumeRequest {
int32 count = 1;
string collection = 2;
string replication = 3;
int32 ttl_sec = 4;
string data_center = 5;
}
message AssignVolumeResponse {
string file_id = 1;
string url = 2;
string public_url = 3;
int32 count = 4;
}
message LookupVolumeRequest {
repeated string volume_ids = 1;
}
message Locations {
repeated Location locations = 1;
}
message Location {
string url = 1;
string public_url = 2;
}
message LookupVolumeResponse {
map<string, Locations> locations_map = 1;
}
message DeleteCollectionRequest {
string collection = 1;
}
message DeleteCollectionResponse {
}
message StatisticsRequest {
string replication = 1;
string collection = 2;
string ttl = 3;
}
message StatisticsResponse {
string replication = 1;
string collection = 2;
string ttl = 3;
uint64 total_size = 4;
uint64 used_size = 5;
uint64 file_count = 6;
}
+169
View File
@@ -0,0 +1,169 @@
<?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>com.github.chrislusf</groupId>
<artifactId>seaweedfs-hadoop-client</artifactId>
<version>1.0.2</version>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>org/slf4j/**</exclude>
<exclude>META-INF/maven/org.slf4j/**</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<relocations>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>shaded.com.google</shadedPattern>
</relocation>
<relocation>
<pattern>io.grpc.internal</pattern>
<shadedPattern>shaded.io.grpc.internal</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>shaded.org.apache.commons</shadedPattern>
<excludes>
<exclude>org.apache.hadoop</exclude>
<exclude>org.apache.log4j</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<hadoop.version>3.1.1</hadoop.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>com.github.chrislusf</groupId>
<artifactId>seaweedfs-client</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,137 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package seaweed.hdfs;
import java.util.concurrent.CountDownLatch;
class ReadBuffer {
private SeaweedInputStream stream;
private long offset; // offset within the file for the buffer
private int length; // actual length, set after the buffer is filles
private int requestedLength; // requested length of the read
private byte[] buffer; // the buffer itself
private int bufferindex = -1; // index in the buffers array in Buffer manager
private ReadBufferStatus status; // status of the buffer
private CountDownLatch latch = null; // signaled when the buffer is done reading, so any client
// waiting on this buffer gets unblocked
// fields to help with eviction logic
private long timeStamp = 0; // tick at which buffer became available to read
private boolean isFirstByteConsumed = false;
private boolean isLastByteConsumed = false;
private boolean isAnyByteConsumed = false;
public SeaweedInputStream getStream() {
return stream;
}
public void setStream(SeaweedInputStream stream) {
this.stream = stream;
}
public long getOffset() {
return offset;
}
public void setOffset(long offset) {
this.offset = offset;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getRequestedLength() {
return requestedLength;
}
public void setRequestedLength(int requestedLength) {
this.requestedLength = requestedLength;
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
}
public int getBufferindex() {
return bufferindex;
}
public void setBufferindex(int bufferindex) {
this.bufferindex = bufferindex;
}
public ReadBufferStatus getStatus() {
return status;
}
public void setStatus(ReadBufferStatus status) {
this.status = status;
}
public CountDownLatch getLatch() {
return latch;
}
public void setLatch(CountDownLatch latch) {
this.latch = latch;
}
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
public boolean isFirstByteConsumed() {
return isFirstByteConsumed;
}
public void setFirstByteConsumed(boolean isFirstByteConsumed) {
this.isFirstByteConsumed = isFirstByteConsumed;
}
public boolean isLastByteConsumed() {
return isLastByteConsumed;
}
public void setLastByteConsumed(boolean isLastByteConsumed) {
this.isLastByteConsumed = isLastByteConsumed;
}
public boolean isAnyByteConsumed() {
return isAnyByteConsumed;
}
public void setAnyByteConsumed(boolean isAnyByteConsumed) {
this.isAnyByteConsumed = isAnyByteConsumed;
}
}
@@ -0,0 +1,394 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package seaweed.hdfs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.CountDownLatch;
/**
* The Read Buffer Manager for Rest AbfsClient.
*/
final class ReadBufferManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ReadBufferManager.class);
private static final int NUM_BUFFERS = 16;
private static final int BLOCK_SIZE = 4 * 1024 * 1024;
private static final int NUM_THREADS = 8;
private static final int THRESHOLD_AGE_MILLISECONDS = 3000; // have to see if 3 seconds is a good threshold
private Thread[] threads = new Thread[NUM_THREADS];
private byte[][] buffers; // array of byte[] buffers, to hold the data that is read
private Stack<Integer> freeList = new Stack<>(); // indices in buffers[] array that are available
private Queue<ReadBuffer> readAheadQueue = new LinkedList<>(); // queue of requests that are not picked up by any worker thread yet
private LinkedList<ReadBuffer> inProgressList = new LinkedList<>(); // requests being processed by worker threads
private LinkedList<ReadBuffer> completedReadList = new LinkedList<>(); // buffers available for reading
private static final ReadBufferManager BUFFER_MANAGER; // singleton, initialized in static initialization block
static {
BUFFER_MANAGER = new ReadBufferManager();
BUFFER_MANAGER.init();
}
static ReadBufferManager getBufferManager() {
return BUFFER_MANAGER;
}
private void init() {
buffers = new byte[NUM_BUFFERS][];
for (int i = 0; i < NUM_BUFFERS; i++) {
buffers[i] = new byte[BLOCK_SIZE]; // same buffers are reused. The byte array never goes back to GC
freeList.add(i);
}
for (int i = 0; i < NUM_THREADS; i++) {
Thread t = new Thread(new ReadBufferWorker(i));
t.setDaemon(true);
threads[i] = t;
t.setName("SeaweedFS-prefetch-" + i);
t.start();
}
ReadBufferWorker.UNLEASH_WORKERS.countDown();
}
// hide instance constructor
private ReadBufferManager() {
}
/*
*
* SeaweedInputStream-facing methods
*
*/
/**
* {@link SeaweedInputStream} calls this method to queue read-aheads.
*
* @param stream The {@link SeaweedInputStream} for which to do the read-ahead
* @param requestedOffset The offset in the file which shoukd be read
* @param requestedLength The length to read
*/
void queueReadAhead(final SeaweedInputStream stream, final long requestedOffset, final int requestedLength) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Start Queueing readAhead for {} offset {} length {}",
stream.getPath(), requestedOffset, requestedLength);
}
ReadBuffer buffer;
synchronized (this) {
if (isAlreadyQueued(stream, requestedOffset)) {
return; // already queued, do not queue again
}
if (freeList.isEmpty() && !tryEvict()) {
return; // no buffers available, cannot queue anything
}
buffer = new ReadBuffer();
buffer.setStream(stream);
buffer.setOffset(requestedOffset);
buffer.setLength(0);
buffer.setRequestedLength(requestedLength);
buffer.setStatus(ReadBufferStatus.NOT_AVAILABLE);
buffer.setLatch(new CountDownLatch(1));
Integer bufferIndex = freeList.pop(); // will return a value, since we have checked size > 0 already
buffer.setBuffer(buffers[bufferIndex]);
buffer.setBufferindex(bufferIndex);
readAheadQueue.add(buffer);
notifyAll();
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Done q-ing readAhead for file {} offset {} buffer idx {}",
stream.getPath(), requestedOffset, buffer.getBufferindex());
}
}
/**
* {@link SeaweedInputStream} calls this method read any bytes already available in a buffer (thereby saving a
* remote read). This returns the bytes if the data already exists in buffer. If there is a buffer that is reading
* the requested offset, then this method blocks until that read completes. If the data is queued in a read-ahead
* but not picked up by a worker thread yet, then it cancels that read-ahead and reports cache miss. This is because
* depending on worker thread availability, the read-ahead may take a while - the calling thread can do it's own
* read to get the data faster (copmared to the read waiting in queue for an indeterminate amount of time).
*
* @param stream the file to read bytes for
* @param position the offset in the file to do a read for
* @param length the length to read
* @param buffer the buffer to read data into. Note that the buffer will be written into from offset 0.
* @return the number of bytes read
*/
int getBlock(final SeaweedInputStream stream, final long position, final int length, final byte[] buffer) {
// not synchronized, so have to be careful with locking
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("getBlock for file {} position {} thread {}",
stream.getPath(), position, Thread.currentThread().getName());
}
waitForProcess(stream, position);
int bytesRead = 0;
synchronized (this) {
bytesRead = getBlockFromCompletedQueue(stream, position, length, buffer);
}
if (bytesRead > 0) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Done read from Cache for {} position {} length {}",
stream.getPath(), position, bytesRead);
}
return bytesRead;
}
// otherwise, just say we got nothing - calling thread can do its own read
return 0;
}
/*
*
* Internal methods
*
*/
private void waitForProcess(final SeaweedInputStream stream, final long position) {
ReadBuffer readBuf;
synchronized (this) {
clearFromReadAheadQueue(stream, position);
readBuf = getFromList(inProgressList, stream, position);
}
if (readBuf != null) { // if in in-progress queue, then block for it
try {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("got a relevant read buffer for file {} offset {} buffer idx {}",
stream.getPath(), readBuf.getOffset(), readBuf.getBufferindex());
}
readBuf.getLatch().await(); // blocking wait on the caller stream's thread
// Note on correctness: readBuf gets out of inProgressList only in 1 place: after worker thread
// is done processing it (in doneReading). There, the latch is set after removing the buffer from
// inProgressList. So this latch is safe to be outside the synchronized block.
// Putting it in synchronized would result in a deadlock, since this thread would be holding the lock
// while waiting, so no one will be able to change any state. If this becomes more complex in the future,
// then the latch cane be removed and replaced with wait/notify whenever inProgressList is touched.
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("latch done for file {} buffer idx {} length {}",
stream.getPath(), readBuf.getBufferindex(), readBuf.getLength());
}
}
}
/**
* If any buffer in the completedlist can be reclaimed then reclaim it and return the buffer to free list.
* The objective is to find just one buffer - there is no advantage to evicting more than one.
*
* @return whether the eviction succeeeded - i.e., were we able to free up one buffer
*/
private synchronized boolean tryEvict() {
ReadBuffer nodeToEvict = null;
if (completedReadList.size() <= 0) {
return false; // there are no evict-able buffers
}
// first, try buffers where all bytes have been consumed (approximated as first and last bytes consumed)
for (ReadBuffer buf : completedReadList) {
if (buf.isFirstByteConsumed() && buf.isLastByteConsumed()) {
nodeToEvict = buf;
break;
}
}
if (nodeToEvict != null) {
return evict(nodeToEvict);
}
// next, try buffers where any bytes have been consumed (may be a bad idea? have to experiment and see)
for (ReadBuffer buf : completedReadList) {
if (buf.isAnyByteConsumed()) {
nodeToEvict = buf;
break;
}
}
if (nodeToEvict != null) {
return evict(nodeToEvict);
}
// next, try any old nodes that have not been consumed
long earliestBirthday = Long.MAX_VALUE;
for (ReadBuffer buf : completedReadList) {
if (buf.getTimeStamp() < earliestBirthday) {
nodeToEvict = buf;
earliestBirthday = buf.getTimeStamp();
}
}
if ((currentTimeMillis() - earliestBirthday > THRESHOLD_AGE_MILLISECONDS) && (nodeToEvict != null)) {
return evict(nodeToEvict);
}
// nothing can be evicted
return false;
}
private boolean evict(final ReadBuffer buf) {
freeList.push(buf.getBufferindex());
completedReadList.remove(buf);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Evicting buffer idx {}; was used for file {} offset {} length {}",
buf.getBufferindex(), buf.getStream().getPath(), buf.getOffset(), buf.getLength());
}
return true;
}
private boolean isAlreadyQueued(final SeaweedInputStream stream, final long requestedOffset) {
// returns true if any part of the buffer is already queued
return (isInList(readAheadQueue, stream, requestedOffset)
|| isInList(inProgressList, stream, requestedOffset)
|| isInList(completedReadList, stream, requestedOffset));
}
private boolean isInList(final Collection<ReadBuffer> list, final SeaweedInputStream stream, final long requestedOffset) {
return (getFromList(list, stream, requestedOffset) != null);
}
private ReadBuffer getFromList(final Collection<ReadBuffer> list, final SeaweedInputStream stream, final long requestedOffset) {
for (ReadBuffer buffer : list) {
if (buffer.getStream() == stream) {
if (buffer.getStatus() == ReadBufferStatus.AVAILABLE
&& requestedOffset >= buffer.getOffset()
&& requestedOffset < buffer.getOffset() + buffer.getLength()) {
return buffer;
} else if (requestedOffset >= buffer.getOffset()
&& requestedOffset < buffer.getOffset() + buffer.getRequestedLength()) {
return buffer;
}
}
}
return null;
}
private void clearFromReadAheadQueue(final SeaweedInputStream stream, final long requestedOffset) {
ReadBuffer buffer = getFromList(readAheadQueue, stream, requestedOffset);
if (buffer != null) {
readAheadQueue.remove(buffer);
notifyAll(); // lock is held in calling method
freeList.push(buffer.getBufferindex());
}
}
private int getBlockFromCompletedQueue(final SeaweedInputStream stream, final long position, final int length,
final byte[] buffer) {
ReadBuffer buf = getFromList(completedReadList, stream, position);
if (buf == null || position >= buf.getOffset() + buf.getLength()) {
return 0;
}
int cursor = (int) (position - buf.getOffset());
int availableLengthInBuffer = buf.getLength() - cursor;
int lengthToCopy = Math.min(length, availableLengthInBuffer);
System.arraycopy(buf.getBuffer(), cursor, buffer, 0, lengthToCopy);
if (cursor == 0) {
buf.setFirstByteConsumed(true);
}
if (cursor + lengthToCopy == buf.getLength()) {
buf.setLastByteConsumed(true);
}
buf.setAnyByteConsumed(true);
return lengthToCopy;
}
/*
*
* ReadBufferWorker-thread-facing methods
*
*/
/**
* ReadBufferWorker thread calls this to get the next buffer that it should work on.
*
* @return {@link ReadBuffer}
* @throws InterruptedException if thread is interrupted
*/
ReadBuffer getNextBlockToRead() throws InterruptedException {
ReadBuffer buffer = null;
synchronized (this) {
//buffer = readAheadQueue.take(); // blocking method
while (readAheadQueue.size() == 0) {
wait();
}
buffer = readAheadQueue.remove();
notifyAll();
if (buffer == null) {
return null; // should never happen
}
buffer.setStatus(ReadBufferStatus.READING_IN_PROGRESS);
inProgressList.add(buffer);
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("ReadBufferWorker picked file {} for offset {}",
buffer.getStream().getPath(), buffer.getOffset());
}
return buffer;
}
/**
* ReadBufferWorker thread calls this method to post completion.
*
* @param buffer the buffer whose read was completed
* @param result the {@link ReadBufferStatus} after the read operation in the worker thread
* @param bytesActuallyRead the number of bytes that the worker thread was actually able to read
*/
void doneReading(final ReadBuffer buffer, final ReadBufferStatus result, final int bytesActuallyRead) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("ReadBufferWorker completed file {} for offset {} bytes {}",
buffer.getStream().getPath(), buffer.getOffset(), bytesActuallyRead);
}
synchronized (this) {
inProgressList.remove(buffer);
if (result == ReadBufferStatus.AVAILABLE && bytesActuallyRead > 0) {
buffer.setStatus(ReadBufferStatus.AVAILABLE);
buffer.setTimeStamp(currentTimeMillis());
buffer.setLength(bytesActuallyRead);
completedReadList.add(buffer);
} else {
freeList.push(buffer.getBufferindex());
// buffer should go out of scope after the end of the calling method in ReadBufferWorker, and eligible for GC
}
}
//outside the synchronized, since anyone receiving a wake-up from the latch must see safe-published results
buffer.getLatch().countDown(); // wake up waiting threads (if any)
}
/**
* Similar to System.currentTimeMillis, except implemented with System.nanoTime().
* System.currentTimeMillis can go backwards when system clock is changed (e.g., with NTP time synchronization),
* making it unsuitable for measuring time intervals. nanotime is strictly monotonically increasing per CPU core.
* Note: it is not monotonic across Sockets, and even within a CPU, its only the
* more recent parts which share a clock across all cores.
*
* @return current time in milliseconds
*/
private long currentTimeMillis() {
return System.nanoTime() / 1000 / 1000;
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package seaweed.hdfs;
/**
* The ReadBufferStatus for Rest AbfsClient
*/
public enum ReadBufferStatus {
NOT_AVAILABLE, // buffers sitting in readaheadqueue have this stats
READING_IN_PROGRESS, // reading is in progress on this buffer. Buffer should be in inProgressList
AVAILABLE, // data is available in buffer. It should be in completedList
READ_FAILED // read completed, but failed.
}
@@ -0,0 +1,70 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package seaweed.hdfs;
import java.util.concurrent.CountDownLatch;
class ReadBufferWorker implements Runnable {
protected static final CountDownLatch UNLEASH_WORKERS = new CountDownLatch(1);
private int id;
ReadBufferWorker(final int id) {
this.id = id;
}
/**
* return the ID of ReadBufferWorker.
*/
public int getId() {
return this.id;
}
/**
* Waits until a buffer becomes available in ReadAheadQueue.
* Once a buffer becomes available, reads the file specified in it and then posts results back to buffer manager.
* Rinse and repeat. Forever.
*/
public void run() {
try {
UNLEASH_WORKERS.await();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
ReadBufferManager bufferManager = ReadBufferManager.getBufferManager();
ReadBuffer buffer;
while (true) {
try {
buffer = bufferManager.getNextBlockToRead(); // blocks, until a buffer is available for this thread
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return;
}
if (buffer != null) {
try {
// do the actual read, from the file.
int bytesRead = buffer.getStream().readRemote(buffer.getOffset(), buffer.getBuffer(), 0, buffer.getRequestedLength());
bufferManager.doneReading(buffer, ReadBufferStatus.AVAILABLE, bytesRead); // post result back to ReadBufferManager
} catch (Exception ex) {
bufferManager.doneReading(buffer, ReadBufferStatus.READ_FAILED, 0);
}
}
}
}
}
@@ -0,0 +1,601 @@
package seaweed.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileAlreadyExistsException;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.ParentNotDirectoryException;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.UnsupportedFileSystemException;
import org.apache.hadoop.fs.XAttrSetFlag;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Progressable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.IO_FILE_BUFFER_SIZE_KEY;
public class SeaweedFileSystem extends org.apache.hadoop.fs.FileSystem {
public static final int FS_SEAWEED_DEFAULT_PORT = 8888;
public static final String FS_SEAWEED_FILER_HOST = "fs.seaweed.filer.host";
public static final String FS_SEAWEED_FILER_PORT = "fs.seaweed.filer.port";
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystem.class);
private static int BUFFER_SIZE = 16 * 1024 * 1024;
private URI uri;
private Path workingDirectory = new Path("/");
private SeaweedFileSystemStore seaweedFileSystemStore;
public URI getUri() {
return uri;
}
public String getScheme() {
return "seaweedfs";
}
@Override
public void initialize(URI uri, Configuration conf) throws IOException { // get
super.initialize(uri, conf);
// get host information from uri (overrides info in conf)
String host = uri.getHost();
host = (host == null) ? conf.get(FS_SEAWEED_FILER_HOST, "localhost") : host;
if (host == null) {
throw new IOException("Invalid host specified");
}
conf.set(FS_SEAWEED_FILER_HOST, host);
// get port information from uri, (overrides info in conf)
int port = uri.getPort();
port = (port == -1) ? FS_SEAWEED_DEFAULT_PORT : port;
conf.setInt(FS_SEAWEED_FILER_PORT, port);
conf.setInt(IO_FILE_BUFFER_SIZE_KEY, BUFFER_SIZE);
setConf(conf);
this.uri = uri;
seaweedFileSystemStore = new SeaweedFileSystemStore(host, port);
}
@Override
public FSDataInputStream open(Path path, int bufferSize) throws IOException {
LOG.debug("open path: {} bufferSize:{}", path, bufferSize);
path = qualify(path);
try {
InputStream inputStream = seaweedFileSystemStore.openFileForRead(path, statistics, bufferSize);
return new FSDataInputStream(inputStream);
} catch (Exception ex) {
return null;
}
}
@Override
public FSDataOutputStream create(Path path, FsPermission permission, final boolean overwrite, final int bufferSize,
final short replication, final long blockSize, final Progressable progress) throws IOException {
LOG.debug("create path: {} bufferSize:{} blockSize:{}", path, bufferSize, blockSize);
path = qualify(path);
try {
String replicaPlacement = String.format("%03d", replication - 1);
OutputStream outputStream = seaweedFileSystemStore.createFile(path, overwrite, permission, bufferSize, replicaPlacement);
return new FSDataOutputStream(outputStream, statistics);
} catch (Exception ex) {
return null;
}
}
@Override
public FSDataOutputStream append(Path path, int bufferSize, Progressable progressable) throws IOException {
LOG.debug("append path: {} bufferSize:{}", path, bufferSize);
path = qualify(path);
try {
OutputStream outputStream = seaweedFileSystemStore.createFile(path, false, null, bufferSize, "");
return new FSDataOutputStream(outputStream, statistics);
} catch (Exception ex) {
return null;
}
}
@Override
public boolean rename(Path src, Path dst) {
LOG.debug("rename path: {} => {}", src, dst);
if (src.isRoot()) {
return false;
}
if (src.equals(dst)) {
return true;
}
FileStatus dstFileStatus = getFileStatus(dst);
String sourceFileName = src.getName();
Path adjustedDst = dst;
if (dstFileStatus != null) {
if (!dstFileStatus.isDirectory()) {
return false;
}
adjustedDst = new Path(dst, sourceFileName);
}
Path qualifiedSrcPath = qualify(src);
Path qualifiedDstPath = qualify(adjustedDst);
seaweedFileSystemStore.rename(qualifiedSrcPath, qualifiedDstPath);
return true;
}
@Override
public boolean delete(Path path, boolean recursive) {
LOG.debug("delete path: {} recursive:{}", path, recursive);
path = qualify(path);
FileStatus fileStatus = getFileStatus(path);
if (fileStatus == null) {
return true;
}
return seaweedFileSystemStore.deleteEntries(path, fileStatus.isDirectory(), recursive);
}
@Override
public FileStatus[] listStatus(Path path) throws IOException {
LOG.debug("listStatus path: {}", path);
path = qualify(path);
return seaweedFileSystemStore.listEntries(path);
}
@Override
public Path getWorkingDirectory() {
return workingDirectory;
}
@Override
public void setWorkingDirectory(Path path) {
if (path.isAbsolute()) {
workingDirectory = path;
} else {
workingDirectory = new Path(workingDirectory, path);
}
}
@Override
public boolean mkdirs(Path path, FsPermission fsPermission) throws IOException {
LOG.debug("mkdirs path: {}", path);
path = qualify(path);
FileStatus fileStatus = getFileStatus(path);
if (fileStatus == null) {
UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
return seaweedFileSystemStore.createDirectory(path, currentUser,
fsPermission == null ? FsPermission.getDirDefault() : fsPermission,
FsPermission.getUMask(getConf()));
}
if (fileStatus.isDirectory()) {
return true;
} else {
throw new FileAlreadyExistsException("Path is a file: " + path);
}
}
@Override
public FileStatus getFileStatus(Path path) {
LOG.debug("getFileStatus path: {}", path);
path = qualify(path);
return seaweedFileSystemStore.getFileStatus(path);
}
/**
* Set owner of a path (i.e. a file or a directory).
* The parameters owner and group cannot both be null.
*
* @param path The path
* @param owner If it is null, the original username remains unchanged.
* @param group If it is null, the original groupname remains unchanged.
*/
@Override
public void setOwner(Path path, final String owner, final String group)
throws IOException {
LOG.debug("setOwner path: {}", path);
path = qualify(path);
seaweedFileSystemStore.setOwner(path, owner, group);
}
/**
* Set permission of a path.
*
* @param path The path
* @param permission Access permission
*/
@Override
public void setPermission(Path path, final FsPermission permission) throws IOException {
LOG.debug("setPermission path: {}", path);
if (permission == null) {
throw new IllegalArgumentException("The permission can't be null");
}
path = qualify(path);
seaweedFileSystemStore.setPermission(path, permission);
}
Path qualify(Path path) {
return path.makeQualified(uri, workingDirectory);
}
/**
* Concat existing files together.
* @param trg the path to the target destination.
* @param psrcs the paths to the sources to use for the concatenation.
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default).
*/
@Override
public void concat(final Path trg, final Path [] psrcs) throws IOException {
throw new UnsupportedOperationException("Not implemented by the " +
getClass().getSimpleName() + " FileSystem implementation");
}
/**
* Truncate the file in the indicated path to the indicated size.
* <ul>
* <li>Fails if path is a directory.</li>
* <li>Fails if path does not exist.</li>
* <li>Fails if path is not closed.</li>
* <li>Fails if new size is greater than current size.</li>
* </ul>
* @param f The path to the file to be truncated
* @param newLength The size the file is to be truncated to
*
* @return <code>true</code> if the file has been truncated to the desired
* <code>newLength</code> and is immediately available to be reused for
* write operations such as <code>append</code>, or
* <code>false</code> if a background process of adjusting the length of
* the last block has been started, and clients should wait for it to
* complete before proceeding with further file updates.
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default).
*/
@Override
public boolean truncate(Path f, long newLength) throws IOException {
throw new UnsupportedOperationException("Not implemented by the " +
getClass().getSimpleName() + " FileSystem implementation");
}
/**
* See {@link FileContext#createSymlink(Path, Path, boolean)}.
*/
@Override
public void createSymlink(final Path target, final Path link,
final boolean createParent) throws AccessControlException,
FileAlreadyExistsException, FileNotFoundException,
ParentNotDirectoryException, UnsupportedFileSystemException,
IOException {
// Supporting filesystems should override this method
throw new UnsupportedOperationException(
"Filesystem does not support symlinks!");
}
/**
* See {@link AbstractFileSystem#supportsSymlinks()}.
*/
@Override
public boolean supportsSymlinks() {
return false;
}
/**
* Create a snapshot.
* @param path The directory where snapshots will be taken.
* @param snapshotName The name of the snapshot
* @return the snapshot path.
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
*/
@Override
public Path createSnapshot(Path path, String snapshotName)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support createSnapshot");
}
/**
* Rename a snapshot.
* @param path The directory path where the snapshot was taken
* @param snapshotOldName Old name of the snapshot
* @param snapshotNewName New name of the snapshot
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void renameSnapshot(Path path, String snapshotOldName,
String snapshotNewName) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support renameSnapshot");
}
/**
* Delete a snapshot of a directory.
* @param path The directory that the to-be-deleted snapshot belongs to
* @param snapshotName The name of the snapshot
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void deleteSnapshot(Path path, String snapshotName)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support deleteSnapshot");
}
/**
* Modifies ACL entries of files and directories. This method can add new ACL
* entries or modify the permissions on existing ACL entries. All existing
* ACL entries that are not specified in this call are retained without
* changes. (Modifications are merged into the current ACL.)
*
* @param path Path to modify
* @param aclSpec List&lt;AclEntry&gt; describing modifications
* @throws IOException if an ACL could not be modified
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support modifyAclEntries");
}
/**
* Removes ACL entries from files and directories. Other ACL entries are
* retained.
*
* @param path Path to modify
* @param aclSpec List describing entries to remove
* @throws IOException if an ACL could not be modified
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void removeAclEntries(Path path, List<AclEntry> aclSpec)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support removeAclEntries");
}
/**
* Removes all default ACL entries from files and directories.
*
* @param path Path to modify
* @throws IOException if an ACL could not be modified
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void removeDefaultAcl(Path path)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support removeDefaultAcl");
}
/**
* Removes all but the base ACL entries of files and directories. The entries
* for user, group, and others are retained for compatibility with permission
* bits.
*
* @param path Path to modify
* @throws IOException if an ACL could not be removed
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void removeAcl(Path path)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support removeAcl");
}
/**
* Fully replaces ACL of files and directories, discarding all existing
* entries.
*
* @param path Path to modify
* @param aclSpec List describing modifications, which must include entries
* for user, group, and others for compatibility with permission bits.
* @throws IOException if an ACL could not be modified
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support setAcl");
}
/**
* Gets the ACL of a file or directory.
*
* @param path Path to get
* @return AclStatus describing the ACL of the file or directory
* @throws IOException if an ACL could not be read
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public AclStatus getAclStatus(Path path) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support getAclStatus");
}
/**
* Set an xattr of a file or directory.
* The name must be prefixed with the namespace followed by ".". For example,
* "user.attr".
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to modify
* @param name xattr name.
* @param value xattr value.
* @param flag xattr set flag
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void setXAttr(Path path, String name, byte[] value,
EnumSet<XAttrSetFlag> flag) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support setXAttr");
}
/**
* Get an xattr name and value for a file or directory.
* The name must be prefixed with the namespace followed by ".". For example,
* "user.attr".
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to get extended attribute
* @param name xattr name.
* @return byte[] xattr value.
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public byte[] getXAttr(Path path, String name) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support getXAttr");
}
/**
* Get all of the xattr name/value pairs for a file or directory.
* Only those xattrs which the logged-in user has permissions to view
* are returned.
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to get extended attributes
* @return Map describing the XAttrs of the file or directory
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public Map<String, byte[]> getXAttrs(Path path) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support getXAttrs");
}
/**
* Get all of the xattrs name/value pairs for a file or directory.
* Only those xattrs which the logged-in user has permissions to view
* are returned.
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to get extended attributes
* @param names XAttr names.
* @return Map describing the XAttrs of the file or directory
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public Map<String, byte[]> getXAttrs(Path path, List<String> names)
throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support getXAttrs");
}
/**
* Get all of the xattr names for a file or directory.
* Only those xattr names which the logged-in user has permissions to view
* are returned.
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to get extended attributes
* @return List{@literal <String>} of the XAttr names of the file or directory
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public List<String> listXAttrs(Path path) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support listXAttrs");
}
/**
* Remove an xattr of a file or directory.
* The name must be prefixed with the namespace followed by ".". For example,
* "user.attr".
* <p>
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to remove extended attribute
* @param name xattr name
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
*/
@Override
public void removeXAttr(Path path, String name) throws IOException {
throw new UnsupportedOperationException(getClass().getSimpleName()
+ " doesn't support removeXAttr");
}
}
@@ -0,0 +1,296 @@
package seaweed.hdfs;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seaweedfs.client.FilerClient;
import seaweedfs.client.FilerGrpcClient;
import seaweedfs.client.FilerProto;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SeaweedFileSystemStore {
private static final Logger LOG = LoggerFactory.getLogger(SeaweedFileSystemStore.class);
private FilerGrpcClient filerGrpcClient;
private FilerClient filerClient;
public SeaweedFileSystemStore(String host, int port) {
int grpcPort = 10000 + port;
filerGrpcClient = new FilerGrpcClient(host, grpcPort);
filerClient = new FilerClient(filerGrpcClient);
}
public static String getParentDirectory(Path path) {
return path.isRoot() ? "/" : path.getParent().toUri().getPath();
}
static int permissionToMode(FsPermission permission, boolean isDirectory) {
int p = permission.toShort();
if (isDirectory) {
p = p | 1 << 31;
}
return p;
}
public boolean createDirectory(final Path path, UserGroupInformation currentUser,
final FsPermission permission, final FsPermission umask) {
LOG.debug("createDirectory path: {} permission: {} umask: {}",
path,
permission,
umask);
return filerClient.mkdirs(
path.toUri().getPath(),
permissionToMode(permission, true),
currentUser.getUserName(),
currentUser.getGroupNames()
);
}
public FileStatus[] listEntries(final Path path) {
LOG.debug("listEntries path: {}", path);
List<FileStatus> fileStatuses = new ArrayList<FileStatus>();
List<FilerProto.Entry> entries = filerClient.listEntries(path.toUri().getPath());
for (FilerProto.Entry entry : entries) {
FileStatus fileStatus = doGetFileStatus(new Path(path, entry.getName()), entry);
fileStatuses.add(fileStatus);
}
return fileStatuses.toArray(new FileStatus[0]);
}
public FileStatus getFileStatus(final Path path) {
FilerProto.Entry entry = lookupEntry(path);
if (entry == null) {
return null;
}
LOG.debug("doGetFileStatus path:{} entry:{}", path, entry);
FileStatus fileStatus = doGetFileStatus(path, entry);
return fileStatus;
}
public boolean deleteEntries(final Path path, boolean isDirectory, boolean recursive) {
LOG.debug("deleteEntries path: {} isDirectory {} recursive: {}",
path,
String.valueOf(isDirectory),
String.valueOf(recursive));
if (path.isRoot()) {
return true;
}
if (recursive && isDirectory) {
List<FilerProto.Entry> entries = filerClient.listEntries(path.toUri().getPath());
for (FilerProto.Entry entry : entries) {
deleteEntries(new Path(path, entry.getName()), entry.getIsDirectory(), true);
}
}
return filerClient.deleteEntry(getParentDirectory(path), path.getName(), true, recursive);
}
private FileStatus doGetFileStatus(Path path, FilerProto.Entry entry) {
FilerProto.FuseAttributes attributes = entry.getAttributes();
long length = SeaweedRead.totalSize(entry.getChunksList());
boolean isDir = entry.getIsDirectory();
int block_replication = 1;
int blocksize = 512;
long modification_time = attributes.getMtime() * 1000; // milliseconds
long access_time = 0;
FsPermission permission = FsPermission.createImmutable((short) attributes.getFileMode());
String owner = attributes.getUserName();
String group = attributes.getGroupNameCount() > 0 ? attributes.getGroupName(0) : "";
return new FileStatus(length, isDir, block_replication, blocksize,
modification_time, access_time, permission, owner, group, null, path);
}
private FilerProto.Entry lookupEntry(Path path) {
return filerClient.lookupEntry(getParentDirectory(path), path.getName());
}
public void rename(Path source, Path destination) {
LOG.debug("rename source: {} destination:{}", source, destination);
if (source.isRoot()) {
return;
}
LOG.warn("rename lookupEntry source: {}", source);
FilerProto.Entry entry = lookupEntry(source);
if (entry == null) {
LOG.warn("rename non-existing source: {}", source);
return;
}
LOG.warn("rename moveEntry source: {}", source);
moveEntry(source.getParent(), entry, destination);
}
private boolean moveEntry(Path oldParent, FilerProto.Entry entry, Path destination) {
LOG.debug("moveEntry: {}/{} => {}", oldParent, entry.getName(), destination);
FilerProto.Entry.Builder newEntry = entry.toBuilder().setName(destination.getName());
boolean isDirectoryCreated = filerClient.createEntry(getParentDirectory(destination), newEntry.build());
if (!isDirectoryCreated) {
return false;
}
if (entry.getIsDirectory()) {
Path entryPath = new Path(oldParent, entry.getName());
List<FilerProto.Entry> entries = filerClient.listEntries(entryPath.toUri().getPath());
for (FilerProto.Entry ent : entries) {
boolean isSucess = moveEntry(entryPath, ent, new Path(destination, ent.getName()));
if (!isSucess) {
return false;
}
}
}
return filerClient.deleteEntry(
oldParent.toUri().getPath(), entry.getName(), false, false);
}
public OutputStream createFile(final Path path,
final boolean overwrite,
FsPermission permission,
int bufferSize,
String replication) throws IOException {
permission = permission == null ? FsPermission.getFileDefault() : permission;
LOG.debug("createFile path: {} overwrite: {} permission: {}",
path,
overwrite,
permission.toString());
UserGroupInformation userGroupInformation = UserGroupInformation.getCurrentUser();
long now = System.currentTimeMillis() / 1000L;
FilerProto.Entry.Builder entry = null;
long writePosition = 0;
if (!overwrite) {
FilerProto.Entry existingEntry = lookupEntry(path);
LOG.debug("createFile merged entry path:{} existingEntry:{}", path, existingEntry);
if (existingEntry != null) {
entry = FilerProto.Entry.newBuilder();
entry.mergeFrom(existingEntry);
entry.getAttributesBuilder().setMtime(now);
}
LOG.debug("createFile merged entry path:{} entry:{} from:{}", path, entry, existingEntry);
writePosition = SeaweedRead.totalSize(existingEntry.getChunksList());
replication = existingEntry.getAttributes().getReplication();
}
if (entry == null) {
entry = FilerProto.Entry.newBuilder()
.setName(path.getName())
.setIsDirectory(false)
.setAttributes(FilerProto.FuseAttributes.newBuilder()
.setFileMode(permissionToMode(permission, false))
.setReplication(replication)
.setCrtime(now)
.setMtime(now)
.setUserName(userGroupInformation.getUserName())
.clearGroupName()
.addAllGroupName(Arrays.asList(userGroupInformation.getGroupNames()))
);
}
return new SeaweedOutputStream(filerGrpcClient, path, entry, writePosition, bufferSize, replication);
}
public InputStream openFileForRead(final Path path, FileSystem.Statistics statistics,
int bufferSize) throws IOException {
LOG.debug("openFileForRead path:{} bufferSize:{}", path, bufferSize);
int readAheadQueueDepth = 2;
FilerProto.Entry entry = lookupEntry(path);
if (entry == null) {
throw new FileNotFoundException("read non-exist file " + path);
}
return new SeaweedInputStream(filerGrpcClient,
statistics,
path.toUri().getPath(),
entry,
bufferSize,
readAheadQueueDepth);
}
public void setOwner(Path path, String owner, String group) {
LOG.debug("setOwner path:{} owner:{} group:{}", path, owner, group);
FilerProto.Entry entry = lookupEntry(path);
if (entry == null) {
LOG.debug("setOwner path:{} entry:{}", path, entry);
return;
}
FilerProto.Entry.Builder entryBuilder = entry.toBuilder();
FilerProto.FuseAttributes.Builder attributesBuilder = entry.getAttributes().toBuilder();
if (owner != null) {
attributesBuilder.setUserName(owner);
}
if (group != null) {
attributesBuilder.clearGroupName();
attributesBuilder.addGroupName(group);
}
entryBuilder.setAttributes(attributesBuilder);
LOG.debug("setOwner path:{} entry:{}", path, entryBuilder);
filerClient.updateEntry(getParentDirectory(path), entryBuilder.build());
}
public void setPermission(Path path, FsPermission permission) {
LOG.debug("setPermission path:{} permission:{}", path, permission);
FilerProto.Entry entry = lookupEntry(path);
if (entry == null) {
LOG.debug("setPermission path:{} entry:{}", path, entry);
return;
}
FilerProto.Entry.Builder entryBuilder = entry.toBuilder();
FilerProto.FuseAttributes.Builder attributesBuilder = entry.getAttributes().toBuilder();
attributesBuilder.setFileMode(permissionToMode(permission, entry.getIsDirectory()));
entryBuilder.setAttributes(attributesBuilder);
LOG.debug("setPermission path:{} entry:{}", path, entryBuilder);
filerClient.updateEntry(getParentDirectory(path), entryBuilder.build());
}
}
@@ -0,0 +1,370 @@
package seaweed.hdfs;
// based on org.apache.hadoop.fs.azurebfs.services.AbfsInputStream
import com.google.common.base.Preconditions;
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 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 final int bufferSize; // default buffer size
private final int readAheadQueueDepth; // initialized in constructor
private final boolean readAheadEnabled; // whether enable readAhead;
private byte[] buffer = null; // will be initialized on first use
private long fCursor = 0; // cursor of buffer within file - offset of next byte to read from remote server
private long fCursorAfterLastRead = -1;
private int bCursor = 0; // cursor of read within buffer - offset of next byte to be returned from buffer
private int limit = 0; // offset of next byte to be read into buffer from service (i.e., upper marker+1
// of valid bytes in buffer)
private boolean closed = false;
public SeaweedInputStream(
final FilerGrpcClient filerGrpcClient,
final Statistics statistics,
final String path,
final FilerProto.Entry entry,
final int bufferSize,
final int readAheadQueueDepth) {
this.filerGrpcClient = filerGrpcClient;
this.statistics = statistics;
this.path = path;
this.entry = entry;
this.contentLength = SeaweedRead.totalSize(entry.getChunksList());
this.bufferSize = bufferSize;
this.readAheadQueueDepth = (readAheadQueueDepth >= 0) ? readAheadQueueDepth : Runtime.getRuntime().availableProcessors();
this.readAheadEnabled = true;
this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(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 {
int currentOff = off;
int currentLen = len;
int lastReadBytes;
int totalReadBytes = 0;
do {
lastReadBytes = readOneBlock(b, currentOff, currentLen);
if (lastReadBytes > 0) {
currentOff += lastReadBytes;
currentLen -= lastReadBytes;
totalReadBytes += lastReadBytes;
}
if (currentLen <= 0 || currentLen > b.length - currentOff) {
break;
}
} while (lastReadBytes > 0);
return totalReadBytes > 0 ? totalReadBytes : lastReadBytes;
}
private int readOneBlock(final byte[] b, final int off, final int len) throws IOException {
if (closed) {
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
}
Preconditions.checkNotNull(b);
if (len == 0) {
return 0;
}
if (this.available() == 0) {
return -1;
}
if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
//If buffer is empty, then fill the buffer.
if (bCursor == limit) {
//If EOF, then return -1
if (fCursor >= contentLength) {
return -1;
}
long bytesRead = 0;
//reset buffer to initial state - i.e., throw away existing data
bCursor = 0;
limit = 0;
if (buffer == null) {
buffer = new byte[bufferSize];
}
// Enable readAhead when reading sequentially
if (-1 == fCursorAfterLastRead || fCursorAfterLastRead == fCursor || b.length >= bufferSize) {
bytesRead = readInternal(fCursor, buffer, 0, bufferSize, false);
} else {
bytesRead = readInternal(fCursor, buffer, 0, b.length, true);
}
if (bytesRead == -1) {
return -1;
}
limit += bytesRead;
fCursor += bytesRead;
fCursorAfterLastRead = fCursor;
}
//If there is anything in the buffer, then return lesser of (requested bytes) and (bytes in buffer)
//(bytes returned may be less than requested)
int bytesRemaining = limit - bCursor;
int bytesToRead = Math.min(len, bytesRemaining);
System.arraycopy(buffer, bCursor, b, off, bytesToRead);
bCursor += bytesToRead;
if (statistics != null) {
statistics.incrementBytesRead(bytesToRead);
}
return bytesToRead;
}
private int readInternal(final long position, final byte[] b, final int offset, final int length,
final boolean bypassReadAhead) throws IOException {
if (readAheadEnabled && !bypassReadAhead) {
// try reading from read-ahead
if (offset != 0) {
throw new IllegalArgumentException("readahead buffers cannot have non-zero buffer offsets");
}
int receivedBytes;
// queue read-aheads
int numReadAheads = this.readAheadQueueDepth;
long nextSize;
long nextOffset = position;
while (numReadAheads > 0 && nextOffset < contentLength) {
nextSize = Math.min((long) bufferSize, contentLength - nextOffset);
ReadBufferManager.getBufferManager().queueReadAhead(this, nextOffset, (int) nextSize);
nextOffset = nextOffset + nextSize;
numReadAheads--;
}
// try reading from buffers first
receivedBytes = ReadBufferManager.getBufferManager().getBlock(this, position, length, b);
if (receivedBytes > 0) {
return receivedBytes;
}
// got nothing from read-ahead, do our own read now
receivedBytes = readRemote(position, b, offset, length);
return receivedBytes;
} else {
return readRemote(position, b, offset, length);
}
}
int readRemote(long position, byte[] b, int offset, int length) 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 (offset >= b.length) {
throw new IllegalArgumentException("offset greater than length of array");
}
if (length < 0) {
throw new IllegalArgumentException("requested read length is less than zero");
}
if (length > (b.length - offset)) {
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
}
long bytesRead = SeaweedRead.read(filerGrpcClient, visibleIntervalList, position, b, offset, length);
if (bytesRead > Integer.MAX_VALUE) {
throw new IOException("Unexpected Content-Length");
}
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);
}
if (n >= fCursor - limit && n <= fCursor) { // within buffer
bCursor = (int) (n - (fCursor - limit));
return;
}
// next read will read from here
fCursor = n;
//invalidate buffer
limit = 0;
bCursor = 0;
}
@Override
public synchronized long skip(long n) throws IOException {
if (closed) {
throw new IOException(FSExceptionMessages.STREAM_IS_CLOSED);
}
long currentPos = getPos();
if (currentPos == contentLength) {
if (n > 0) {
throw new EOFException(FSExceptionMessages.CANNOT_SEEK_PAST_EOF);
}
}
long newPos = currentPos + n;
if (newPos < 0) {
newPos = 0;
n = newPos - currentPos;
}
if (newPos > contentLength) {
newPos = contentLength;
n = newPos - currentPos;
}
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 fCursor - limit + bCursor;
}
/**
* 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;
buffer = null; // de-reference the buffer so it can be GC'ed sooner
}
/**
* 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;
}
}

Some files were not shown because too many files have changed in this diff Show More