├── LICENSE ├── README.md ├── _bench_test.go ├── go.mod ├── go.sum ├── leveldb_store.go └── leveldb_store_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | raft-leveldb 2 | ============ 3 | 4 | This repository provides the `raftleveldb` package. The package exports the 5 | `LevelDBStore` which is an implementation of both a `LogStore` and `StableStore`. 6 | 7 | It is meant to be used as a backend for the `raft` [package 8 | here](https://github.com/hashicorp/raft). 9 | 10 | This implementation uses [LevelDB](https://github.com/syndtr/goleveldb). LevelDB is 11 | a simple key/value store implemented in pure Go. 12 | -------------------------------------------------------------------------------- /_bench_test.go: -------------------------------------------------------------------------------- 1 | package raftleveldb 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkLevelDBStore_FirstIndex(b *testing.B) { 9 | store := testLevelDBStoreHigh(b) 10 | defer store.Close() 11 | defer os.Remove(store.path) 12 | 13 | raftbench.FirstIndex(b, store) 14 | } 15 | 16 | func BenchmarkLevelDBStore_LastIndex(b *testing.B) { 17 | store := testLevelDBStoreHigh(b) 18 | defer store.Close() 19 | defer os.Remove(store.path) 20 | 21 | raftbench.LastIndex(b, store) 22 | } 23 | 24 | func BenchmarkLevelDBStore_GetLog(b *testing.B) { 25 | store := testLevelDBStoreHigh(b) 26 | defer store.Close() 27 | defer os.Remove(store.path) 28 | 29 | raftbench.GetLog(b, store) 30 | } 31 | 32 | func BenchmarkLevelDBStore_StoreLog(b *testing.B) { 33 | store := testLevelDBStoreHigh(b) 34 | defer store.Close() 35 | defer os.Remove(store.path) 36 | 37 | raftbench.StoreLog(b, store) 38 | } 39 | 40 | func BenchmarkLevelDBStore_StoreLogs(b *testing.B) { 41 | store := testLevelDBStoreHigh(b) 42 | defer store.Close() 43 | defer os.Remove(store.path) 44 | 45 | raftbench.StoreLogs(b, store) 46 | } 47 | 48 | func BenchmarkLevelDBStore_DeleteRange(b *testing.B) { 49 | store := testLevelDBStoreHigh(b) 50 | defer store.Close() 51 | defer os.Remove(store.path) 52 | 53 | raftbench.DeleteRange(b, store) 54 | } 55 | 56 | func BenchmarkLevelDBStore_Set(b *testing.B) { 57 | store := testLevelDBStoreHigh(b) 58 | defer store.Close() 59 | defer os.Remove(store.path) 60 | 61 | raftbench.Set(b, store) 62 | } 63 | 64 | func BenchmarkLevelDBStore_Get(b *testing.B) { 65 | store := testLevelDBStoreHigh(b) 66 | defer store.Close() 67 | defer os.Remove(store.path) 68 | 69 | raftbench.Get(b, store) 70 | } 71 | 72 | func BenchmarkLevelDBStore_SetUint64(b *testing.B) { 73 | store := testLevelDBStoreHigh(b) 74 | defer store.Close() 75 | defer os.Remove(store.path) 76 | 77 | raftbench.SetUint64(b, store) 78 | } 79 | 80 | func BenchmarkLevelDBStore_GetUint64(b *testing.B) { 81 | store := testLevelDBStoreHigh(b) 82 | defer store.Close() 83 | defer os.Remove(store.path) 84 | 85 | raftbench.GetUint64(b, store) 86 | } 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tidwall/raft-leveldb 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/hashicorp/raft v1.3.1 7 | github.com/syndtr/goleveldb v1.0.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 2 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= 3 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= 4 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 5 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 6 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 13 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 14 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 15 | github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM= 16 | github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 17 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 18 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 19 | github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= 20 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 21 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 22 | github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= 23 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 24 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 25 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 26 | github.com/hashicorp/raft v1.3.1 h1:zDT8ke8y2aP4wf9zPTB2uSIeavJ3Hx/ceY4jxI2JxuY= 27 | github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= 28 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 29 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 30 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 31 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 32 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 33 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 34 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 35 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 36 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 37 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 38 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 42 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 43 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 44 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 45 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 46 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 47 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 48 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 49 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 50 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 51 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 52 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 53 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= 54 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 55 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 56 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 58 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 59 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 62 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 63 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 64 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 65 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 66 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 67 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 68 | -------------------------------------------------------------------------------- /leveldb_store.go: -------------------------------------------------------------------------------- 1 | package raftleveldb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "sync" 8 | "time" 9 | 10 | "github.com/hashicorp/raft" 11 | "github.com/syndtr/goleveldb/leveldb" 12 | "github.com/syndtr/goleveldb/leveldb/opt" 13 | ) 14 | 15 | const maxBatchSize = 1024 * 1024 16 | 17 | var ( 18 | // Bucket names we perform transactions in 19 | dbLogs = []byte("logs") 20 | dbConf = []byte("conf") 21 | ) 22 | 23 | // ErrKeyNotFound is returned when a given key does not exist 24 | var ErrKeyNotFound = errors.New("not found") 25 | 26 | // ErrClosed is returned when the log is closed 27 | var ErrClosed = errors.New("closed") 28 | 29 | // ErrCorrupt is returned when the log is corrup 30 | var ErrCorrupt = errors.New("corrupt") 31 | 32 | // var errInvalidLog = errors.New("invalid log") 33 | 34 | // LevelDBStore provides access to BoltDB for Raft to store and retrieve 35 | // log entries. It also provides key/value storage, and can be used as 36 | // a LogStore and StableStore. 37 | type LevelDBStore struct { 38 | mu sync.RWMutex 39 | db *leveldb.DB // db is the underlying handle to the db. 40 | path string // The path to the Bolt database file 41 | dur Level 42 | batch leveldb.Batch 43 | bsize int 44 | closed bool 45 | sbuf []byte // reusable buffers for StoreLog 46 | } 47 | 48 | // Level is the consistency level 49 | type Level int 50 | 51 | // Low, Medium, or High level 52 | const ( 53 | Low Level = -1 54 | Medium Level = 0 55 | High Level = 1 56 | ) 57 | 58 | // NewLevelDBStore takes a file path and returns a connected Raft backend. 59 | func NewLevelDBStore(path string, durability Level) (*LevelDBStore, error) { 60 | var opts opt.Options 61 | opts.OpenFilesCacheCapacity = 20 62 | 63 | // Try to connect 64 | db, err := leveldb.OpenFile(path, &opts) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | // Create the new store 70 | store := &LevelDBStore{ 71 | db: db, 72 | path: path, 73 | dur: durability, 74 | } 75 | if durability <= Low { 76 | go store.keepSynced() 77 | } 78 | return store, nil 79 | } 80 | 81 | // keepSynced runs ensure that the data is flushed and fsynced to disk every 82 | // second. This is only expected when the log durability is not set to high. 83 | func (b *LevelDBStore) keepSynced() { 84 | for { 85 | b.mu.Lock() 86 | if b.closed { 87 | b.mu.Unlock() 88 | return 89 | } 90 | func() { 91 | defer b.mu.Unlock() 92 | b.batchFlush(flushSync) 93 | }() 94 | time.Sleep(time.Second) 95 | } 96 | } 97 | 98 | func (b *LevelDBStore) batchDelete(key []byte) { 99 | b.batch.Delete(key) 100 | b.bsize += len(key) 101 | } 102 | 103 | func (b *LevelDBStore) batchPut(key, value []byte) { 104 | b.batch.Put(key, value) 105 | b.bsize += len(key) + len(value) 106 | } 107 | 108 | type flushKind int 109 | 110 | const ( 111 | flushSync flushKind = 1 // forces a journal sync to disk 112 | flushBeforeRead flushKind = 2 // forces batch flush always 113 | flushAfterWrite flushKind = 3 // forces batch flush, if at capacity 114 | ) 115 | 116 | func (b *LevelDBStore) batchFlush(kind flushKind) error { 117 | var opt opt.WriteOptions 118 | opt.Sync = b.dur >= High || kind == flushSync 119 | var requireWrite bool 120 | switch kind { 121 | case flushSync: 122 | if b.dur < High && b.bsize == 0 { 123 | // add a __sync__ key to force the sync to the leveldb journal 124 | b.batchPut([]byte("__sync__"), nil) 125 | } 126 | requireWrite = true 127 | case flushBeforeRead: 128 | requireWrite = true 129 | default: 130 | requireWrite = b.bsize > maxBatchSize || b.dur >= Medium 131 | } 132 | if b.bsize == 0 { 133 | return nil // nothing to flush 134 | } 135 | if requireWrite { 136 | if err := b.db.Write(&b.batch, &opt); err != nil { 137 | return err 138 | } 139 | b.batch.Reset() 140 | b.bsize = 0 141 | } 142 | return nil 143 | } 144 | 145 | // Close is used to gracefully close the DB connection. 146 | func (b *LevelDBStore) Close() error { 147 | b.mu.Lock() 148 | defer b.mu.Unlock() 149 | if b.closed { 150 | return ErrClosed 151 | } 152 | if err := b.batchFlush(flushSync); err != nil { 153 | return err 154 | } 155 | if err := b.db.Close(); err != nil { 156 | return err 157 | } 158 | b.closed = true 159 | return nil 160 | } 161 | 162 | // FirstIndex returns the first known index from the Raft log. 163 | func (b *LevelDBStore) FirstIndex() (uint64, error) { 164 | b.mu.RLock() 165 | defer b.mu.RUnlock() 166 | if b.closed { 167 | return 0, ErrClosed 168 | } 169 | if err := b.batchFlush(flushBeforeRead); err != nil { 170 | return 0, err 171 | } 172 | var n uint64 173 | iter := b.db.NewIterator(nil, nil) 174 | for ok := iter.Seek(dbLogs); ok; ok = iter.Next() { 175 | // Use key/value. 176 | key := iter.Key() 177 | if !bytes.HasPrefix(key, dbLogs) { 178 | break 179 | } 180 | n = bytesToUint64(key[len(dbLogs):]) 181 | break 182 | } 183 | iter.Release() 184 | err := iter.Error() 185 | if err != nil { 186 | return 0, err 187 | } 188 | return n, nil 189 | } 190 | 191 | // LastIndex returns the last known index from the Raft log. 192 | func (b *LevelDBStore) LastIndex() (uint64, error) { 193 | b.mu.RLock() 194 | defer b.mu.RUnlock() 195 | if b.closed { 196 | return 0, ErrClosed 197 | } 198 | if err := b.batchFlush(flushBeforeRead); err != nil { 199 | return 0, err 200 | } 201 | var n uint64 202 | iter := b.db.NewIterator(nil, nil) 203 | for ok := iter.Last(); ok; ok = iter.Prev() { 204 | key := iter.Key() 205 | if !bytes.HasPrefix(key, dbLogs) { 206 | break 207 | } 208 | n = bytesToUint64(key[len(dbLogs):]) 209 | break 210 | } 211 | iter.Release() 212 | err := iter.Error() 213 | if err != nil { 214 | return 0, err 215 | } 216 | return n, nil 217 | } 218 | 219 | // GetLog is used to retrieve a log from BoltDB at a given index. 220 | func (b *LevelDBStore) GetLog(idx uint64, log *raft.Log) error { 221 | b.mu.RLock() 222 | defer b.mu.RUnlock() 223 | if b.closed { 224 | return ErrClosed 225 | } 226 | if err := b.batchFlush(flushBeforeRead); err != nil { 227 | return err 228 | } 229 | key := append(dbLogs, uint64ToBytes(idx)...) 230 | val, err := b.db.Get(key, nil) 231 | if err != nil { 232 | if err == leveldb.ErrNotFound { 233 | return raft.ErrLogNotFound 234 | } 235 | return err 236 | } 237 | return decodeLog(val, log) 238 | } 239 | 240 | // StoreLog is used to store a single raft log 241 | func (b *LevelDBStore) StoreLog(log *raft.Log) error { 242 | return b.StoreLogs([]*raft.Log{log}) 243 | } 244 | 245 | // StoreLogs is used to store a set of raft logs 246 | func (b *LevelDBStore) StoreLogs(logs []*raft.Log) error { 247 | b.mu.Lock() 248 | defer b.mu.Unlock() 249 | if b.closed { 250 | return ErrClosed 251 | } 252 | for _, log := range logs { 253 | b.sbuf = append(b.sbuf[:0], dbLogs...) 254 | b.sbuf = appendUint64(b.sbuf, log.Index) 255 | key := b.sbuf 256 | b.sbuf = appendEncodeLog(b.sbuf, log) 257 | val := b.sbuf[len(key):] 258 | b.batchPut(key, val) 259 | } 260 | if err := b.batchFlush(flushAfterWrite); err != nil { 261 | return err 262 | } 263 | if cap(b.sbuf) > 1048576 { 264 | b.sbuf = nil 265 | } 266 | return nil 267 | } 268 | 269 | // DeleteRange is used to delete logs within a given range inclusively. 270 | func (b *LevelDBStore) DeleteRange(min, max uint64) error { 271 | b.mu.Lock() 272 | defer b.mu.Unlock() 273 | if b.closed { 274 | return ErrClosed 275 | } 276 | prefix := append(dbLogs, uint64ToBytes(min)...) 277 | if err := b.batchFlush(flushBeforeRead); err != nil { 278 | return err 279 | } 280 | iter := b.db.NewIterator(nil, nil) 281 | for ok := iter.Seek(prefix); ok; ok = iter.Next() { 282 | key := iter.Key() 283 | if !bytes.HasPrefix(key, dbLogs) { 284 | break 285 | } 286 | if bytesToUint64(key[len(dbLogs):]) > max { 287 | break 288 | } 289 | b.batchDelete(key) 290 | } 291 | iter.Release() 292 | err := iter.Error() 293 | if err != nil { 294 | return err 295 | } 296 | return b.batchFlush(flushAfterWrite) 297 | } 298 | 299 | // Set is used to set a key/value set outside of the raft log 300 | func (b *LevelDBStore) Set(k, v []byte) error { 301 | b.mu.Lock() 302 | defer b.mu.Unlock() 303 | if b.closed { 304 | return ErrClosed 305 | } 306 | key := append(dbConf, k...) 307 | val := v 308 | b.batchPut(key, val) 309 | return b.batchFlush(flushAfterWrite) 310 | } 311 | 312 | // Get is used to retrieve a value from the k/v store by key 313 | func (b *LevelDBStore) Get(k []byte) ([]byte, error) { 314 | b.mu.RLock() 315 | defer b.mu.RUnlock() 316 | if b.closed { 317 | return nil, ErrClosed 318 | } 319 | if err := b.batchFlush(flushBeforeRead); err != nil { 320 | return nil, err 321 | } 322 | val, err := b.db.Get(append(dbConf, k...), nil) 323 | if err != nil { 324 | if err == leveldb.ErrNotFound { 325 | return nil, ErrKeyNotFound 326 | } 327 | return nil, err 328 | } 329 | return bcopy(val), nil 330 | } 331 | 332 | // SetUint64 is like Set, but handles uint64 values 333 | func (b *LevelDBStore) SetUint64(key []byte, val uint64) error { 334 | return b.Set(key, uint64ToBytes(val)) 335 | } 336 | 337 | // GetUint64 is like Get, but handles uint64 values 338 | func (b *LevelDBStore) GetUint64(key []byte) (uint64, error) { 339 | val, err := b.Get(key) 340 | if err != nil { 341 | return 0, err 342 | } 343 | return bytesToUint64(val), nil 344 | } 345 | 346 | func bcopy(b []byte) []byte { 347 | r := make([]byte, len(b)) 348 | copy(r, b) 349 | return r 350 | } 351 | 352 | // Decode reverses the encode operation on a byte slice input 353 | func decodeLog(buf []byte, log *raft.Log) error { 354 | if len(buf) < 25 { 355 | return ErrCorrupt 356 | } 357 | log.Index = binary.LittleEndian.Uint64(buf[0:8]) 358 | log.Term = binary.LittleEndian.Uint64(buf[8:16]) 359 | log.Type = raft.LogType(buf[16]) 360 | log.Data = make([]byte, binary.LittleEndian.Uint64(buf[17:25])) 361 | if len(buf[25:]) < len(log.Data) { 362 | return ErrCorrupt 363 | } 364 | copy(log.Data, buf[25:]) 365 | return nil 366 | } 367 | 368 | // Encode writes an encoded object to a new bytes buffer 369 | func appendEncodeLog(dst []byte, log *raft.Log) []byte { 370 | var num [8]byte 371 | binary.LittleEndian.PutUint64(num[:], log.Index) 372 | dst = append(dst, num[:]...) 373 | binary.LittleEndian.PutUint64(num[:], log.Term) 374 | dst = append(dst, num[:]...) 375 | dst = append(dst, byte(log.Type)) 376 | binary.LittleEndian.PutUint64(num[:], uint64(len(log.Data))) 377 | dst = append(dst, num[:]...) 378 | dst = append(dst, log.Data...) 379 | return dst 380 | } 381 | 382 | // Converts bytes to an integer 383 | func bytesToUint64(b []byte) uint64 { 384 | return binary.BigEndian.Uint64(b) 385 | } 386 | 387 | // Converts a uint to a byte slice 388 | func uint64ToBytes(u uint64) []byte { 389 | buf := make([]byte, 8) 390 | binary.BigEndian.PutUint64(buf, u) 391 | return buf 392 | } 393 | 394 | // Converts a uint to a byte slice 395 | func appendUint64(dst []byte, u uint64) []byte { 396 | var buf [8]byte 397 | binary.BigEndian.PutUint64(buf[:], u) 398 | dst = append(dst, buf[:]...) 399 | return dst 400 | } 401 | -------------------------------------------------------------------------------- /leveldb_store_test.go: -------------------------------------------------------------------------------- 1 | package raftleveldb 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/hashicorp/raft" 11 | "github.com/syndtr/goleveldb/leveldb" 12 | ) 13 | 14 | func testLevelDBStoreLow(t testing.TB) *LevelDBStore { 15 | return testLevelDBStore(t, Low) 16 | } 17 | 18 | func testLevelDBStoreHigh(t testing.TB) *LevelDBStore { 19 | return testLevelDBStore(t, High) 20 | } 21 | 22 | func testLevelDBStore(t testing.TB, durability Level) *LevelDBStore { 23 | fh, err := ioutil.TempFile("", "bolt") 24 | if err != nil { 25 | t.Fatalf("err: %s", err) 26 | } 27 | os.Remove(fh.Name()) 28 | 29 | // Successfully creates and returns a store 30 | store, err := NewLevelDBStore(fh.Name(), durability) 31 | if err != nil { 32 | t.Fatalf("err: %s", err) 33 | } 34 | 35 | return store 36 | } 37 | 38 | func testRaftLog(idx uint64, data string) *raft.Log { 39 | return &raft.Log{ 40 | Data: []byte(data), 41 | Index: idx, 42 | } 43 | } 44 | 45 | func TestLevelDBStore_Implements(t *testing.T) { 46 | var store interface{} = &LevelDBStore{} 47 | if _, ok := store.(raft.StableStore); !ok { 48 | t.Fatalf("LevelDBStore does not implement raft.StableStore") 49 | } 50 | if _, ok := store.(raft.LogStore); !ok { 51 | t.Fatalf("LevelDBStore does not implement raft.LogStore") 52 | } 53 | } 54 | 55 | func TestNewLevelDBStore(t *testing.T) { 56 | fh, err := ioutil.TempDir("", "bolt") 57 | if err != nil { 58 | t.Fatalf("err: %s", err) 59 | } 60 | os.RemoveAll(fh) 61 | defer os.RemoveAll(fh) 62 | 63 | // Successfully creates and returns a store 64 | store, err := NewLevelDBStore(fh, High) 65 | if err != nil { 66 | t.Fatalf("err: %s", err) 67 | } 68 | 69 | // Ensure the file was created 70 | if store.path != fh { 71 | t.Fatalf("unexpected file path %v", store) 72 | } 73 | if _, err := os.Stat(fh); err != nil { 74 | t.Fatalf("err: %s", err) 75 | } 76 | 77 | // Close the store so we can open again 78 | if err := store.Close(); err != nil { 79 | t.Fatalf("err: %s", err) 80 | } 81 | 82 | // Ensure our tables were created 83 | db, err := leveldb.OpenFile(fh, nil) 84 | if err != nil { 85 | t.Fatalf("err: %s", err) 86 | } 87 | defer db.Close() 88 | 89 | } 90 | 91 | func TestLevelDBStore_FirstIndex(t *testing.T) { 92 | store := testLevelDBStoreHigh(t) 93 | defer store.Close() 94 | defer os.Remove(store.path) 95 | 96 | // Should get 0 index on empty log 97 | idx, err := store.FirstIndex() 98 | if err != nil { 99 | t.Fatalf("err: %s", err) 100 | } 101 | if idx != 0 { 102 | t.Fatalf("bad: %v", idx) 103 | } 104 | 105 | // Set a mock raft log 106 | logs := []*raft.Log{ 107 | testRaftLog(1, "log1"), 108 | testRaftLog(2, "log2"), 109 | testRaftLog(3, "log3"), 110 | } 111 | if err := store.StoreLogs(logs); err != nil { 112 | t.Fatalf("bad: %s", err) 113 | } 114 | 115 | // Fetch the first Raft index 116 | idx, err = store.FirstIndex() 117 | if err != nil { 118 | t.Fatalf("err: %s", err) 119 | } 120 | if idx != 1 { 121 | t.Fatalf("bad: %d", idx) 122 | } 123 | } 124 | 125 | func TestLevelDBStore_LastIndex(t *testing.T) { 126 | store := testLevelDBStoreHigh(t) 127 | defer store.Close() 128 | defer os.Remove(store.path) 129 | 130 | // Should get 0 index on empty log 131 | idx, err := store.LastIndex() 132 | if err != nil { 133 | t.Fatalf("err: %s", err) 134 | } 135 | if idx != 0 { 136 | t.Fatalf("bad: %v", idx) 137 | } 138 | 139 | // Set a mock raft log 140 | logs := []*raft.Log{ 141 | testRaftLog(1, "log1"), 142 | testRaftLog(2, "log2"), 143 | testRaftLog(3, "log3"), 144 | } 145 | if err := store.StoreLogs(logs); err != nil { 146 | t.Fatalf("bad: %s", err) 147 | } 148 | 149 | // Fetch the last Raft index 150 | idx, err = store.LastIndex() 151 | if err != nil { 152 | t.Fatalf("err: %s", err) 153 | } 154 | if idx != 3 { 155 | t.Fatalf("bad: %d", idx) 156 | } 157 | } 158 | 159 | func TestLevelDBStore_GetLog(t *testing.T) { 160 | store := testLevelDBStoreHigh(t) 161 | defer store.Close() 162 | defer os.Remove(store.path) 163 | 164 | log := new(raft.Log) 165 | 166 | // Should return an error on non-existent log 167 | if err := store.GetLog(1, log); err != raft.ErrLogNotFound { 168 | t.Fatalf("expected raft log not found error, got: %v", err) 169 | } 170 | 171 | // Set a mock raft log 172 | logs := []*raft.Log{ 173 | testRaftLog(1, "log1"), 174 | testRaftLog(2, "log2"), 175 | testRaftLog(3, "log3"), 176 | } 177 | if err := store.StoreLogs(logs); err != nil { 178 | t.Fatalf("bad: %s", err) 179 | } 180 | 181 | // Should return the proper log 182 | if err := store.GetLog(2, log); err != nil { 183 | t.Fatalf("err: %s", err) 184 | } 185 | if !reflect.DeepEqual(log, logs[1]) { 186 | t.Fatalf("bad: %#v", log) 187 | } 188 | } 189 | 190 | func TestLevelDBStore_SetLog(t *testing.T) { 191 | store := testLevelDBStoreHigh(t) 192 | defer store.Close() 193 | defer os.Remove(store.path) 194 | 195 | // Create the log 196 | log := &raft.Log{ 197 | Data: []byte("log1"), 198 | Index: 1, 199 | } 200 | 201 | // Attempt to store the log 202 | if err := store.StoreLog(log); err != nil { 203 | t.Fatalf("err: %s", err) 204 | } 205 | 206 | // Retrieve the log again 207 | result := new(raft.Log) 208 | if err := store.GetLog(1, result); err != nil { 209 | t.Fatalf("err: %s", err) 210 | } 211 | 212 | // Ensure the log comes back the same 213 | if !reflect.DeepEqual(log, result) { 214 | t.Fatalf("bad: %v", result) 215 | } 216 | } 217 | 218 | func TestLevelDBStore_SetLogs(t *testing.T) { 219 | store := testLevelDBStoreHigh(t) 220 | defer store.Close() 221 | defer os.Remove(store.path) 222 | 223 | // Create a set of logs 224 | logs := []*raft.Log{ 225 | testRaftLog(1, "log1"), 226 | testRaftLog(2, "log2"), 227 | } 228 | 229 | // Attempt to store the logs 230 | if err := store.StoreLogs(logs); err != nil { 231 | t.Fatalf("err: %s", err) 232 | } 233 | 234 | // Ensure we stored them all 235 | result1, result2 := new(raft.Log), new(raft.Log) 236 | if err := store.GetLog(1, result1); err != nil { 237 | t.Fatalf("err: %s", err) 238 | } 239 | if !reflect.DeepEqual(logs[0], result1) { 240 | t.Fatalf("bad: %#v", result1) 241 | } 242 | if err := store.GetLog(2, result2); err != nil { 243 | t.Fatalf("err: %s", err) 244 | } 245 | if !reflect.DeepEqual(logs[1], result2) { 246 | t.Fatalf("bad: %#v", result2) 247 | } 248 | } 249 | 250 | func TestLevelDBStore_DeleteRange(t *testing.T) { 251 | store := testLevelDBStoreHigh(t) 252 | defer store.Close() 253 | defer os.Remove(store.path) 254 | 255 | // Create a set of logs 256 | log1 := testRaftLog(1, "log1") 257 | log2 := testRaftLog(2, "log2") 258 | log3 := testRaftLog(3, "log3") 259 | logs := []*raft.Log{log1, log2, log3} 260 | 261 | // Attempt to store the logs 262 | if err := store.StoreLogs(logs); err != nil { 263 | t.Fatalf("err: %s", err) 264 | } 265 | 266 | // Attempt to delete a range of logs 267 | if err := store.DeleteRange(1, 2); err != nil { 268 | t.Fatalf("err: %s", err) 269 | } 270 | 271 | // Ensure the logs were deleted 272 | if err := store.GetLog(1, new(raft.Log)); err != raft.ErrLogNotFound { 273 | t.Fatalf("should have deleted log1") 274 | } 275 | if err := store.GetLog(2, new(raft.Log)); err != raft.ErrLogNotFound { 276 | t.Fatalf("should have deleted log2") 277 | } 278 | } 279 | 280 | func TestLevelDBStore_Set_Get(t *testing.T) { 281 | store := testLevelDBStoreHigh(t) 282 | defer store.Close() 283 | defer os.Remove(store.path) 284 | 285 | // Returns error on non-existent key 286 | if _, err := store.Get([]byte("bad")); err != ErrKeyNotFound { 287 | t.Fatalf("expected not found error, got: %q", err) 288 | } 289 | 290 | k, v := []byte("hello"), []byte("world") 291 | 292 | // Try to set a k/v pair 293 | if err := store.Set(k, v); err != nil { 294 | t.Fatalf("err: %s", err) 295 | } 296 | 297 | // Try to read it back 298 | val, err := store.Get(k) 299 | if err != nil { 300 | t.Fatalf("err: %s", err) 301 | } 302 | if !bytes.Equal(val, v) { 303 | t.Fatalf("bad: %v", val) 304 | } 305 | } 306 | 307 | func TestLevelDBStore_SetUint64_GetUint64(t *testing.T) { 308 | store := testLevelDBStoreHigh(t) 309 | defer store.Close() 310 | defer os.Remove(store.path) 311 | 312 | // Returns error on non-existent key 313 | if _, err := store.GetUint64([]byte("bad")); err != ErrKeyNotFound { 314 | t.Fatalf("expected not found error, got: %q", err) 315 | } 316 | 317 | k, v := []byte("abc"), uint64(123) 318 | 319 | // Attempt to set the k/v pair 320 | if err := store.SetUint64(k, v); err != nil { 321 | t.Fatalf("err: %s", err) 322 | } 323 | 324 | // Read back the value 325 | val, err := store.GetUint64(k) 326 | if err != nil { 327 | t.Fatalf("err: %s", err) 328 | } 329 | if val != v { 330 | t.Fatalf("bad: %v", val) 331 | } 332 | } 333 | --------------------------------------------------------------------------------