├── .cproject ├── .gitignore ├── .gitmodules ├── .idea ├── cpp-raft.iml ├── encodings.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── .project ├── .settings └── language.settings.xml ├── .ycm_extra_conf.py ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.rst ├── raft.h ├── raft_logger.cpp ├── raft_logger.h ├── raft_msg.h ├── raft_node.cpp ├── raft_node.h ├── raft_private.h ├── raft_server.cpp ├── raft_server.h ├── raft_server_properties.cpp ├── state_mach.cpp ├── state_mach.h └── tests ├── main_test.cpp ├── make-tests.sh ├── mock_send_functions.cpp ├── mock_send_functions.h ├── test_log.cpp ├── test_node.cpp ├── test_scenario.cpp └── test_server.cpp /.cproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | /usr/bin/make 282 | -j2 283 | cpp_raft 284 | true 285 | false 286 | 287 | 288 | /usr/bin/make 289 | -j2 290 | cpp_raft/fast 291 | true 292 | false 293 | 294 | 295 | /usr/bin/make 296 | -C "/home/osada/progs/cpp-raft" -j2 297 | cpp_raft 298 | true 299 | false 300 | 301 | 302 | /usr/bin/cmake 303 | -E chdir "/home/osada/progs/cpp-raft" "/usr/bin/cmake" -P "CMakeFiles/cpp_raft.dir/cmake_clean.cmake" 304 | 305 | true 306 | false 307 | 308 | 309 | /usr/bin/make 310 | -j2 311 | rebuild_cache 312 | true 313 | false 314 | 315 | 316 | /usr/bin/make 317 | -j2 318 | all 319 | true 320 | false 321 | 322 | 323 | /usr/bin/make 324 | -j2 325 | clean 326 | true 327 | false 328 | 329 | 330 | /usr/bin/make 331 | -j2 332 | raft_logger.cpp.o 333 | true 334 | false 335 | 336 | 337 | /usr/bin/make 338 | -j2 339 | raft_logger.cpp.i 340 | true 341 | false 342 | 343 | 344 | /usr/bin/make 345 | -j2 346 | raft_logger.cpp.s 347 | true 348 | false 349 | 350 | 351 | /usr/bin/make 352 | -j2 353 | raft_node.cpp.o 354 | true 355 | false 356 | 357 | 358 | /usr/bin/make 359 | -j2 360 | raft_node.cpp.i 361 | true 362 | false 363 | 364 | 365 | /usr/bin/make 366 | -j2 367 | raft_node.cpp.s 368 | true 369 | false 370 | 371 | 372 | /usr/bin/make 373 | -j2 374 | raft_server.cpp.o 375 | true 376 | false 377 | 378 | 379 | /usr/bin/make 380 | -j2 381 | raft_server.cpp.i 382 | true 383 | false 384 | 385 | 386 | /usr/bin/make 387 | -j2 388 | raft_server.cpp.s 389 | true 390 | false 391 | 392 | 393 | /usr/bin/make 394 | -j2 395 | raft_server_properties.cpp.o 396 | true 397 | false 398 | 399 | 400 | /usr/bin/make 401 | -j2 402 | raft_server_properties.cpp.i 403 | true 404 | false 405 | 406 | 407 | /usr/bin/make 408 | -j2 409 | raft_server_properties.cpp.s 410 | true 411 | false 412 | 413 | 414 | /usr/bin/make 415 | -j2 416 | state_mach.cpp.o 417 | true 418 | false 419 | 420 | 421 | /usr/bin/make 422 | -j2 423 | state_mach.cpp.i 424 | true 425 | false 426 | 427 | 428 | /usr/bin/make 429 | -j2 430 | state_mach.cpp.s 431 | true 432 | false 433 | 434 | 435 | /usr/bin/make 436 | -j2 437 | tests/main_test.cpp.o 438 | true 439 | false 440 | 441 | 442 | /usr/bin/make 443 | -j2 444 | tests/main_test.cpp.i 445 | true 446 | false 447 | 448 | 449 | /usr/bin/make 450 | -j2 451 | tests/main_test.cpp.s 452 | true 453 | false 454 | 455 | 456 | /usr/bin/make 457 | -j2 458 | tests/mock_send_functions.cpp.o 459 | true 460 | false 461 | 462 | 463 | /usr/bin/make 464 | -j2 465 | tests/mock_send_functions.cpp.i 466 | true 467 | false 468 | 469 | 470 | /usr/bin/make 471 | -j2 472 | tests/mock_send_functions.cpp.s 473 | true 474 | false 475 | 476 | 477 | /usr/bin/make 478 | -j2 479 | tests/test_log.cpp.o 480 | true 481 | false 482 | 483 | 484 | /usr/bin/make 485 | -j2 486 | tests/test_log.cpp.i 487 | true 488 | false 489 | 490 | 491 | /usr/bin/make 492 | -j2 493 | tests/test_log.cpp.s 494 | true 495 | false 496 | 497 | 498 | /usr/bin/make 499 | -j2 500 | tests/test_node.cpp.o 501 | true 502 | false 503 | 504 | 505 | /usr/bin/make 506 | -j2 507 | tests/test_node.cpp.i 508 | true 509 | false 510 | 511 | 512 | /usr/bin/make 513 | -j2 514 | tests/test_node.cpp.s 515 | true 516 | false 517 | 518 | 519 | /usr/bin/make 520 | -j2 521 | tests/test_scenario.cpp.o 522 | true 523 | false 524 | 525 | 526 | /usr/bin/make 527 | -j2 528 | tests/test_scenario.cpp.i 529 | true 530 | false 531 | 532 | 533 | /usr/bin/make 534 | -j2 535 | tests/test_scenario.cpp.s 536 | true 537 | false 538 | 539 | 540 | /usr/bin/make 541 | -j2 542 | tests/test_server.cpp.o 543 | true 544 | false 545 | 546 | 547 | /usr/bin/make 548 | -j2 549 | tests/test_server.cpp.i 550 | true 551 | false 552 | 553 | 554 | /usr/bin/make 555 | -j2 556 | tests/test_server.cpp.s 557 | true 558 | false 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.o 4 | *.pyc 5 | *.gcda 6 | *.gcno 7 | *.bak 8 | *.d 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "CLinkedListQueue"] 2 | path = CLinkedListQueue 3 | url = https://github.com/willemt/CLinkedListQueue.git 4 | [submodule "pantheios"] 5 | path = pantheios 6 | url = https://github.com/zvelo/pantheios 7 | branch = master 8 | -------------------------------------------------------------------------------- /.idea/cpp-raft.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | cpp_raft-Debug@cpp-raft 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.cdt.make.core.makeBuilder 10 | clean,full,incremental, 11 | 12 | 13 | org.eclipse.cdt.make.core.cleanBuildTarget 14 | clean 15 | 16 | 17 | org.eclipse.cdt.make.core.enableCleanBuild 18 | true 19 | 20 | 21 | org.eclipse.cdt.make.core.append_environment 22 | true 23 | 24 | 25 | org.eclipse.cdt.make.core.stopOnError 26 | true 27 | 28 | 29 | org.eclipse.cdt.make.core.enabledIncrementalBuild 30 | true 31 | 32 | 33 | org.eclipse.cdt.make.core.build.command 34 | /usr/bin/make 35 | 36 | 37 | org.eclipse.cdt.make.core.contents 38 | org.eclipse.cdt.make.core.activeConfigSettings 39 | 40 | 41 | org.eclipse.cdt.make.core.build.target.inc 42 | all 43 | 44 | 45 | org.eclipse.cdt.make.core.build.arguments 46 | -j2 47 | 48 | 49 | org.eclipse.cdt.make.core.buildLocation 50 | /home/osada/progs/cpp-raft 51 | 52 | 53 | org.eclipse.cdt.make.core.useDefaultBuildCmd 54 | false 55 | 56 | 57 | org.eclipse.cdt.make.core.environment 58 | VERBOSE=1|CMAKE_NO_VERBOSE=1| 59 | 60 | 61 | org.eclipse.cdt.make.core.enableFullBuild 62 | true 63 | 64 | 65 | org.eclipse.cdt.make.core.build.target.auto 66 | all 67 | 68 | 69 | org.eclipse.cdt.make.core.enableAutoBuild 70 | false 71 | 72 | 73 | org.eclipse.cdt.make.core.build.target.clean 74 | clean 75 | 76 | 77 | org.eclipse.cdt.make.core.fullBuildTarget 78 | all 79 | 80 | 81 | org.eclipse.cdt.make.core.buildArguments 82 | 83 | 84 | 85 | org.eclipse.cdt.make.core.build.location 86 | /home/osada/progs/cpp-raft 87 | 88 | 89 | org.eclipse.cdt.make.core.autoBuildTarget 90 | all 91 | 92 | 93 | org.eclipse.cdt.core.errorOutputParser 94 | org.eclipse.cdt.core.MakeErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser; 95 | 96 | 97 | 98 | 99 | org.eclipse.cdt.make.core.ScannerConfigBuilder 100 | 101 | 102 | 103 | 104 | 105 | org.eclipse.cdt.core.ccnature 106 | org.eclipse.cdt.make.core.makeNature 107 | org.eclipse.cdt.make.core.ScannerConfigNature 108 | org.eclipse.cdt.core.cnature 109 | 110 | 111 | 112 | [Subprojects] 113 | 2 114 | virtual:/virtual 115 | 116 | 117 | [Targets] 118 | 2 119 | virtual:/virtual 120 | 121 | 122 | [Targets]/[exe] cpp_raft 123 | 2 124 | virtual:/virtual 125 | 126 | 127 | [Targets]/[exe] cpp_raft/ 128 | 2 129 | virtual:/virtual 130 | 131 | 132 | [Targets]/[exe] cpp_raft/Source Files 133 | 2 134 | virtual:/virtual 135 | 136 | 137 | [Targets]/[exe] cpp_raft/Source Files/main_test.cpp 138 | 1 139 | /home/osada/progs/cpp-raft/tests/main_test.cpp 140 | 141 | 142 | [Targets]/[exe] cpp_raft/Source Files/mock_send_functions.cpp 143 | 1 144 | /home/osada/progs/cpp-raft/tests/mock_send_functions.cpp 145 | 146 | 147 | [Targets]/[exe] cpp_raft/Source Files/test_log.cpp 148 | 1 149 | /home/osada/progs/cpp-raft/tests/test_log.cpp 150 | 151 | 152 | [Targets]/[exe] cpp_raft/Source Files/test_node.cpp 153 | 1 154 | /home/osada/progs/cpp-raft/tests/test_node.cpp 155 | 156 | 157 | [Targets]/[exe] cpp_raft/Source Files/test_scenario.cpp 158 | 1 159 | /home/osada/progs/cpp-raft/tests/test_scenario.cpp 160 | 161 | 162 | [Targets]/[exe] cpp_raft/Source Files/test_server.cpp 163 | 1 164 | /home/osada/progs/cpp-raft/tests/test_server.cpp 165 | 166 | 167 | [Targets]/[exe] cpp_raft/Source Files/raft_logger.cpp 168 | 1 169 | /home/osada/progs/cpp-raft/raft_logger.cpp 170 | 171 | 172 | [Targets]/[exe] cpp_raft/Source Files/raft_node.cpp 173 | 1 174 | /home/osada/progs/cpp-raft/raft_node.cpp 175 | 176 | 177 | [Targets]/[exe] cpp_raft/Source Files/raft_server.cpp 178 | 1 179 | /home/osada/progs/cpp-raft/raft_server.cpp 180 | 181 | 182 | [Targets]/[exe] cpp_raft/Source Files/raft_server_properties.cpp 183 | 1 184 | /home/osada/progs/cpp-raft/raft_server_properties.cpp 185 | 186 | 187 | [Targets]/[exe] cpp_raft/Source Files/state_mach.cpp 188 | 1 189 | /home/osada/progs/cpp-raft/state_mach.cpp 190 | 191 | 192 | [Targets]/[exe] cpp_raft/Header Files 193 | 2 194 | virtual:/virtual 195 | 196 | 197 | [Targets]/[exe] cpp_raft/Header Files/mock_send_functions.h 198 | 1 199 | /home/osada/progs/cpp-raft/tests/mock_send_functions.h 200 | 201 | 202 | [Targets]/[exe] cpp_raft/Header Files/raft.h 203 | 1 204 | /home/osada/progs/cpp-raft/raft.h 205 | 206 | 207 | [Targets]/[exe] cpp_raft/Header Files/raft_logger.h 208 | 1 209 | /home/osada/progs/cpp-raft/raft_logger.h 210 | 211 | 212 | [Targets]/[exe] cpp_raft/Header Files/raft_msg.h 213 | 1 214 | /home/osada/progs/cpp-raft/raft_msg.h 215 | 216 | 217 | [Targets]/[exe] cpp_raft/Header Files/raft_node.h 218 | 1 219 | /home/osada/progs/cpp-raft/raft_node.h 220 | 221 | 222 | [Targets]/[exe] cpp_raft/Header Files/raft_private.h 223 | 1 224 | /home/osada/progs/cpp-raft/raft_private.h 225 | 226 | 227 | [Targets]/[exe] cpp_raft/Header Files/raft_server.h 228 | 1 229 | /home/osada/progs/cpp-raft/raft_server.h 230 | 231 | 232 | [Targets]/[exe] cpp_raft/Header Files/state_mach.h 233 | 1 234 | /home/osada/progs/cpp-raft/state_mach.h 235 | 236 | 237 | [Targets]/[exe] cpp_raft/CMake Rules 238 | 2 239 | virtual:/virtual 240 | 241 | 242 | [Targets]/[exe] cpp_raft/Resources 243 | 2 244 | virtual:/virtual 245 | 246 | 247 | [Targets]/[exe] cpp_raft/Object Files 248 | 2 249 | virtual:/virtual 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /.settings/language.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | 4 | # These are the compilation flags that will be used in case there's no 5 | # compilation database set (by default, one is not set). 6 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 7 | flags = [ 8 | '-Wall', 9 | '-Wextra', 10 | '-Werror', 11 | '-Wc++98-compat', 12 | '-Wno-long-long', 13 | '-Wno-variadic-macros', 14 | '-fexceptions', 15 | '-DNDEBUG', 16 | # THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which 17 | # language to use when compiling headers. So it will guess. Badly. So C++ 18 | # headers will be compiled as C headers. You don't want that so ALWAYS specify 19 | # a "-std=". 20 | # For a C project, you would set this to something like 'c99' instead of 21 | # 'c++11'. 22 | '-std=c++11', 23 | # ...and the same thing goes for the magic -x option which specifies the 24 | # language that the files to be compiled are written in. This is mostly 25 | # relevant for c++ headers. 26 | # For a C project, you would set this to 'c' instead of 'c++'. 27 | '-x', 28 | 'c++', 29 | '-I', 30 | '.', 31 | '-isystem', 32 | '/usr/include', 33 | '-isystem', 34 | '/usr/local/include', 35 | ] 36 | 37 | 38 | # Set this to the absolute path to the folder (NOT the file!) containing the 39 | # compile_commands.json file to use that instead of 'flags'. See here for 40 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 41 | # 42 | # Most projects will NOT need to set this to anything; you can just change the 43 | # 'flags' list of compilation flags. Notice that YCM itself uses that approach. 44 | compilation_database_folder = '' 45 | 46 | if os.path.exists( compilation_database_folder ): 47 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 48 | else: 49 | database = None 50 | 51 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 52 | 53 | def DirectoryOfThisScript(): 54 | return os.path.dirname( os.path.abspath( __file__ ) ) 55 | 56 | 57 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 58 | if not working_directory: 59 | return list( flags ) 60 | new_flags = [] 61 | make_next_absolute = False 62 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 63 | for flag in flags: 64 | new_flag = flag 65 | 66 | if make_next_absolute: 67 | make_next_absolute = False 68 | if not flag.startswith( '/' ): 69 | new_flag = os.path.join( working_directory, flag ) 70 | 71 | for path_flag in path_flags: 72 | if flag == path_flag: 73 | make_next_absolute = True 74 | break 75 | 76 | if flag.startswith( path_flag ): 77 | path = flag[ len( path_flag ): ] 78 | new_flag = path_flag + os.path.join( working_directory, path ) 79 | break 80 | 81 | if new_flag: 82 | new_flags.append( new_flag ) 83 | return new_flags 84 | 85 | 86 | def IsHeaderFile( filename ): 87 | extension = os.path.splitext( filename )[ 1 ] 88 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 89 | 90 | 91 | def GetCompilationInfoForFile( filename ): 92 | # The compilation_commands.json file generated by CMake does not have entries 93 | # for header files. So we do our best by asking the db for flags for a 94 | # corresponding source file, if any. If one exists, the flags for that file 95 | # should be good enough. 96 | if IsHeaderFile( filename ): 97 | basename = os.path.splitext( filename )[ 0 ] 98 | for extension in SOURCE_EXTENSIONS: 99 | replacement_file = basename + extension 100 | if os.path.exists( replacement_file ): 101 | compilation_info = database.GetCompilationInfoForFile( 102 | replacement_file ) 103 | if compilation_info.compiler_flags_: 104 | return compilation_info 105 | return None 106 | return database.GetCompilationInfoForFile( filename ) 107 | 108 | 109 | def FlagsForFile( filename, **kwargs ): 110 | if database: 111 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 112 | # python list, but a "list-like" StringVec object 113 | compilation_info = GetCompilationInfoForFile( filename ) 114 | if not compilation_info: 115 | return None 116 | 117 | final_flags = MakeRelativePathsInFlagsAbsolute( 118 | compilation_info.compiler_flags_, 119 | compilation_info.compiler_working_dir_ ) 120 | 121 | # NOTE: This is just for YouCompleteMe; it's highly likely that your project 122 | # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR 123 | # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT. 124 | try: 125 | final_flags.remove( '-stdlib=libc++' ) 126 | except ValueError: 127 | pass 128 | else: 129 | relative_to = DirectoryOfThisScript() 130 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 131 | 132 | return { 133 | 'flags': final_flags, 134 | 'do_cache': True 135 | } 136 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.4) 2 | project(cpp_raft) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -I./ -g -I../ -I/usr/include/ ") 5 | 6 | set(SOURCE_FILES 7 | tests/main_test.cpp 8 | tests/mock_send_functions.cpp 9 | tests/mock_send_functions.h 10 | tests/test_log.cpp 11 | tests/test_node.cpp 12 | tests/test_scenario.cpp 13 | tests/test_server.cpp 14 | raft.h 15 | raft_logger.cpp 16 | raft_logger.h 17 | raft_msg.h 18 | raft_node.cpp 19 | raft_node.h 20 | raft_private.h 21 | raft_server.cpp 22 | raft_server.h 23 | raft_server_properties.cpp 24 | state_mach.cpp 25 | state_mach.h) 26 | 27 | find_library(boost /usr/local/) 28 | include_directories(".") 29 | include_directories(/usr/include/gtest) 30 | include_directories("./gtest/") 31 | include_directories(SYSTEM) 32 | link_directories(/usr/local/lib) 33 | link_directories(/usr/lib) 34 | link_directories(SYSTEM) 35 | set(CMAKE_VERBOSE_MAKEFILE on) 36 | add_executable(cpp_raft ${SOURCE_FILES}) 37 | target_link_libraries(cpp_raft gtest pthread) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Willem-Hendrik Thiart 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The names of its contributors may not be used to endorse or promote 12 | products derived from this software without specific prior written 13 | permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL WILLEM-HENDRIK THIART BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "Unix Makefiles" Generator, CMake Version 2.8 3 | 4 | # Default target executed when no arguments are given to make. 5 | default_target: all 6 | .PHONY : default_target 7 | 8 | #============================================================================= 9 | # Special targets provided by cmake. 10 | 11 | # Disable implicit rules so canonical targets will work. 12 | .SUFFIXES: 13 | 14 | # Remove some rules from gmake that .SUFFIXES does not remove. 15 | SUFFIXES = 16 | 17 | .SUFFIXES: .hpux_make_needs_suffix_list 18 | 19 | # Produce verbose output by default. 20 | VERBOSE = 1 21 | 22 | # Suppress display of executed commands. 23 | $(VERBOSE).SILENT: 24 | 25 | # A target that is always out of date. 26 | cmake_force: 27 | .PHONY : cmake_force 28 | 29 | #============================================================================= 30 | # Set environment variables for the build. 31 | 32 | # The shell in which to execute make rules. 33 | SHELL = /bin/sh 34 | 35 | # The CMake executable. 36 | CMAKE_COMMAND = /usr/bin/cmake 37 | 38 | # The command to remove a file. 39 | RM = /usr/bin/cmake -E remove -f 40 | 41 | # Escaping for special characters. 42 | EQUALS = = 43 | 44 | # The top-level source directory on which CMake was run. 45 | CMAKE_SOURCE_DIR = /home/osada/progs/cpp-raft 46 | 47 | # The top-level build directory on which CMake was run. 48 | CMAKE_BINARY_DIR = /home/osada/progs/cpp-raft 49 | 50 | #============================================================================= 51 | # Targets provided globally by CMake. 52 | 53 | # Special rule for the target edit_cache 54 | edit_cache: 55 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running interactive CMake command-line interface..." 56 | /usr/bin/cmake -i . 57 | .PHONY : edit_cache 58 | 59 | # Special rule for the target edit_cache 60 | edit_cache/fast: edit_cache 61 | .PHONY : edit_cache/fast 62 | 63 | # Special rule for the target rebuild_cache 64 | rebuild_cache: 65 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." 66 | /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) 67 | .PHONY : rebuild_cache 68 | 69 | # Special rule for the target rebuild_cache 70 | rebuild_cache/fast: rebuild_cache 71 | .PHONY : rebuild_cache/fast 72 | 73 | # The main all target 74 | all: cmake_check_build_system 75 | $(CMAKE_COMMAND) -E cmake_progress_start /home/osada/progs/cpp-raft/CMakeFiles /home/osada/progs/cpp-raft/CMakeFiles/progress.marks 76 | $(MAKE) -f CMakeFiles/Makefile2 all 77 | $(CMAKE_COMMAND) -E cmake_progress_start /home/osada/progs/cpp-raft/CMakeFiles 0 78 | .PHONY : all 79 | 80 | # The main clean target 81 | clean: 82 | $(MAKE) -f CMakeFiles/Makefile2 clean 83 | .PHONY : clean 84 | 85 | # The main clean target 86 | clean/fast: clean 87 | .PHONY : clean/fast 88 | 89 | # Prepare targets for installation. 90 | preinstall: all 91 | $(MAKE) -f CMakeFiles/Makefile2 preinstall 92 | .PHONY : preinstall 93 | 94 | # Prepare targets for installation. 95 | preinstall/fast: 96 | $(MAKE) -f CMakeFiles/Makefile2 preinstall 97 | .PHONY : preinstall/fast 98 | 99 | # clear depends 100 | depend: 101 | $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 102 | .PHONY : depend 103 | 104 | #============================================================================= 105 | # Target rules for targets named cpp_raft 106 | 107 | # Build rule for target. 108 | cpp_raft: cmake_check_build_system 109 | $(MAKE) -f CMakeFiles/Makefile2 cpp_raft 110 | .PHONY : cpp_raft 111 | 112 | # fast build rule for target. 113 | cpp_raft/fast: 114 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/build 115 | .PHONY : cpp_raft/fast 116 | 117 | raft_logger.o: raft_logger.cpp.o 118 | .PHONY : raft_logger.o 119 | 120 | # target to build an object file 121 | raft_logger.cpp.o: 122 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_logger.cpp.o 123 | .PHONY : raft_logger.cpp.o 124 | 125 | raft_logger.i: raft_logger.cpp.i 126 | .PHONY : raft_logger.i 127 | 128 | # target to preprocess a source file 129 | raft_logger.cpp.i: 130 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_logger.cpp.i 131 | .PHONY : raft_logger.cpp.i 132 | 133 | raft_logger.s: raft_logger.cpp.s 134 | .PHONY : raft_logger.s 135 | 136 | # target to generate assembly for a file 137 | raft_logger.cpp.s: 138 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_logger.cpp.s 139 | .PHONY : raft_logger.cpp.s 140 | 141 | raft_node.o: raft_node.cpp.o 142 | .PHONY : raft_node.o 143 | 144 | # target to build an object file 145 | raft_node.cpp.o: 146 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_node.cpp.o 147 | .PHONY : raft_node.cpp.o 148 | 149 | raft_node.i: raft_node.cpp.i 150 | .PHONY : raft_node.i 151 | 152 | # target to preprocess a source file 153 | raft_node.cpp.i: 154 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_node.cpp.i 155 | .PHONY : raft_node.cpp.i 156 | 157 | raft_node.s: raft_node.cpp.s 158 | .PHONY : raft_node.s 159 | 160 | # target to generate assembly for a file 161 | raft_node.cpp.s: 162 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_node.cpp.s 163 | .PHONY : raft_node.cpp.s 164 | 165 | raft_server.o: raft_server.cpp.o 166 | .PHONY : raft_server.o 167 | 168 | # target to build an object file 169 | raft_server.cpp.o: 170 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server.cpp.o 171 | .PHONY : raft_server.cpp.o 172 | 173 | raft_server.i: raft_server.cpp.i 174 | .PHONY : raft_server.i 175 | 176 | # target to preprocess a source file 177 | raft_server.cpp.i: 178 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server.cpp.i 179 | .PHONY : raft_server.cpp.i 180 | 181 | raft_server.s: raft_server.cpp.s 182 | .PHONY : raft_server.s 183 | 184 | # target to generate assembly for a file 185 | raft_server.cpp.s: 186 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server.cpp.s 187 | .PHONY : raft_server.cpp.s 188 | 189 | raft_server_properties.o: raft_server_properties.cpp.o 190 | .PHONY : raft_server_properties.o 191 | 192 | # target to build an object file 193 | raft_server_properties.cpp.o: 194 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server_properties.cpp.o 195 | .PHONY : raft_server_properties.cpp.o 196 | 197 | raft_server_properties.i: raft_server_properties.cpp.i 198 | .PHONY : raft_server_properties.i 199 | 200 | # target to preprocess a source file 201 | raft_server_properties.cpp.i: 202 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server_properties.cpp.i 203 | .PHONY : raft_server_properties.cpp.i 204 | 205 | raft_server_properties.s: raft_server_properties.cpp.s 206 | .PHONY : raft_server_properties.s 207 | 208 | # target to generate assembly for a file 209 | raft_server_properties.cpp.s: 210 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/raft_server_properties.cpp.s 211 | .PHONY : raft_server_properties.cpp.s 212 | 213 | state_mach.o: state_mach.cpp.o 214 | .PHONY : state_mach.o 215 | 216 | # target to build an object file 217 | state_mach.cpp.o: 218 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/state_mach.cpp.o 219 | .PHONY : state_mach.cpp.o 220 | 221 | state_mach.i: state_mach.cpp.i 222 | .PHONY : state_mach.i 223 | 224 | # target to preprocess a source file 225 | state_mach.cpp.i: 226 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/state_mach.cpp.i 227 | .PHONY : state_mach.cpp.i 228 | 229 | state_mach.s: state_mach.cpp.s 230 | .PHONY : state_mach.s 231 | 232 | # target to generate assembly for a file 233 | state_mach.cpp.s: 234 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/state_mach.cpp.s 235 | .PHONY : state_mach.cpp.s 236 | 237 | tests/main_test.o: tests/main_test.cpp.o 238 | .PHONY : tests/main_test.o 239 | 240 | # target to build an object file 241 | tests/main_test.cpp.o: 242 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/main_test.cpp.o 243 | .PHONY : tests/main_test.cpp.o 244 | 245 | tests/main_test.i: tests/main_test.cpp.i 246 | .PHONY : tests/main_test.i 247 | 248 | # target to preprocess a source file 249 | tests/main_test.cpp.i: 250 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/main_test.cpp.i 251 | .PHONY : tests/main_test.cpp.i 252 | 253 | tests/main_test.s: tests/main_test.cpp.s 254 | .PHONY : tests/main_test.s 255 | 256 | # target to generate assembly for a file 257 | tests/main_test.cpp.s: 258 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/main_test.cpp.s 259 | .PHONY : tests/main_test.cpp.s 260 | 261 | tests/mock_send_functions.o: tests/mock_send_functions.cpp.o 262 | .PHONY : tests/mock_send_functions.o 263 | 264 | # target to build an object file 265 | tests/mock_send_functions.cpp.o: 266 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/mock_send_functions.cpp.o 267 | .PHONY : tests/mock_send_functions.cpp.o 268 | 269 | tests/mock_send_functions.i: tests/mock_send_functions.cpp.i 270 | .PHONY : tests/mock_send_functions.i 271 | 272 | # target to preprocess a source file 273 | tests/mock_send_functions.cpp.i: 274 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/mock_send_functions.cpp.i 275 | .PHONY : tests/mock_send_functions.cpp.i 276 | 277 | tests/mock_send_functions.s: tests/mock_send_functions.cpp.s 278 | .PHONY : tests/mock_send_functions.s 279 | 280 | # target to generate assembly for a file 281 | tests/mock_send_functions.cpp.s: 282 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/mock_send_functions.cpp.s 283 | .PHONY : tests/mock_send_functions.cpp.s 284 | 285 | tests/test_log.o: tests/test_log.cpp.o 286 | .PHONY : tests/test_log.o 287 | 288 | # target to build an object file 289 | tests/test_log.cpp.o: 290 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_log.cpp.o 291 | .PHONY : tests/test_log.cpp.o 292 | 293 | tests/test_log.i: tests/test_log.cpp.i 294 | .PHONY : tests/test_log.i 295 | 296 | # target to preprocess a source file 297 | tests/test_log.cpp.i: 298 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_log.cpp.i 299 | .PHONY : tests/test_log.cpp.i 300 | 301 | tests/test_log.s: tests/test_log.cpp.s 302 | .PHONY : tests/test_log.s 303 | 304 | # target to generate assembly for a file 305 | tests/test_log.cpp.s: 306 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_log.cpp.s 307 | .PHONY : tests/test_log.cpp.s 308 | 309 | tests/test_node.o: tests/test_node.cpp.o 310 | .PHONY : tests/test_node.o 311 | 312 | # target to build an object file 313 | tests/test_node.cpp.o: 314 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_node.cpp.o 315 | .PHONY : tests/test_node.cpp.o 316 | 317 | tests/test_node.i: tests/test_node.cpp.i 318 | .PHONY : tests/test_node.i 319 | 320 | # target to preprocess a source file 321 | tests/test_node.cpp.i: 322 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_node.cpp.i 323 | .PHONY : tests/test_node.cpp.i 324 | 325 | tests/test_node.s: tests/test_node.cpp.s 326 | .PHONY : tests/test_node.s 327 | 328 | # target to generate assembly for a file 329 | tests/test_node.cpp.s: 330 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_node.cpp.s 331 | .PHONY : tests/test_node.cpp.s 332 | 333 | tests/test_scenario.o: tests/test_scenario.cpp.o 334 | .PHONY : tests/test_scenario.o 335 | 336 | # target to build an object file 337 | tests/test_scenario.cpp.o: 338 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_scenario.cpp.o 339 | .PHONY : tests/test_scenario.cpp.o 340 | 341 | tests/test_scenario.i: tests/test_scenario.cpp.i 342 | .PHONY : tests/test_scenario.i 343 | 344 | # target to preprocess a source file 345 | tests/test_scenario.cpp.i: 346 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_scenario.cpp.i 347 | .PHONY : tests/test_scenario.cpp.i 348 | 349 | tests/test_scenario.s: tests/test_scenario.cpp.s 350 | .PHONY : tests/test_scenario.s 351 | 352 | # target to generate assembly for a file 353 | tests/test_scenario.cpp.s: 354 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_scenario.cpp.s 355 | .PHONY : tests/test_scenario.cpp.s 356 | 357 | tests/test_server.o: tests/test_server.cpp.o 358 | .PHONY : tests/test_server.o 359 | 360 | # target to build an object file 361 | tests/test_server.cpp.o: 362 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_server.cpp.o 363 | .PHONY : tests/test_server.cpp.o 364 | 365 | tests/test_server.i: tests/test_server.cpp.i 366 | .PHONY : tests/test_server.i 367 | 368 | # target to preprocess a source file 369 | tests/test_server.cpp.i: 370 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_server.cpp.i 371 | .PHONY : tests/test_server.cpp.i 372 | 373 | tests/test_server.s: tests/test_server.cpp.s 374 | .PHONY : tests/test_server.s 375 | 376 | # target to generate assembly for a file 377 | tests/test_server.cpp.s: 378 | $(MAKE) -f CMakeFiles/cpp_raft.dir/build.make CMakeFiles/cpp_raft.dir/tests/test_server.cpp.s 379 | .PHONY : tests/test_server.cpp.s 380 | 381 | # Help Target 382 | help: 383 | @echo "The following are some of the valid targets for this Makefile:" 384 | @echo "... all (the default if no target is provided)" 385 | @echo "... clean" 386 | @echo "... depend" 387 | @echo "... cpp_raft" 388 | @echo "... edit_cache" 389 | @echo "... rebuild_cache" 390 | @echo "... raft_logger.o" 391 | @echo "... raft_logger.i" 392 | @echo "... raft_logger.s" 393 | @echo "... raft_node.o" 394 | @echo "... raft_node.i" 395 | @echo "... raft_node.s" 396 | @echo "... raft_server.o" 397 | @echo "... raft_server.i" 398 | @echo "... raft_server.s" 399 | @echo "... raft_server_properties.o" 400 | @echo "... raft_server_properties.i" 401 | @echo "... raft_server_properties.s" 402 | @echo "... state_mach.o" 403 | @echo "... state_mach.i" 404 | @echo "... state_mach.s" 405 | @echo "... tests/main_test.o" 406 | @echo "... tests/main_test.i" 407 | @echo "... tests/main_test.s" 408 | @echo "... tests/mock_send_functions.o" 409 | @echo "... tests/mock_send_functions.i" 410 | @echo "... tests/mock_send_functions.s" 411 | @echo "... tests/test_log.o" 412 | @echo "... tests/test_log.i" 413 | @echo "... tests/test_log.s" 414 | @echo "... tests/test_node.o" 415 | @echo "... tests/test_node.i" 416 | @echo "... tests/test_node.s" 417 | @echo "... tests/test_scenario.o" 418 | @echo "... tests/test_scenario.i" 419 | @echo "... tests/test_scenario.s" 420 | @echo "... tests/test_server.o" 421 | @echo "... tests/test_server.i" 422 | @echo "... tests/test_server.s" 423 | .PHONY : help 424 | 425 | 426 | 427 | #============================================================================= 428 | # Special targets to cleanup operation of make. 429 | 430 | # Special rule to run CMake to check the build system integrity. 431 | # No rule that depends on this can have commands that come from listfiles 432 | # because they might be regenerated. 433 | cmake_check_build_system: 434 | $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 435 | .PHONY : cmake_check_build_system 436 | 437 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | CRaft 3 | ===== 4 | 5 | What? 6 | ----- 7 | C implementation of the Raft Consensus protocol, BSD licensed 8 | 9 | How does it work? 10 | ----------------- 11 | See raft.h for full documentation. 12 | 13 | Networking is out of scope for this project. The implementor will need to do all the plumbing. *Currently*, this is done by: 14 | 15 | - Implementing all the callbacks within raft_cbs_t; and 16 | - Calling raft_recv_.* functions with msg_.* message structs 17 | 18 | Dependencies 19 | ------------http://marketplace.eclipse.org/marketplace-client-intro?mpc_install=369 20 | There are no dependencies, however https://github.com/willemt/CLinkedListQueue is required for testing. 21 | 22 | Building 23 | -------- 24 | $make 25 | 26 | Todo 27 | ---- 28 | - Member changes 29 | - Log compaction 30 | - More scenario tests (ie. more varied network partition scenarios) 31 | - Usage example 32 | 33 | -------------------------------------------------------------------------------- /raft.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_RAFT_H 2 | #define INCLUDED_RAFT_H 3 | 4 | #include 5 | 6 | /** 7 | * Copyright (c) 2013, Willem-Hendrik Thiart 8 | * Use of this source code is governed by a BSD-style license that can be 9 | * found in the LICENSE file. 10 | * 11 | * @file 12 | * @author Willem Thiart himself@willemthiart.com 13 | * @version 0.1 14 | */ 15 | 16 | typedef struct { 17 | /** The ID that this node used to have. 18 | * So that we can tell which nodes were removed/added when the 19 | * configuration changes */ 20 | int old_id; 21 | 22 | /** User data for addressing. 23 | * Examples of what this could be: 24 | * - void* pointing to implementor's networking data 25 | * - a (IP,Port) tuple */ 26 | boost::any userData; 27 | } raft_node_configuration_t; 28 | 29 | 30 | typedef int ( 31 | *func_send_f 32 | ) ( 33 | void *cb_ctx, 34 | void *udata, 35 | int node, 36 | int msg_type, 37 | const unsigned char *send_data, 38 | const int d_len 39 | ); 40 | 41 | #ifndef HAVE_FUNC_LOG 42 | #define HAVE_FUNC_LOG 43 | typedef void ( 44 | *func_log_f 45 | ) ( 46 | void *cb_ctx, 47 | void *src, 48 | const char *buf, 49 | ... 50 | ); 51 | #endif 52 | 53 | /** 54 | * Apply this log to the state macine */ 55 | typedef int ( 56 | *func_applylog_f 57 | ) ( 58 | void *cb_ctx, 59 | void *udata, 60 | const unsigned char *d_data, 61 | const int d_len 62 | ); 63 | 64 | typedef struct { 65 | func_send_f send; 66 | func_log_f log; 67 | func_applylog_f applylog; 68 | } raft_cbs_t; 69 | 70 | typedef void* raft_server_t; 71 | typedef int* raft_node_t; 72 | 73 | 74 | #endif //INCLUDED_RAFT_H 75 | 76 | -------------------------------------------------------------------------------- /raft_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "raft_logger.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | RaftLogger::RaftLogger() : entries() { 10 | } 11 | 12 | int RaftLogger::log_append_entry(const raft_entry_t& c) { 13 | if (0 == c.d_id) 14 | return 0; 15 | 16 | entries.push_back(c); 17 | entries.back().d_num_nodes = 0; 18 | return 1; 19 | 20 | } 21 | 22 | raft_entry_t& RaftLogger::log_get_from_idx(int idx) { 23 | if (entries.empty() || idx < 0 || static_cast(idx) > entries.size()) { 24 | throw std::runtime_error("No Such Log"); 25 | } else { 26 | return entries[idx-1]; 27 | } 28 | } 29 | 30 | int RaftLogger::log_count() 31 | { 32 | return entries.size(); 33 | } 34 | 35 | void RaftLogger::log_delete(int idx) 36 | { 37 | entries.erase(entries.begin() + idx - 1, entries.end()); 38 | } 39 | 40 | raft_entry_t& RaftLogger::log_peektail() 41 | { 42 | if (!entries.empty()) { 43 | return entries.back(); 44 | } else { 45 | throw std::runtime_error("No Such Log"); 46 | } 47 | } 48 | 49 | void RaftLogger::log_empty() 50 | { 51 | entries.clear(); 52 | } 53 | 54 | RaftLogger::~RaftLogger() 55 | { 56 | } 57 | 58 | void RaftLogger::log_mark_node_has_committed(int idx) 59 | { 60 | raft_entry_t& e = log_get_from_idx(idx); 61 | e.d_num_nodes += 1; 62 | } 63 | -------------------------------------------------------------------------------- /raft_logger.h: -------------------------------------------------------------------------------- 1 | #ifndef RAFT_LOGGER_H 2 | #define RAFT_LOGGER_H 3 | 4 | #include "raft.h" 5 | #include "raft_msg.h" 6 | #include 7 | 8 | class RaftLogger { 9 | 10 | std::vector entries; 11 | 12 | /** 13 | * @breif This function when called will ensure that we have enough capacity in the current logger state 14 | */ 15 | void ensurecapacity(); 16 | 17 | public: 18 | 19 | /** 20 | * @brief The default constructor 21 | */ 22 | RaftLogger(); 23 | 24 | /** 25 | * @brief Add entry to log. 26 | * Don't add entry if we've already added this entry (based off ID) 27 | * Don't add entries with ID=0 28 | * @return 0 if unsucessful; 1 otherwise 29 | */ 30 | int log_append_entry(const raft_entry_t& c); 31 | 32 | /** 33 | * @brief Retrieve the log entry with a give index 34 | */ 35 | raft_entry_t& log_get_from_idx(int idx); 36 | 37 | /** 38 | * @brief Get the log count held (Written) by this logger 39 | */ 40 | int log_count(); 41 | 42 | /** 43 | * @brief Delete all logs from this log onwards 44 | */ 45 | void log_delete(int idx); 46 | 47 | /* 48 | * @return youngest entry 49 | */ 50 | raft_entry_t& log_peektail(); 51 | 52 | /** 53 | * @brief Empty the queue. 54 | */ 55 | void log_empty(); 56 | 57 | virtual ~RaftLogger(); 58 | 59 | void log_mark_node_has_committed(int idx); 60 | 61 | }; 62 | 63 | #endif //RAFT_LOGGER_H 64 | -------------------------------------------------------------------------------- /raft_msg.h: -------------------------------------------------------------------------------- 1 | #ifndef RAFT_MSG_INCLUDED_H 2 | #define RAFT_MSG_INCLUDED_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class msg_requestvote_t { 9 | /* candidate's term */ 10 | int d_term; 11 | 12 | /* candidate requesting vote */ 13 | int d_candidate_id; 14 | 15 | /* idx of candidate's last log entry */ 16 | int d_last_log_idx; 17 | 18 | /* term of candidate's last log entry */ 19 | int d_last_log_term; 20 | 21 | public: 22 | 23 | msg_requestvote_t(int term, int candidate_id, int last_log_idx, int last_log_term): 24 | d_term(term), d_candidate_id(candidate_id), d_last_log_idx(last_log_idx), d_last_log_term(last_log_term) {} 25 | 26 | inline int term() { return d_term; } 27 | inline int candidate_id() { return d_candidate_id; } 28 | inline int last_log_idx() { return d_last_log_idx; } 29 | inline int last_log_term() { return d_last_log_term; } 30 | 31 | inline void term(int term) { d_term = term; } 32 | inline void candidate_id(int candidate_id) { d_candidate_id = candidate_id; } 33 | inline void last_log_idx(int last_log_idx) { d_last_log_idx = last_log_idx; } 34 | inline void last_log_term(int last_log_term) { d_last_log_term = last_log_term; } 35 | 36 | }; 37 | 38 | class msg_entry_t { 39 | /* the entry's unique ID */ 40 | unsigned int d_id; 41 | 42 | /* entry data */ 43 | unsigned char* d_data; 44 | 45 | /* length of entry data */ 46 | unsigned int d_len; 47 | 48 | public: 49 | 50 | msg_entry_t(): d_id(0), d_data(NULL), d_len(0) { } 51 | 52 | msg_entry_t(unsigned int id, unsigned char* data, unsigned int len): 53 | d_id(id), d_data(data), d_len(len) { } 54 | 55 | unsigned char* data() const { 56 | return d_data; 57 | } 58 | 59 | void data(unsigned char* data) { 60 | this->d_data = data; 61 | } 62 | 63 | unsigned int id() const { 64 | return d_id; 65 | } 66 | 67 | void id(unsigned int id) { 68 | this->d_id = id; 69 | } 70 | 71 | unsigned int len() const { 72 | return d_len; 73 | } 74 | 75 | void len(unsigned int len) { 76 | this->d_len = len; 77 | } 78 | }; 79 | 80 | struct raft_entry_t { 81 | /* entry's term */ 82 | unsigned int d_term; 83 | /* the entry's unique ID */ 84 | unsigned int d_id; 85 | /* entry d_data */ 86 | char* d_data; 87 | /* length of entry d_data */ 88 | unsigned int d_len; 89 | /* number of nodes that have this entry */ 90 | unsigned int d_num_nodes; 91 | 92 | raft_entry_t() : d_term(0), d_id(0), d_data(0), d_len(0), d_num_nodes(0) { 93 | } 94 | 95 | raft_entry_t(unsigned int term, unsigned int id, char* data, unsigned int len, unsigned int num_nodes = 0) : 96 | d_term(term), d_id(id), d_data(data), d_len(len), d_num_nodes(num_nodes) { 97 | } 98 | 99 | char* getData() const { 100 | return d_data; 101 | } 102 | 103 | void setData(char* data) { 104 | this->d_data = data; 105 | } 106 | 107 | unsigned int getId() const { 108 | return d_id; 109 | } 110 | 111 | void setId(unsigned int id) { 112 | this->d_id = id; 113 | } 114 | 115 | unsigned int getLen() const { 116 | return d_len; 117 | } 118 | 119 | void setLen(unsigned int len) { 120 | this->d_len = len; 121 | } 122 | 123 | unsigned int getNumNodes() const { 124 | return d_num_nodes; 125 | } 126 | 127 | void setNumNodes(unsigned int numNodes) { 128 | d_num_nodes = numNodes; 129 | } 130 | 131 | unsigned int getTerm() const { 132 | return d_term; 133 | } 134 | 135 | void setTerm(unsigned int term) { 136 | this->d_term = term; 137 | } 138 | 139 | void populateFromMsgEntry(const msg_entry_t& msg) { 140 | d_len = msg.len(); 141 | d_id = msg.id(); 142 | d_data = reinterpret_cast(malloc(msg.len())); 143 | memcpy(d_data, msg.data(), msg.len()); 144 | } 145 | 146 | }; 147 | 148 | typedef struct { 149 | /* the entry's unique ID */ 150 | unsigned int id; 151 | 152 | /* whether or not the entry was committed */ 153 | int was_committed; 154 | } msg_entry_response_t; 155 | 156 | typedef struct { 157 | /* currentTerm, for candidate to update itself */ 158 | int term; 159 | 160 | /* true means candidate received vote */ 161 | int vote_granted; 162 | } msg_requestvote_response_t; 163 | 164 | class MsgAppendEntries { 165 | int d_term; 166 | int d_leader_id; 167 | int d_prev_log_idx; 168 | int d_prev_log_term; 169 | std::vector d_entries; 170 | int d_leader_commit; 171 | 172 | public: 173 | 174 | MsgAppendEntries() : d_term(0), d_leader_id(0), d_prev_log_idx(0), 175 | d_prev_log_term(0), d_entries(), d_leader_commit(0) { 176 | 177 | } 178 | 179 | MsgAppendEntries(int term, int leader_id, int prev_log_idx, 180 | int prev_log_term, int n_entries, int leader_commit) : 181 | d_term(term), d_leader_id(leader_id), d_prev_log_idx(prev_log_idx), 182 | d_prev_log_term(prev_log_term), d_entries(), d_leader_commit(leader_commit) { 183 | 184 | } 185 | 186 | const msg_entry_t& getEntry(int i) const { 187 | return d_entries[i]; 188 | } 189 | 190 | void addEntry(const msg_entry_t& entry) { 191 | d_entries.push_back(entry); 192 | } 193 | 194 | int getLeaderCommit() const { 195 | return d_leader_commit; 196 | } 197 | 198 | void setLeaderCommit(int leaderCommit) { 199 | d_leader_commit = leaderCommit; 200 | } 201 | 202 | int getLeaderId() const { 203 | return d_leader_id; 204 | } 205 | 206 | void setLeaderId(int leaderId) { 207 | d_leader_id = leaderId; 208 | } 209 | 210 | int getNEntries() const { 211 | return d_entries.size(); 212 | } 213 | 214 | int getPrevLogIdx() const { 215 | return d_prev_log_idx; 216 | } 217 | 218 | bool hasAnyLogs() const { 219 | return d_prev_log_idx != 0; 220 | } 221 | 222 | void setPrevLogIdx(int prevLogIdx) { 223 | d_prev_log_idx = prevLogIdx; 224 | } 225 | 226 | int getPrevLogTerm() const { 227 | return d_prev_log_term; 228 | } 229 | 230 | void setPrevLogTerm(int prevLogTerm) { 231 | d_prev_log_term = prevLogTerm; 232 | } 233 | 234 | int getTerm() const { 235 | return d_term; 236 | } 237 | 238 | void setTerm(int term) { 239 | d_term = term; 240 | } 241 | }; 242 | 243 | typedef struct { 244 | /* currentTerm, for leader to update itself */ 245 | int term; 246 | 247 | /* success true if follower contained entry matching 248 | * prevLogidx and prevLogTerm */ 249 | int success; 250 | 251 | /* Non-Raft fields follow: */ 252 | /* Having the following fields allows us to do less book keeping in 253 | * regards to full fledged RPC */ 254 | /* This is the highest log IDX we've received and appended to our log */ 255 | int current_idx; 256 | /* The first idx that we received within the appendentries message */ 257 | int first_idx; 258 | } msg_appendentries_response_t; 259 | 260 | enum { 261 | RAFT_MSG_REQUESTVOTE, 262 | RAFT_MSG_REQUESTVOTE_RESPONSE, 263 | RAFT_MSG_APPENDENTRIES, 264 | RAFT_MSG_APPENDENTRIES_RESPONSE, 265 | RAFT_MSG_ENTRY, 266 | RAFT_MSG_ENTRY_RESPONSE, 267 | }; 268 | 269 | #endif 270 | -------------------------------------------------------------------------------- /raft_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "raft.h" 7 | #include "raft_node.h" 8 | 9 | RaftNode::RaftNode(const boost::any& udata) : d_udata(udata), next_idx(0) 10 | { } 11 | 12 | int RaftNode::is_leader() 13 | { 14 | // TODO 15 | return 0; 16 | } 17 | 18 | int RaftNode::get_next_idx() 19 | { 20 | return next_idx; 21 | } 22 | 23 | void RaftNode::set_next_idx(int nextIdx) 24 | { 25 | next_idx = nextIdx; 26 | } 27 | 28 | boost::any& RaftNode::get_udata() 29 | { 30 | return d_udata; 31 | } 32 | -------------------------------------------------------------------------------- /raft_node.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_RAFT_NODE_H 2 | #define INCLUDED_RAFT_NODE_H 3 | 4 | #include 5 | 6 | class RaftNode { 7 | 8 | int next_idx; 9 | boost::any d_udata; 10 | 11 | public: 12 | 13 | RaftNode(const boost::any& udata); 14 | int is_leader(); 15 | int get_next_idx(); 16 | void set_next_idx(int nextIdx); 17 | boost::any& get_udata(); 18 | 19 | }; 20 | 21 | #endif //INCLUDED_RAFT_NODE_H 22 | -------------------------------------------------------------------------------- /raft_private.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Willem-Hendrik Thiart 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * 6 | * @file 7 | * @author Willem Thiart himself@willemthiart.com 8 | * @version 0.1 9 | */ 10 | 11 | 12 | class RaftLogger; 13 | 14 | /** 15 | * Apply entry at lastApplied + 1. Entry becomes 'committed'. 16 | * @return 1 if entry committed, 0 otherwise */ 17 | //int raft_apply_entry(raft_server_t* me_); 18 | 19 | /** 20 | * Appends entry using the current term. 21 | * Note: we make the assumption that current term is up-to-date 22 | * @return 0 if unsuccessful */ 23 | //int raft_append_entry(raft_server_t* me_, raft_entry_t* c); 24 | 25 | 26 | -------------------------------------------------------------------------------- /raft_server.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013, Willem-Hendrik Thiart 3 | * Use of this source code is governed by a BSD-style license that can be 4 | * found in the LICENSE file. 5 | * 6 | * @file 7 | * @brief Implementation of a Raft server 8 | * @author Willem Thiart 9 | * @version 0.1 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "state_mach.h" 21 | #include "raft.h" 22 | #include "raft_server.h" 23 | #include "raft_logger.h" 24 | #include "raft_node.h" 25 | #include "raft_private.h" 26 | 27 | #include 28 | 29 | static void __log(void *src, const char *fmt, ...) { 30 | char buf[1024]; 31 | va_list args; 32 | 33 | va_start(args, fmt); 34 | vsprintf(buf, fmt, args); 35 | } 36 | 37 | RaftServer::RaftServer() : 38 | current_term(0), voted_for(-1), current_idx(1), timeout_elapsed(0), request_timeout(200), election_timeout( 39 | 1000), last_applied_idx(0), log(new RaftLogger()), commit_idx(0), votes_for_me(), nodeid(0), cb_ctx(NULL) { 40 | d_state.set(RAFT_STATE_FOLLOWER); 41 | } 42 | 43 | void RaftServer::set_callbacks(raft_cbs_t *funcs, void *cb_ctx) { 44 | memcpy(&this->cb, funcs, sizeof(raft_cbs_t)); 45 | this->cb_ctx = cb_ctx; 46 | } 47 | 48 | RaftServer::~RaftServer() { 49 | } 50 | 51 | void RaftServer::forAllNodesExceptSelf(std::function callback) { 52 | for (int i = 0; i < this->nodes.size(); i++) { 53 | if (this->nodeid == i) { 54 | continue; 55 | } else { 56 | callback(i); 57 | } 58 | } 59 | } 60 | 61 | void RaftServer::election_start() { 62 | 63 | __log(NULL, "election starting: %d %d, term: %d", this->election_timeout, this->timeout_elapsed, 64 | this->current_term); 65 | 66 | become_candidate(); 67 | } 68 | 69 | void RaftServer::become_leader() { 70 | size_t i; 71 | 72 | __log(NULL, "becoming leader"); 73 | 74 | d_state.set(RAFT_STATE_LEADER); 75 | this->voted_for = -1; 76 | forAllNodesExceptSelf([this](int i) { 77 | get_node(i)->set_next_idx(get_current_idx() + 1); 78 | send_appendentries(i); 79 | }); 80 | } 81 | 82 | void RaftServer::become_candidate() { 83 | size_t i; 84 | 85 | __log(NULL, "becoming candidate"); 86 | 87 | this->votes_for_me.clear(); 88 | this->current_term += 1; 89 | vote(this->nodeid); 90 | d_state.set(RAFT_STATE_CANDIDATE); 91 | 92 | /* we need a random factor here to prevent simultaneous candidates */ 93 | this->timeout_elapsed = rand() % 500; 94 | 95 | /* request votes from nodes */ 96 | forAllNodesExceptSelf(&RaftServer::send_requestvote); 97 | } 98 | 99 | void RaftServer::become_follower() { 100 | 101 | __log(NULL, "becoming follower"); 102 | 103 | d_state.set(RAFT_STATE_FOLLOWER); 104 | this->voted_for = -1; 105 | } 106 | 107 | int RaftServer::periodic(int msec_since_last_period) { 108 | 109 | __log(NULL, "periodic elapsed time: %d", this->timeout_elapsed); 110 | 111 | switch (d_state.get()) { 112 | case RAFT_STATE_FOLLOWER: 113 | if (this->last_applied_idx < this->commit_idx) { 114 | if (0 == apply_entry()) 115 | return 0; 116 | } 117 | break; 118 | default: 119 | break; 120 | } 121 | 122 | this->timeout_elapsed += msec_since_last_period; 123 | 124 | if (d_state.get() == RAFT_STATE_LEADER) { 125 | if (this->request_timeout <= this->timeout_elapsed) { 126 | send_appendentries_all(); 127 | this->timeout_elapsed = 0; 128 | } 129 | } else { 130 | if (this->election_timeout <= this->timeout_elapsed) { 131 | election_start(); 132 | } 133 | } 134 | 135 | return 1; 136 | } 137 | 138 | raft_entry_t& RaftServer::get_entry_from_idx(int etyidx) { 139 | return this->log->log_get_from_idx(etyidx); 140 | } 141 | 142 | int RaftServer::recv_appendentries_response(int node, msg_appendentries_response_t *r) { 143 | NodeIter p; 144 | 145 | __log(NULL, "received appendentries response from: %d", node); 146 | 147 | p = get_node(node); 148 | assert(p != this->nodes.end()); 149 | 150 | if (1 == r->success) { 151 | int i; 152 | std::cout << "INcrease Success" << std::endl; 153 | p->set_next_idx(r->current_idx); 154 | for (i = r->first_idx; i <= r->current_idx; i++) 155 | this->log->log_mark_node_has_committed(i); 156 | 157 | while (this->get_commit_idx() < r->current_idx) { 158 | raft_entry_t& e = this->log->log_get_from_idx(this->last_applied_idx + 1); 159 | /* majority has this */ 160 | if (this->nodes.size() / 2 <= e.d_num_nodes) { 161 | if (1 != apply_entry()) 162 | throw std::runtime_error("Could Not Apply!"); 163 | if (e.getId() == r->current_idx) { 164 | return 1; 165 | } 166 | } else { 167 | break; 168 | } 169 | } 170 | } else { 171 | /* If AppendEntries fails because of log inconsistency: 172 | decrement nextIndex and retry (�5.3) */ 173 | assert(0 <= p->get_next_idx()); 174 | // TODO does this have test coverage? 175 | // TODO can jump back to where node is different instead of iterating 176 | p->set_next_idx(p->get_next_idx() - 1); 177 | send_appendentries(node); 178 | } 179 | 180 | return 1; 181 | } 182 | 183 | int RaftServer::recv_appendentries(const int node, MsgAppendEntries *ae) { 184 | msg_appendentries_response_t r; 185 | 186 | this->timeout_elapsed = 0; 187 | 188 | __log(NULL, "received appendentries from: %d", node); 189 | 190 | r.term = this->current_term; 191 | 192 | /* we've found a leader who is legitimate */ 193 | if (d_state.is_leader() && this->current_term <= ae->getTerm()) 194 | become_follower(); 195 | 196 | /* 1. Reply false if term < currentTerm (�5.1) */ 197 | if (ae->getTerm() < this->current_term) { 198 | __log(NULL, "AE term is less than current term"); 199 | r.success = 0; 200 | goto done; 201 | } 202 | 203 | /* not the first appendentries we've received */ 204 | if (ae->hasAnyLogs()) { 205 | try { 206 | raft_entry_t& e = get_entry_from_idx(ae->getPrevLogIdx()); 207 | /* 2. Reply false if log doesn�t contain an entry at prevLogIndex 208 | whose term matches prevLogTerm (�5.3) */ 209 | if (e.d_term != ae->getPrevLogTerm()) { 210 | __log(NULL, "AE term doesn't match prev_idx"); 211 | r.success = 0; 212 | goto done; 213 | } 214 | /* 3. If an existing entry conflicts with a new one (same index 215 | but different terms), delete the existing entry and all that 216 | follow it (�5.3) */ 217 | try { 218 | raft_entry_t& e2 = get_entry_from_idx(ae->getPrevLogIdx() + 1); 219 | this->log->log_delete(ae->getPrevLogIdx() + 1); 220 | } catch (std::runtime_error& err){ 221 | } 222 | 223 | } catch (std::runtime_error& err) { 224 | __log(NULL, "AE no log at prev_idx"); 225 | r.success = 0; 226 | goto done; 227 | //assert(0); 228 | } 229 | } 230 | 231 | /* 5. If leaderCommit > commitIndex, set commitIndex = 232 | min(leaderCommit, last log index) */ 233 | if (get_commit_idx() < ae->getLeaderCommit()) { 234 | try { 235 | const raft_entry_t& e = this->log->log_peektail(); 236 | set_commit_idx(e.d_id < ae->getLeaderCommit() ? e.d_id : ae->getLeaderCommit()); 237 | set_last_applied_idx(e.d_id < ae->getLeaderCommit() ? e.d_id : ae->getLeaderCommit()); 238 | while (1 == apply_entry()) 239 | ; 240 | } catch (std::runtime_error& err) { 241 | 242 | } 243 | } 244 | 245 | if (d_state.is_candidate()) 246 | become_follower(); 247 | 248 | set_current_term(ae->getTerm()); 249 | 250 | /* append all entries to log */ 251 | for (int i = 0; i < ae->getNEntries(); i++) { 252 | /* TODO: replace malloc with mempoll/arena */ 253 | raft_entry_t c; 254 | c.d_term = this->current_term; 255 | c.populateFromMsgEntry(ae->getEntry(i)); 256 | if (0 == append_entry(c)) { 257 | __log(NULL, "AE failure; couldn't append entry"); 258 | r.success = 0; 259 | goto done; 260 | } 261 | } 262 | 263 | r.success = 1; 264 | r.current_idx = get_current_idx(); 265 | r.first_idx = ae->getPrevLogIdx() + 1; 266 | 267 | done: if (this->cb.send) 268 | this->cb.send(this->cb_ctx, this, node, RAFT_MSG_APPENDENTRIES_RESPONSE, (const unsigned char*) &r, 269 | sizeof(msg_appendentries_response_t)); 270 | return 1; 271 | } 272 | 273 | int RaftServer::recv_requestvote(int node, msg_requestvote_t *vr) { 274 | msg_requestvote_response_t r; 275 | 276 | if (get_current_term() < vr->term()) { 277 | this->voted_for = -1; 278 | } 279 | 280 | if (vr->term() < get_current_term() || /* we've already voted */ 281 | -1 != this->voted_for || /* we have a more up-to-date log */ 282 | vr->last_log_idx() < this->current_idx) { 283 | r.vote_granted = 0; 284 | } else { 285 | vote(node); 286 | r.vote_granted = 1; 287 | } 288 | 289 | __log(NULL, "node requested vote: %d replying: %s", node, r.vote_granted == 1 ? "granted" : "not granted"); 290 | 291 | r.term = get_current_term(); 292 | if (this->cb.send) 293 | this->cb.send(this->cb_ctx, this, node, RAFT_MSG_REQUESTVOTE_RESPONSE, (const unsigned char *) &r, 294 | sizeof(msg_requestvote_response_t)); 295 | 296 | return 0; 297 | } 298 | 299 | void RaftServer::recv_requestvote_response(int node, msg_requestvote_response_t *r) { 300 | 301 | __log(NULL, "node responded to requestvote: %d status: %s", node, r->vote_granted == 1 ? "granted" : "not granted"); 302 | 303 | if (d_state.is_leader()) 304 | return; 305 | 306 | assert(node < this->nodes.size()); 307 | 308 | if (1 == r->vote_granted) { 309 | this->votes_for_me[node] = 1; 310 | if (raft_votes_is_majority(this->nodes.size(), get_nvotes_for_me())) 311 | become_leader(); 312 | } 313 | } 314 | 315 | int RaftServer::send_entry_response(int node, int etyid, int was_committed) { 316 | msg_entry_response_t res; 317 | 318 | __log(NULL, "send entry response to: %d", node); 319 | 320 | res.id = etyid; 321 | res.was_committed = was_committed; 322 | if (this->cb.send) 323 | this->cb.send(this->cb_ctx, this, node, RAFT_MSG_ENTRY_RESPONSE, (const unsigned char*) &res, 324 | sizeof(msg_entry_response_t)); 325 | return 0; 326 | } 327 | 328 | int RaftServer::recv_entry(int node, msg_entry_t *e) { 329 | raft_entry_t ety(this->current_term, e->id(), reinterpret_cast(e->data()), e->len()); 330 | __log(NULL, "received entry from: %d", node); 331 | send_entry_response(node, e->id(), append_entry(ety)); 332 | forAllNodesExceptSelf(std::bind(&RaftServer::send_appendentries, this, std::placeholders::_1)); 333 | return 0; 334 | } 335 | 336 | void RaftServer::send_requestvote(int node) { 337 | msg_requestvote_t rv(current_term, 0, get_current_idx(), 0); 338 | 339 | __log(NULL, "sending requestvote to: %d", node); 340 | 341 | if (this->cb.send) 342 | this->cb.send(this->cb_ctx, this, node, RAFT_MSG_REQUESTVOTE, (const unsigned char*) &rv, 343 | sizeof(msg_requestvote_t)); 344 | } 345 | 346 | int RaftServer::append_entry(const raft_entry_t& c) { 347 | 348 | if (1 == this->log->log_append_entry(c)) { 349 | this->current_idx += 1; 350 | return 1; 351 | } 352 | return 0; 353 | } 354 | 355 | int RaftServer::apply_entry() { 356 | raft_entry_t e; 357 | 358 | try { 359 | e = this->log->log_get_from_idx(this->last_applied_idx + 1); 360 | } catch (std::runtime_error& err) { 361 | return 0; 362 | } 363 | 364 | __log(NULL, "applying log: %d", this->last_applied_idx); 365 | 366 | this->last_applied_idx++; 367 | if (get_commit_idx() < this->last_applied_idx) 368 | set_commit_idx(this->last_applied_idx); 369 | if (this->cb.applylog) 370 | this->cb.applylog(this->cb_ctx, this, reinterpret_cast(e.d_data), e.d_len); 371 | return 1; 372 | } 373 | 374 | void RaftServer::send_appendentries(int node) { 375 | 376 | __log(NULL, "sending appendentries to: %d", node); 377 | 378 | if (!(this->cb.send)) 379 | return; 380 | 381 | NodeIter p = get_node(node); 382 | 383 | MsgAppendEntries ae(current_term, nodeid, 0, p->get_next_idx(), 0, 0); 384 | this->cb.send(this->cb_ctx, this, node, RAFT_MSG_APPENDENTRIES, (const unsigned char*) &ae, 385 | sizeof(MsgAppendEntries)); 386 | } 387 | 388 | void RaftServer::send_appendentries_all() { 389 | forAllNodesExceptSelf(&RaftServer::send_appendentries); 390 | } 391 | 392 | void RaftServer::set_configuration(std::vector nodes, int my_idx) { 393 | this->nodes.clear(); 394 | for (int i=0; inodes.push_back(RaftNode(nodes[i].userData)); 396 | this->votes_for_me.push_back(int(0)); 397 | } 398 | this->nodeid = my_idx; 399 | } 400 | 401 | int RaftServer::get_nvotes_for_me() { 402 | int votes = 0; 403 | const std::vector& votes_for_me = this->votes_for_me; 404 | for (int i = 0; i < this->nodes.size(); i++) { 405 | if (this->nodeid == i) { 406 | continue; 407 | } else { 408 | if (1 == this->votes_for_me[i]) { 409 | votes += 1; 410 | } 411 | } 412 | } 413 | 414 | if (this->voted_for == this->nodeid) 415 | votes += 1; 416 | 417 | return votes; 418 | } 419 | 420 | void RaftServer::vote(int node) { 421 | this->voted_for = node; 422 | } 423 | 424 | NodeIter RaftServer::get_node(size_t nodeid) { 425 | if (nodeid < 0 || this->nodes.size() <= nodeid) { 426 | return this->nodes.end(); 427 | } else { 428 | return this->nodes.begin() + nodeid; 429 | } 430 | } 431 | 432 | void RaftServer::set_election_timeout(int millisec) { 433 | this->election_timeout = millisec; 434 | } 435 | 436 | void RaftServer::set_request_timeout(int millisec) { 437 | this->request_timeout = millisec; 438 | } 439 | 440 | int RaftServer::get_nodeid() { 441 | return this->nodeid; 442 | } 443 | 444 | int RaftServer::get_election_timeout() { 445 | return this->election_timeout; 446 | } 447 | 448 | int RaftServer::get_request_timeout() { 449 | return this->request_timeout; 450 | } 451 | 452 | size_t RaftServer::get_num_nodes() { 453 | return this->nodes.size(); 454 | } 455 | 456 | int RaftServer::get_timeout_elapsed() { 457 | return this->timeout_elapsed; 458 | } 459 | 460 | int RaftServer::get_log_count() { 461 | return this->log->log_count(); 462 | } 463 | 464 | int RaftServer::get_voted_for() { 465 | return this->voted_for; 466 | } 467 | 468 | void RaftServer::set_current_term(int term) { 469 | this->current_term = term; 470 | } 471 | 472 | int RaftServer::get_current_term() { 473 | return this->current_term; 474 | } 475 | 476 | void RaftServer::set_current_idx(int idx) { 477 | this->current_idx = idx; 478 | } 479 | 480 | int RaftServer::get_current_idx() { 481 | return this->current_idx; 482 | } 483 | 484 | int RaftServer::get_my_id() { 485 | return this->nodeid; 486 | } 487 | 488 | void RaftServer::set_commit_idx(int idx) { 489 | this->commit_idx = idx; 490 | } 491 | 492 | void RaftServer::set_last_applied_idx(int idx) { 493 | this->last_applied_idx = idx; 494 | } 495 | 496 | int RaftServer::get_last_applied_idx() { 497 | return this->last_applied_idx; 498 | } 499 | 500 | int RaftServer::get_commit_idx() { 501 | return this->commit_idx; 502 | } 503 | 504 | /*--------------------------------------------------------------79-characters-*/ 505 | -------------------------------------------------------------------------------- /raft_server.h: -------------------------------------------------------------------------------- 1 | #ifndef RAFT_SERVER_H 2 | #define RAFT_SERVER_H 3 | 4 | #include "state_mach.h" 5 | #include "raft.h" 6 | #include "raft_msg.h" 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class RaftLogger; 14 | class RaftNode; 15 | typedef std::vector::iterator NodeIter; 16 | 17 | class RaftServer { 18 | 19 | /* Persistent state: */ 20 | ///the server's best guess of what the current term is starts at zero 21 | int current_term; 22 | 23 | /// The candidate the server voted for in its current term, or Nil if it hasn't voted for any. 24 | int voted_for; 25 | 26 | /// the log which is replicated 27 | std::shared_ptr log; 28 | 29 | /* Volatile state: */ 30 | /// idx of highest log entry known to be committed 31 | int commit_idx; 32 | 33 | /// idx of highest log entry applied to state machine 34 | int last_applied_idx; 35 | 36 | /// follower/leader/candidate indicator 37 | Raft::State d_state; 38 | 39 | /// most recently append idx, also indicates size of log 40 | int current_idx; 41 | 42 | /// amount of time left till timeout 43 | int timeout_elapsed; 44 | 45 | /// who has voted for me. This is an array with N = 'num_nodes' elements 46 | std::vector votes_for_me; 47 | 48 | std::vector nodes; 49 | 50 | int election_timeout; 51 | int request_timeout; 52 | 53 | /// callbacks 54 | raft_cbs_t cb; 55 | void* cb_ctx; 56 | 57 | /// my node ID 58 | size_t nodeid; 59 | 60 | typedef void (RaftServer::*PerNodeCallback)(int); 61 | void forAllNodesExceptSelf(std::function callback); 62 | void inline forAllNodesExceptSelf(PerNodeCallback callback) { 63 | forAllNodesExceptSelf(std::bind(callback, this, std::placeholders::_1)); 64 | } 65 | 66 | public: 67 | 68 | /** 69 | * Initialise a new raft server 70 | * 71 | * Request timeout defaults to 200 milliseconds 72 | * Election timeout defaults to 1000 milliseconds 73 | * @return newly initialised raft server */ 74 | RaftServer(); 75 | 76 | virtual ~RaftServer(); 77 | 78 | /** 79 | * Set callbacks 80 | * @param funcs Callbacks 81 | * @param cb_ctx The context that we include with all callbacks */ 82 | void set_callbacks(raft_cbs_t* funcs, void* cb_ctx); 83 | 84 | void election_start(); 85 | 86 | void become_leader(); 87 | 88 | void become_candidate(); 89 | void become_follower(); 90 | 91 | /** 92 | * Run actions that are dependent on time passing 93 | * @return 0 on error */ 94 | int periodic(int msec_since_last_period); 95 | 96 | /** 97 | * @param idx The entry's index 98 | * @return entry from index */ 99 | raft_entry_t& get_entry_from_idx(int etyidx); 100 | 101 | /** 102 | * Receive a response from an appendentries message we sent 103 | * @param node Who sent us the response 104 | * @param r The appendentries response 105 | * @return 0 on error */ 106 | int recv_appendentries_response(int node, msg_appendentries_response_t* r); 107 | /** 108 | * Receive an appendentries message 109 | * @param node Who sent us the response 110 | * @param ae The appendentries message 111 | * @return 0 on error */ 112 | int recv_appendentries(const int node, MsgAppendEntries* ae); 113 | /** 114 | * Receive a requestvote message 115 | * @param node Who sent us the message 116 | * @param vr The requestvote message 117 | * @return 0 on error */ 118 | int recv_requestvote(int node, msg_requestvote_t* vr); 119 | 120 | /** 121 | * Receive a response from a requestvote message we sent 122 | * @param node Who sent us the response 123 | * @param r The requestvote response 124 | */ 125 | void recv_requestvote_response(int node, msg_requestvote_response_t* r); 126 | 127 | int send_entry_response(int node, int etyid, int was_committed); 128 | 129 | /** 130 | * Receive an entry message from client. 131 | * Append the entry to the log 132 | * Send appendentries to followers 133 | * @param node The node this response was sent by 134 | * @param e The entry message */ 135 | int recv_entry(int node, msg_entry_t* e); 136 | 137 | void send_requestvote(int node); 138 | 139 | int append_entry(const raft_entry_t& c); 140 | 141 | int apply_entry(); 142 | 143 | void send_appendentries(int node); 144 | 145 | void send_appendentries_all(); 146 | /** 147 | * Set configuration 148 | * @param nodes Array of nodes, end of array is marked by NULL entry 149 | * @param my_idx Which node is myself */ 150 | void set_configuration(std::vector nodes, int my_idx); 151 | 152 | /** 153 | * @return number of votes this server has received this election */ 154 | int get_nvotes_for_me(); 155 | 156 | void vote(int node); 157 | 158 | /** 159 | * @param node The node's index 160 | * @return node pointed to by node index 161 | */ 162 | NodeIter get_node(size_t nodeid); 163 | 164 | /** 165 | * @return 1 if follower; 0 otherwise */ 166 | int is_follower(); 167 | 168 | /** 169 | * @return 1 if node is leader; 0 otherwise */ 170 | int is_leader(); 171 | 172 | /** 173 | * @return 1 if candidate; 0 otherwise */ 174 | int is_candidate(); 175 | 176 | /** 177 | * Set election timeout 178 | * @param millisec Election timeout in milliseconds */ 179 | void set_election_timeout(int millisec); 180 | 181 | /** 182 | * Set request timeout in milliseconds 183 | * @param millisec Request timeout in milliseconds */ 184 | void set_request_timeout(int millisec); 185 | 186 | /** 187 | * @return the server's node ID */ 188 | int get_nodeid(); 189 | 190 | /** 191 | * @return currently configured election timeout in milliseconds */ 192 | int get_election_timeout(); 193 | 194 | int get_request_timeout(); 195 | 196 | /** 197 | * @return number of nodes that this server has */ 198 | size_t get_num_nodes(); 199 | 200 | /** 201 | * @return currently elapsed timeout in milliseconds */ 202 | int get_timeout_elapsed(); 203 | 204 | /** 205 | * @return number of items within log */ 206 | int get_log_count(); 207 | 208 | /** 209 | * @return node ID of who I voted for */ 210 | int get_voted_for(); 211 | 212 | void set_current_term(int term); 213 | 214 | /** 215 | * @return current term */ 216 | int get_current_term(); 217 | 218 | void set_current_idx(int idx); 219 | 220 | /** 221 | * @return current log index */ 222 | int get_current_idx(); 223 | 224 | int get_my_id(); 225 | 226 | void set_commit_idx(int idx); 227 | 228 | void set_last_applied_idx(int idx); 229 | 230 | /** 231 | * @return index of last applied entry */ 232 | int get_last_applied_idx(); 233 | 234 | int get_commit_idx(); 235 | 236 | inline Raft::State& get_state() { 237 | return d_state; 238 | } 239 | ; 240 | 241 | inline NodeIter get_last_node() { 242 | return nodes.end(); 243 | } 244 | 245 | }; 246 | 247 | inline int raft_votes_is_majority(const int num_nodes, const int nvotes) { 248 | int half; 249 | 250 | if (num_nodes < nvotes) 251 | return 0; 252 | half = num_nodes / 2; 253 | return half + 1 <= nvotes; 254 | } 255 | 256 | #endif //RAFT_SERVER_H 257 | -------------------------------------------------------------------------------- /raft_server_properties.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Copyright (c) 2013, Willem-Hendrik Thiart 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | * 7 | * @file 8 | * @author Willem Thiart himself@willemthiart.com 9 | * @version 0.1 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* for varags */ 18 | #include 19 | 20 | #include "raft.h" 21 | #include "raft_logger.h" 22 | #include "raft_private.h" 23 | //#include "raft_server_properties.h" 24 | 25 | 26 | -------------------------------------------------------------------------------- /state_mach.cpp: -------------------------------------------------------------------------------- 1 | #include "state_mach.h" 2 | 3 | using namespace Raft; 4 | 5 | State::State() { 6 | } 7 | 8 | State::~State() { 9 | } 10 | 11 | bool State::is_follower() { 12 | return d_state == RAFT_STATE_FOLLOWER; 13 | } 14 | 15 | bool State::is_leader() { 16 | return d_state == RAFT_STATE_LEADER; 17 | } 18 | 19 | bool State::is_candidate() { 20 | return d_state == RAFT_STATE_CANDIDATE; 21 | } 22 | 23 | void State::set(RAFT_STATE state) { d_state = state; } 24 | 25 | RAFT_STATE State::get() { return d_state; } 26 | 27 | -------------------------------------------------------------------------------- /state_mach.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_STATE_MACH 2 | #define INCLUDED_STATE_MACH 3 | 4 | enum RAFT_STATE { 5 | RAFT_STATE_NONE, 6 | RAFT_STATE_FOLLOWER, 7 | RAFT_STATE_CANDIDATE, 8 | RAFT_STATE_LEADER 9 | }; 10 | 11 | namespace Raft { 12 | 13 | class State { 14 | 15 | RAFT_STATE d_state; 16 | 17 | public: 18 | 19 | /** 20 | * @brief This creates a defualt constrcuted state object. 21 | * The default state is NONE 22 | */ 23 | State(); 24 | 25 | /** 26 | * @brief The default destructor 27 | */ 28 | virtual ~State(); 29 | 30 | /** 31 | * @brief Return true iff state is leader 32 | */ 33 | bool is_leader(); 34 | 35 | /** 36 | * @brief Returns true iff state is follower 37 | */ 38 | bool is_follower(); 39 | 40 | /** 41 | * @brief Returns true iff state is candidate 42 | */ 43 | bool is_candidate(); 44 | 45 | /** 46 | * @brief Sets the state of this object 47 | */ 48 | void set(RAFT_STATE state); 49 | 50 | /** 51 | * @brief Gets the current state of the object 52 | */ 53 | RAFT_STATE get(); 54 | 55 | }; 56 | 57 | } //namespace Raft 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /tests/main_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char* argv[]) 5 | { 6 | ::testing::InitGoogleTest(&argc, argv); 7 | ::testing::FLAGS_gtest_death_test_style = "fast"; 8 | return RUN_ALL_TESTS(); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/make-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Auto generate single AllTests file for CuTest. 4 | # Searches through all *.c files in the current directory. 5 | # Prints to stdout. 6 | # Author: Asim Jalis 7 | # Date: 01/08/2003 8 | 9 | FILES=*.c 10 | 11 | #if test $# -eq 0 ; then FILES=*.c ; else FILES=$* ; fi 12 | 13 | echo ' 14 | 15 | /* This is auto-generated code. Edit at your own peril. */ 16 | #include 17 | #include "CuTest.h" 18 | 19 | ' 20 | 21 | cat $FILES | grep '^void Test' | 22 | sed -e 's/(.*$//' \ 23 | -e 's/$/(CuTest*);/' \ 24 | -e 's/^/extern /' 25 | 26 | echo -n \ 27 | ' 28 | 29 | void RunAllTests(void) 30 | { 31 | CuString *output = CuStringNew(); 32 | CuSuite* suite = CuSuiteNew(); 33 | 34 | ' 35 | cat $FILES | grep '^void Test' | 36 | sed -e 's/^void //' \ 37 | -e 's/(.*$//' \ 38 | -e 's/^/ SUITE_ADD_TEST(suite, /' \ 39 | -e 's/$/);/' 40 | 41 | echo \ 42 | ' 43 | CuSuiteRun(suite); 44 | CuSuiteDetails(suite, output); 45 | printf("%s\\n", output->buffer); 46 | } 47 | 48 | int main() 49 | { 50 | RunAllTests(); 51 | return 0; 52 | } 53 | ' 54 | -------------------------------------------------------------------------------- /tests/mock_send_functions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "gtest/gtest.h" 15 | 16 | #include "raft.h" 17 | #include "raft_server.h" 18 | 19 | typedef struct { 20 | void* data; 21 | int len; 22 | /* what type of message is it? */ 23 | int type; 24 | /* who sent this? */ 25 | int sender; 26 | } msg_t; 27 | 28 | typedef struct { 29 | std::deque* outbox; 30 | std::deque* inbox; 31 | RaftServer* raft; 32 | } sender_t; 33 | 34 | static sender_t** __senders = NULL; 35 | static int __nsenders = 0; 36 | 37 | void senders_new() 38 | { 39 | __senders = NULL; 40 | __nsenders = 0; 41 | } 42 | 43 | int sender_send(void* caller, void* udata, int peer, int type, 44 | const unsigned char* data, int len) 45 | { 46 | sender_t* me = (sender_t*)caller; 47 | msg_t* m; 48 | 49 | m = (msg_t*)malloc(sizeof(msg_t)); 50 | m->type = type; 51 | m->len = len; 52 | m->data = malloc(len); 53 | m->sender = reinterpret_cast(udata)->get_nodeid(); 54 | memcpy(m->data,data,len); 55 | me->outbox->push_back(m); 56 | 57 | if (__nsenders > peer) 58 | { 59 | __senders[peer]->inbox->push_back(m); 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | void* sender_new(void* address) 66 | { 67 | sender_t* me; 68 | 69 | me = (sender_t*) malloc(sizeof(sender_t)); 70 | me->outbox = new std::deque(); 71 | me->inbox = new std::deque(); 72 | __senders = (sender_t**)realloc(__senders,sizeof(sender_t*) * (++__nsenders)); 73 | __senders[__nsenders-1] = me; 74 | return me; 75 | } 76 | 77 | void* sender_poll_msg_data(void* s) 78 | { 79 | sender_t* me = (sender_t*)s; 80 | msg_t* msg; 81 | 82 | void* retVal = !me->outbox->empty() ? me->outbox->back()->data : NULL; 83 | if (!me->outbox->empty()) me->outbox->pop_back(); 84 | return retVal; 85 | } 86 | 87 | void sender_set_raft(void* s, void* r) 88 | { 89 | sender_t* me = (sender_t*)s; 90 | me->raft = reinterpret_cast(r); 91 | } 92 | 93 | int sender_msgs_available(void* s) 94 | { 95 | sender_t* me = (sender_t*)s; 96 | 97 | return !me->inbox->empty(); 98 | } 99 | 100 | void sender_poll_msgs(void* s) 101 | { 102 | sender_t* me = (sender_t*)s; 103 | msg_t* m; 104 | 105 | while (!me->inbox->empty()) 106 | { 107 | m = me->inbox->back(); 108 | switch (m->type) 109 | { 110 | case RAFT_MSG_APPENDENTRIES: 111 | me->raft->recv_appendentries(m->sender, (MsgAppendEntries*) m->data); 112 | break; 113 | case RAFT_MSG_APPENDENTRIES_RESPONSE: 114 | me->raft->recv_appendentries_response(m->sender, (msg_appendentries_response_t*) m->data); 115 | break; 116 | case RAFT_MSG_REQUESTVOTE: 117 | me->raft->recv_requestvote(m->sender, (msg_requestvote_t*) m->data); 118 | break; 119 | case RAFT_MSG_REQUESTVOTE_RESPONSE: 120 | me->raft->recv_requestvote_response(m->sender, (msg_requestvote_response_t*) m->data); 121 | break; 122 | case RAFT_MSG_ENTRY: 123 | me->raft->recv_entry(m->sender, (msg_entry_t*) m->data); 124 | break; 125 | case RAFT_MSG_ENTRY_RESPONSE: 126 | //me->raft->recv_entry_response(m->sender, m->data); 127 | break; 128 | 129 | } 130 | me->inbox->pop_back(); 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /tests/mock_send_functions.h: -------------------------------------------------------------------------------- 1 | 2 | int sender_send(void* caller, void* udata, int peer, int type, 3 | const unsigned char* data, const int len); 4 | 5 | void senders_new(); 6 | 7 | void* sender_new(void* address); 8 | 9 | void* sender_poll_msg_data(void* s); 10 | 11 | int sender_msgs_available(void* s); 12 | 13 | void sender_set_raft(void* s, void* r); 14 | 15 | void sender_poll_msgs(void* s); 16 | -------------------------------------------------------------------------------- /tests/test_log.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "gtest/gtest.h" 9 | 10 | #include "raft.h" 11 | #include "raft_msg.h" 12 | #include "raft_logger.h" 13 | #include "raft_private.h" 14 | 15 | TEST(RaftLogger,new_is_empty) 16 | { 17 | RaftLogger *l; 18 | 19 | l = new RaftLogger(); 20 | ASSERT_TRUE(0 == l->log_count()); 21 | } 22 | 23 | TEST(RaftLogger,append_is_not_empty) 24 | { 25 | RaftLogger *l; 26 | raft_entry_t e; 27 | 28 | e.d_id = 1; 29 | 30 | l = new RaftLogger(); 31 | ASSERT_TRUE(1 == l->log_append_entry( e)); 32 | ASSERT_TRUE(1 == l->log_count()); 33 | } 34 | 35 | TEST(RaftLogger,get_at_idx) 36 | { 37 | RaftLogger *l; 38 | raft_entry_t e1, e2, e3; 39 | 40 | l = new RaftLogger(); 41 | e1.d_id = 1; 42 | ASSERT_TRUE(1 == l->log_append_entry( e1)); 43 | e2.d_id = 2; 44 | ASSERT_TRUE(1 == l->log_append_entry( e2)); 45 | e3.d_id = 3; 46 | ASSERT_TRUE(1 == l->log_append_entry( e3)); 47 | ASSERT_TRUE(3 == l->log_count()); 48 | 49 | ASSERT_TRUE(3 == l->log_count()); 50 | ASSERT_TRUE(e2.d_id == l->log_get_from_idx(2).d_id); 51 | } 52 | 53 | TEST(RaftLogger,get_at_idx_returns_null_where_out_of_bounds) 54 | { 55 | RaftLogger *l; 56 | raft_entry_t e1, e2, e3; 57 | 58 | l = new RaftLogger(); 59 | e1.d_id = 1; 60 | ASSERT_TRUE(1 == l->log_append_entry( e1)); 61 | ASSERT_THROW(l->log_get_from_idx(2),std::runtime_error); 62 | } 63 | 64 | TEST(RaftLogger,mark_node_has_committed_adds_nodes) 65 | { 66 | RaftLogger *l; 67 | raft_entry_t e1, e2, e3; 68 | 69 | l = new RaftLogger(); 70 | e1.d_id = 1; 71 | l->log_append_entry( e1); 72 | ASSERT_TRUE(0 == l->log_get_from_idx(1).d_num_nodes); 73 | l->log_mark_node_has_committed( 1); 74 | ASSERT_TRUE(1 == l->log_get_from_idx(1).d_num_nodes); 75 | l->log_mark_node_has_committed( 1); 76 | ASSERT_TRUE(2 == l->log_get_from_idx(1).d_num_nodes); 77 | } 78 | 79 | TEST(RaftLogger,delete) 80 | { 81 | RaftLogger *l; 82 | raft_entry_t e1, e2, e3; 83 | 84 | l = new RaftLogger(); 85 | e1.d_id = 1; 86 | ASSERT_TRUE(1 == l->log_append_entry( e1)); 87 | e2.d_id = 2; 88 | ASSERT_TRUE(1 == l->log_append_entry( e2)); 89 | e3.d_id = 3; 90 | ASSERT_TRUE(1 == l->log_append_entry( e3)); 91 | ASSERT_TRUE(3 == l->log_count()); 92 | 93 | l->log_delete(3); 94 | ASSERT_TRUE(2 == l->log_count()); 95 | ASSERT_THROW(l->log_get_from_idx(3),std::runtime_error); 96 | l->log_delete(2); 97 | ASSERT_TRUE(1 == l->log_count()); 98 | ASSERT_THROW(l->log_get_from_idx(2),std::runtime_error); 99 | l->log_delete(1); 100 | ASSERT_TRUE(0 == l->log_count()); 101 | ASSERT_THROW(l->log_get_from_idx(1),std::runtime_error); 102 | } 103 | 104 | TEST(RaftLogger,delete_onwards) 105 | { 106 | RaftLogger *l; 107 | raft_entry_t e1, e2, e3; 108 | 109 | l = new RaftLogger(); 110 | e1.d_id = 1; 111 | ASSERT_TRUE(1 == l->log_append_entry( e1)); 112 | e2.d_id = 2; 113 | ASSERT_TRUE(1 == l->log_append_entry( e2)); 114 | e3.d_id = 3; 115 | ASSERT_TRUE(1 == l->log_append_entry( e3)); 116 | ASSERT_TRUE(3 == l->log_count()); 117 | 118 | /* even 3 gets deleted */ 119 | l->log_delete(2); 120 | ASSERT_TRUE(1 == l->log_count()); 121 | ASSERT_TRUE(e1.d_id == l->log_get_from_idx(1).d_id); 122 | ASSERT_THROW(l->log_get_from_idx(2),std::runtime_error); 123 | ASSERT_THROW(l->log_get_from_idx(3),std::runtime_error); 124 | } 125 | 126 | TEST(RaftLogger,peektail) 127 | { 128 | RaftLogger *l; 129 | raft_entry_t e1, e2, e3; 130 | 131 | l = new RaftLogger(); 132 | e1.d_id = 1; 133 | ASSERT_TRUE(1 == l->log_append_entry( e1)); 134 | e2.d_id = 2; 135 | ASSERT_TRUE(1 == l->log_append_entry( e2)); 136 | e3.d_id = 3; 137 | ASSERT_TRUE(1 == l->log_append_entry( e3)); 138 | ASSERT_TRUE(3 == l->log_count()); 139 | ASSERT_TRUE(e3.d_id == l->log_peektail().d_id); 140 | } 141 | 142 | #if 0 143 | // TODO: duplicate testing not implemented yet 144 | void T_estlog_cant_append_duplicates() 145 | { 146 | RaftLogger *l; 147 | raft_entry_t e; 148 | 149 | e.d_id = 1; 150 | 151 | l = new RaftLogger(); 152 | ASSERT_TRUE(1 == l->log_append_entry( &e)); 153 | ASSERT_TRUE(1 == l->log_count()); 154 | } 155 | #endif 156 | 157 | -------------------------------------------------------------------------------- /tests/test_node.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "gtest/gtest.h" 9 | 10 | #include "raft.h" 11 | #include "raft_logger.h" 12 | #include "raft_node.h" 13 | #include "raft_private.h" 14 | 15 | TEST(RaftNode,node_set_nextIdx) 16 | { 17 | RaftNode *p; 18 | 19 | p = new RaftNode((void*)1); 20 | p->set_next_idx(3); 21 | ASSERT_TRUE(3 == p->get_next_idx()); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /tests/test_scenario.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "gtest/gtest.h" 9 | 10 | #include "raft.h" 11 | #include "raft_server.h" 12 | #include "raft_logger.h" 13 | #include "raft_private.h" 14 | #include "mock_send_functions.h" 15 | 16 | static int NODE_ID_1 = 1; 17 | static int NODE_ID_2 = 2; 18 | static int NODE_ID_3 = 3; 19 | 20 | TEST(RaftScenario,leader_appears) 21 | { 22 | int i,j; 23 | RaftServer *r[3]; 24 | void* sender[3]; 25 | std::vector cfg = { 26 | {(-1),&NODE_ID_1}, 27 | {(-1),&NODE_ID_2}, 28 | {(-1),&NODE_ID_3}, 29 | {(-1),NULL}}; 30 | 31 | senders_new(); 32 | 33 | for (j=0;j<3;j++) 34 | { 35 | r[j] = new RaftServer(); 36 | sender[j] = sender_new(NULL); 37 | sender_set_raft(sender[j], r[j]); 38 | r[j]->set_election_timeout(500); 39 | r[j]->set_configuration(cfg,j); 40 | raft_cbs_t* cbs = new raft_cbs_t(); 41 | cbs->send = sender_send; 42 | cbs->log = NULL; 43 | r[j]->set_callbacks(cbs, sender[j]); 44 | } 45 | 46 | for (i=0;i<20;i++) 47 | { 48 | one_more_time: 49 | 50 | for (j=0;j<3;j++) 51 | sender_poll_msgs(sender[j]); 52 | 53 | for (j=0;j<3;j++) 54 | if (sender_msgs_available(sender[j])) 55 | goto one_more_time; 56 | 57 | for (j=0;j<3;j++) 58 | r[j]->periodic(100); 59 | } 60 | 61 | int leaders = 0; 62 | for (j=0;j<3;j++) 63 | { 64 | if (r[j]->get_state().is_leader()) 65 | leaders += 1; 66 | } 67 | 68 | ASSERT_TRUE(0 != leaders); 69 | ASSERT_TRUE(1 == leaders); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /tests/test_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "gtest/gtest.h" 8 | 9 | #include "../raft.h" 10 | #include "../raft_logger.h" 11 | #include "../raft_server.h" 12 | #include "../raft_node.h" 13 | #include "../raft_private.h" 14 | #include "mock_send_functions.h" 15 | 16 | static int NODE_ID_1 = 1; 17 | static int NODE_ID_2 = 2; 18 | static int NODE_ID_3 = 3; 19 | static int NODE_ID_4 = 4; 20 | static int NODE_ID_5 = 5; 21 | 22 | using namespace ::testing; 23 | 24 | // TODO: leader doesn't timeout and cause election 25 | 26 | TEST(RaftServer,voted_for_records_who_we_voted_for) 27 | { 28 | RaftServer r; 29 | r.vote(2); 30 | ASSERT_TRUE(2 == r.get_voted_for()); 31 | } 32 | 33 | TEST(RaftServer,idx_starts_at_1) 34 | { 35 | RaftServer r; 36 | ASSERT_TRUE(1 == r.get_current_idx()); 37 | } 38 | 39 | TEST(RaftServer,currentterm_defaults_to_0) 40 | { 41 | RaftServer r; 42 | ASSERT_TRUE(0 == r.get_current_term()); 43 | } 44 | 45 | TEST(RaftServer,set_currentterm_sets_term) 46 | { 47 | RaftServer r; 48 | r.set_current_term(5); 49 | ASSERT_TRUE(5 == r.get_current_term()); 50 | } 51 | 52 | TEST(RaftServer,voting_results_in_voting) 53 | { 54 | RaftServer r; 55 | r.vote(1); 56 | ASSERT_TRUE(1 == r.get_voted_for()); 57 | r.vote(9); 58 | ASSERT_TRUE(9 == r.get_voted_for()); 59 | } 60 | 61 | TEST(RaftServer,election_start_increments_term) 62 | { 63 | RaftServer r; 64 | r.set_current_term(1); 65 | r.election_start(); 66 | ASSERT_TRUE(2 == r.get_current_term()); 67 | } 68 | 69 | TEST(RaftServer,set_state) 70 | { 71 | RaftServer r; 72 | r.get_state().set(RAFT_STATE_LEADER); 73 | ASSERT_TRUE(RAFT_STATE_LEADER == r.get_state().get()); 74 | } 75 | 76 | TEST(RaftServer,starts_as_follower) 77 | { 78 | RaftServer r; 79 | ASSERT_TRUE(RAFT_STATE_FOLLOWER == r.get_state().get()); 80 | } 81 | 82 | TEST(RaftServer,starts_with_election_timeout_of_1000ms) 83 | { 84 | RaftServer r; 85 | ASSERT_TRUE(1000 == r.get_election_timeout()); 86 | } 87 | 88 | TEST(RaftServer,starts_with_request_timeout_of_200ms) 89 | { 90 | RaftServer r; 91 | ASSERT_TRUE(200 == r.get_request_timeout()); 92 | } 93 | 94 | TEST(RaftServer,entry_append_cant_append_if_id_is_zero) 95 | { 96 | 97 | raft_entry_t ety; 98 | char* str = const_cast("aaa"); 99 | 100 | ety.d_data = str; 101 | ety.d_len = 3; 102 | ety.d_id = 0; 103 | ety.d_term = 1; 104 | 105 | RaftServer r; 106 | ASSERT_TRUE(1 == r.get_current_idx()); 107 | r.append_entry(ety); 108 | ASSERT_TRUE(1 == r.get_current_idx()); 109 | } 110 | 111 | TEST(RaftServer,entry_append_increases_logidx) 112 | { 113 | 114 | raft_entry_t ety; 115 | char* str = const_cast("aaa"); 116 | 117 | ety.d_data = str; 118 | ety.d_len = 3; 119 | ety.d_id = 1; 120 | ety.d_term = 1; 121 | 122 | RaftServer r; 123 | ASSERT_TRUE(1 == r.get_current_idx()); 124 | r.append_entry(ety); 125 | ASSERT_TRUE(2 == r.get_current_idx()); 126 | } 127 | 128 | TEST(RaftServer,append_entry_means_entry_gets_current_term) 129 | { 130 | 131 | raft_entry_t ety; 132 | char* str = const_cast("aaa"); 133 | 134 | ety.d_data = str; 135 | ety.d_len = 3; 136 | ety.d_id = 1; 137 | ety.d_term = 1; 138 | 139 | RaftServer r; 140 | ASSERT_TRUE(1 == r.get_current_idx()); 141 | r.append_entry(ety); 142 | ASSERT_TRUE(2 == r.get_current_idx()); 143 | } 144 | 145 | #if 0 146 | /* TODO: no support for duplicate detection yet */ 147 | void T_estRaft_server_append_entry_not_sucessful_if_entry_with_id_already_appended() 148 | { 149 | 150 | raft_entry_t ety; 151 | char* str = const_cast("aaa"); 152 | 153 | ety.d_data = str; 154 | ety.d_len = 3; 155 | ety.d_id = 1; 156 | ety.d_term = 1; 157 | 158 | RaftServer r; 159 | ASSERT_TRUE(1 == r.get_current_idx()); 160 | r.append_entry(ety); 161 | r.append_entry(ety); 162 | ASSERT_TRUE(2 == r.get_current_idx()); 163 | 164 | /* different ID so we can be successful */ 165 | ety.d_id = 2; 166 | r.append_entry(ety); 167 | ASSERT_TRUE(3 == r.get_current_idx()); 168 | } 169 | #endif 170 | 171 | TEST(RaftServer,entry_is_retrieveable_using_idx) 172 | { 173 | 174 | raft_entry_t e1; 175 | raft_entry_t e2; 176 | raft_entry_t ety_appended; 177 | char* str = const_cast("aaa"); 178 | char* str2 = const_cast("bbb"); 179 | 180 | RaftServer r; 181 | 182 | e1.d_term = 1; 183 | e1.d_id = 1; 184 | e1.d_data = str; 185 | e1.d_len = 3; 186 | r.append_entry(e1); 187 | 188 | /* different ID so we can be successful */ 189 | e2.d_term = 1; 190 | e2.d_id = 2; 191 | e2.d_data = str2; 192 | e2.d_len = 3; 193 | r.append_entry(e2); 194 | 195 | ASSERT_NO_THROW(ety_appended = r.get_entry_from_idx(2)); 196 | ASSERT_TRUE(!strncmp(reinterpret_cast(ety_appended.d_data),reinterpret_cast(str2),3)); 197 | } 198 | 199 | TEST(RaftServer,wont_apply_entry_if_we_dont_have_entry_to_apply) 200 | { 201 | 202 | raft_entry_t ety; 203 | raft_entry_t *ety_appended; 204 | char* str = const_cast("aaa"); 205 | 206 | void *sender = sender_new(NULL); 207 | raft_cbs_t funcs = { 208 | sender_send, 209 | NULL 210 | }; 211 | RaftServer r; 212 | r.set_callbacks(&funcs,sender); 213 | 214 | r.set_commit_idx(0); 215 | r.set_last_applied_idx(0); 216 | 217 | r.apply_entry(); 218 | ASSERT_TRUE(0 == r.get_last_applied_idx()); 219 | ASSERT_TRUE(0 == r.get_commit_idx()); 220 | 221 | ety.d_term = 1; 222 | ety.d_id = 1; 223 | ety.d_data = str; 224 | ety.d_len = 3; 225 | r.append_entry(ety); 226 | r.apply_entry(); 227 | ASSERT_TRUE(1 == r.get_last_applied_idx()); 228 | ASSERT_TRUE(1 == r.get_commit_idx()); 229 | } 230 | 231 | /* If commitidx > lastApplied: increment lastApplied, apply log[lastApplied] 232 | * to state machine (�5.3) */ 233 | TEST(RaftServer,increment_lastApplied_when_lastApplied_lt_commitidx) 234 | { 235 | 236 | raft_entry_t ety; 237 | 238 | RaftServer r; 239 | raft_cbs_t funcs = { 240 | sender_send, 241 | NULL 242 | }; 243 | void* sender = sender_new(NULL); 244 | r.set_callbacks(&funcs,sender); 245 | /* must be follower */ 246 | r.get_state().set(RAFT_STATE_FOLLOWER); 247 | r.set_current_term(1); 248 | r.set_commit_idx(1); 249 | r.set_last_applied_idx(0); 250 | 251 | /* need at least one entry */ 252 | ety.d_term = 1; 253 | ety.d_id = 1; 254 | ety.d_data = const_cast("aaa"); 255 | ety.d_len = 3; 256 | r.append_entry(ety); 257 | 258 | /* let time lapse */ 259 | r.periodic(1); 260 | ASSERT_TRUE(0 != r.get_last_applied_idx()); 261 | ASSERT_TRUE(1 == r.get_last_applied_idx()); 262 | } 263 | 264 | TEST(RaftServer,apply_entry_increments_last_applied_idx) 265 | { 266 | 267 | raft_entry_t ety; 268 | raft_entry_t *ety_appended; 269 | char* str = const_cast("aaa"); 270 | 271 | ety.d_term = 1; 272 | 273 | RaftServer r; 274 | raft_cbs_t funcs = { 275 | sender_send, 276 | NULL 277 | }; 278 | void* sender = sender_new(NULL); 279 | r.set_callbacks(&funcs,sender); 280 | r.set_commit_idx(1); 281 | r.set_last_applied_idx(0); 282 | 283 | ety.d_id = 1; 284 | ety.d_data = str; 285 | ety.d_len = 3; 286 | r.append_entry(ety); 287 | r.apply_entry(); 288 | ASSERT_TRUE(1 == r.get_last_applied_idx()); 289 | } 290 | 291 | TEST(RaftServer,periodic_elapses_election_timeout) 292 | { 293 | RaftServer r; 294 | /* we don't want to set the timeout to zero */ 295 | r.set_election_timeout(1000); 296 | ASSERT_TRUE(0 == r.get_timeout_elapsed()); 297 | 298 | r.periodic(0); 299 | ASSERT_TRUE(0 == r.get_timeout_elapsed()); 300 | 301 | r.periodic(100); 302 | ASSERT_TRUE(100 == r.get_timeout_elapsed()); 303 | } 304 | 305 | TEST(RaftServer,election_timeout_sets_to_zero_when_elapsed_time_greater_than_timeout) 306 | { 307 | RaftServer r; 308 | r.set_election_timeout(1000); 309 | 310 | /* greater than 1000 */ 311 | r.periodic(2000); 312 | /* less than 1000 as the timeout would be randomised */ 313 | ASSERT_TRUE(r.get_timeout_elapsed() < 1000); 314 | } 315 | 316 | TEST(RaftServer,cfg_sets_num_nodes) 317 | { 318 | /* 2 nodes */ 319 | std::vector cfg = { 320 | {(-1),&NODE_ID_1}, 321 | {(-1),&NODE_ID_2}}; 322 | RaftServer r; 323 | r.set_configuration(cfg,0); 324 | 325 | ASSERT_TRUE(2 == r.get_num_nodes()); 326 | } 327 | 328 | TEST(RaftServer,cant_get_node_we_dont_have) 329 | { 330 | /* 2 nodes */ 331 | std::vector cfg = { 332 | {(-1),&NODE_ID_1}, 333 | {(-1),&NODE_ID_2}}; 334 | 335 | RaftServer r; 336 | r.set_configuration(cfg,0); 337 | 338 | ASSERT_TRUE(r.get_last_node() != r.get_node(0)); 339 | ASSERT_TRUE(r.get_last_node() != r.get_node(1)); 340 | ASSERT_TRUE(r.get_last_node() == r.get_node(2)); 341 | } 342 | 343 | /* If term > currentTerm, set currentTerm to term (step down if candidate or 344 | * leader) */ 345 | TEST(RaftServer,votes_are_majority_is_true) 346 | { 347 | /* 1 of 3 = lose */ 348 | ASSERT_TRUE(0 == raft_votes_is_majority(3,1)); 349 | 350 | /* 2 of 3 = win */ 351 | ASSERT_TRUE(1 == raft_votes_is_majority(3,2)); 352 | 353 | /* 2 of 5 = lose */ 354 | ASSERT_TRUE(0 == raft_votes_is_majority(5,2)); 355 | 356 | /* 3 of 5 = win */ 357 | ASSERT_TRUE(1 == raft_votes_is_majority(5,3)); 358 | 359 | /* 2 of 1?? This is an error */ 360 | ASSERT_TRUE(0 == raft_votes_is_majority(1,2)); 361 | } 362 | 363 | TEST(RaftServer,dont_increase_votes_for_me_when_receive_request_vote_response_is_not_granted) 364 | { 365 | 366 | msg_requestvote_response_t rvr; 367 | 368 | /* 2 nodes */ 369 | std::vector cfg = { 370 | {(-1),&NODE_ID_1}, 371 | {(-1),&NODE_ID_2}}; 372 | 373 | RaftServer r; 374 | r.set_configuration(cfg,0); 375 | r.set_current_term(1); 376 | ASSERT_TRUE(0 == r.get_nvotes_for_me()); 377 | 378 | memset(&rvr, 0, sizeof(msg_requestvote_response_t)); 379 | rvr.term = 1; 380 | rvr.vote_granted = 0; 381 | r.recv_requestvote_response(1,&rvr); 382 | ASSERT_TRUE(0 == r.get_nvotes_for_me()); 383 | } 384 | 385 | TEST(RaftServer,increase_votes_for_me_when_receive_request_vote_response) 386 | { 387 | 388 | msg_requestvote_response_t rvr; 389 | 390 | /* 2 nodes */ 391 | std::vector cfg = { 392 | {(-1),&NODE_ID_1}, 393 | {(-1),&NODE_ID_2}}; 394 | 395 | RaftServer r; 396 | r.set_configuration(cfg,0); 397 | r.set_current_term(1); 398 | ASSERT_TRUE(0 == r.get_nvotes_for_me()); 399 | 400 | memset(&rvr, 0, sizeof(msg_requestvote_response_t)); 401 | rvr.term = 1; 402 | rvr.vote_granted = 1; 403 | r.recv_requestvote_response(1,&rvr); 404 | ASSERT_TRUE(1 == r.get_nvotes_for_me()); 405 | } 406 | 407 | /* Reply false if term < currentTerm (�5.1) */ 408 | TEST(RaftServer,recv_requestvote_reply_false_if_term_less_than_current_term) 409 | { 410 | 411 | void *sender; 412 | raft_cbs_t funcs = { 413 | sender_send, 414 | NULL 415 | }; 416 | msg_requestvote_t rv(1,0,0,0); 417 | msg_requestvote_response_t *rvr; 418 | 419 | /* 2 nodes */ 420 | std::vector cfg = { 421 | {(-1),&NODE_ID_1}, 422 | {(-1),&NODE_ID_2}}; 423 | 424 | RaftServer r; 425 | r.set_configuration(cfg,0); 426 | sender = sender_new(NULL); 427 | r.set_callbacks(&funcs,sender); 428 | r.set_current_term(2); 429 | 430 | r.recv_requestvote(1,&rv); 431 | 432 | rvr = reinterpret_cast(sender_poll_msg_data(sender)); 433 | ASSERT_TRUE(NULL != rvr); 434 | ASSERT_TRUE(0 == rvr->vote_granted); 435 | } 436 | 437 | /* If votedFor is null or candidateId, and candidate's log is at 438 | * least as up-to-date as local log, grant vote (�5.2, �5.4) */ 439 | TEST(RaftServer,dont_grant_vote_if_we_didnt_vote_for_this_candidate) 440 | { 441 | 442 | void *sender; 443 | raft_cbs_t funcs = { 444 | sender_send, 445 | NULL 446 | }; 447 | msg_requestvote_t rv(1,1,0,1); 448 | msg_requestvote_response_t *rvr; 449 | 450 | /* 2 nodes */ 451 | std::vector cfg = { 452 | {(-1),&NODE_ID_1}, 453 | {(-1),&NODE_ID_2}}; 454 | 455 | 456 | sender = sender_new(NULL); 457 | RaftServer r; 458 | r.set_configuration(cfg,0); 459 | r.set_callbacks(&funcs,sender); 460 | 461 | r.vote(0); 462 | r.recv_requestvote(1,&rv); 463 | 464 | rvr = reinterpret_cast(sender_poll_msg_data(sender)); 465 | ASSERT_TRUE(NULL != rvr); 466 | ASSERT_TRUE(0 == rvr->vote_granted); 467 | } 468 | 469 | TEST(RaftFollower,becomes_follower_is_follower) 470 | { 471 | RaftServer r; 472 | 473 | r.become_follower(); 474 | ASSERT_TRUE(r.get_state().is_follower()); 475 | } 476 | 477 | TEST(RaftFollower,becomes_follower_clears_voted_for) 478 | { 479 | RaftServer r; 480 | r.vote(1); 481 | ASSERT_TRUE(1 == r.get_voted_for()); 482 | r.become_follower(); 483 | ASSERT_TRUE(-1 == r.get_voted_for()); 484 | } 485 | 486 | /* 5.1 */ 487 | TEST(RaftFollower,recv_appendentries_reply_false_if_term_less_than_currentterm) 488 | { 489 | 490 | void *sender; 491 | raft_cbs_t funcs = { 492 | sender_send, 493 | NULL 494 | }; 495 | msg_appendentries_response_t *aer; 496 | 497 | /* 2 nodes */ 498 | std::vector cfg = { 499 | {(-1),&NODE_ID_1}, 500 | {(-1),&NODE_ID_2}}; 501 | 502 | 503 | RaftServer r; 504 | r.set_configuration(cfg,0); 505 | sender = sender_new(NULL); 506 | r.set_callbacks(&funcs,sender); 507 | 508 | /* term is low */ 509 | MsgAppendEntries ae(1,0,0,0,0,0); 510 | 511 | /* higher current term */ 512 | r.set_current_term(5); 513 | r.recv_appendentries(1,&ae); 514 | 515 | /* response is false */ 516 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 517 | ASSERT_TRUE(NULL != aer); 518 | ASSERT_TRUE(0 == aer->success); 519 | } 520 | 521 | /* TODO: check if test case is needed */ 522 | TEST(RaftFollower,recv_appendentries_updates_currentterm_if_term_gt_currentterm) 523 | { 524 | 525 | void *sender; 526 | msg_appendentries_response_t *aer; 527 | 528 | raft_cbs_t funcs = { 529 | sender_send, 530 | NULL 531 | }; 532 | 533 | /* 2 nodes */ 534 | std::vector cfg = { 535 | {(-1),&NODE_ID_1}, 536 | {(-1),&NODE_ID_2}}; 537 | 538 | RaftServer r; 539 | r.set_configuration(cfg,0); 540 | sender = sender_new(NULL); 541 | r.set_callbacks(&funcs,sender); 542 | 543 | /* older currentterm */ 544 | r.set_current_term(1); 545 | 546 | /* newer term for appendentry */ 547 | /* no prev log idx */ 548 | MsgAppendEntries ae (2,0,0,0,0,0); 549 | 550 | /* appendentry has newer term, so we change our currentterm */ 551 | r.recv_appendentries(1,&ae); 552 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 553 | ASSERT_TRUE(NULL != aer); 554 | ASSERT_TRUE(1 == aer->success); 555 | /* term has been updated */ 556 | ASSERT_TRUE(2 == r.get_current_term()); 557 | } 558 | 559 | TEST(RaftFollower,doesnt_log_after_appendentry_if_no_entries_are_specified) 560 | { 561 | /* 2 nodes */ 562 | std::vector cfg = { 563 | {(-1),&NODE_ID_1}, 564 | {(-1),&NODE_ID_2}}; 565 | 566 | RaftServer r; 567 | r.set_configuration(cfg,0); 568 | raft_cbs_t funcs = { 569 | sender_send, 570 | NULL 571 | }; 572 | void* sender = sender_new(NULL); 573 | r.set_callbacks(&funcs,sender); 574 | 575 | r.get_state().set(RAFT_STATE_FOLLOWER); 576 | ASSERT_TRUE(0 == r.get_log_count()); 577 | 578 | /* receive an appendentry with commit */ 579 | MsgAppendEntries ae(1,0,4,1,0,5); 580 | 581 | r.recv_appendentries(1,&ae); 582 | ASSERT_TRUE(0 == r.get_log_count()); 583 | } 584 | 585 | TEST(RaftFollower,increases_log_after_appendentry) 586 | { 587 | 588 | void *sender; 589 | msg_appendentries_response_t *aer; 590 | char* str = const_cast("aaa"); 591 | 592 | raft_cbs_t funcs = { 593 | sender_send, 594 | NULL 595 | }; 596 | 597 | /* 2 nodes */ 598 | std::vector cfg = { 599 | {(-1),&NODE_ID_1}, 600 | {(-1),&NODE_ID_2}}; 601 | 602 | RaftServer r; 603 | r.set_configuration(cfg,0); 604 | sender = sender_new(NULL); 605 | r.set_callbacks(&funcs,sender); 606 | 607 | r.get_state().set(RAFT_STATE_FOLLOWER); 608 | 609 | /* log size s */ 610 | ASSERT_TRUE(0 == r.get_log_count()); 611 | 612 | /* receive an appendentry with commit */ 613 | 614 | /* include one entry */ 615 | msg_entry_t ety(1,reinterpret_cast(str),3); 616 | MsgAppendEntries ae(1,0,0,1,1,5); 617 | ae.addEntry(ety); 618 | 619 | r.recv_appendentries(1,&ae); 620 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 621 | ASSERT_TRUE(NULL != aer); 622 | ASSERT_TRUE(1 == aer->success); 623 | ASSERT_TRUE(1 == r.get_log_count()); 624 | } 625 | 626 | /* 5.3 */ 627 | TEST(RaftFollower,recv_appendentries_reply_false_if_doesnt_have_log_at_prev_log_idx_which_matches_prev_log_term) 628 | { 629 | 630 | void *sender; 631 | void *msg; 632 | char* str = const_cast("aaa"); 633 | raft_cbs_t funcs = { 634 | sender_send, 635 | NULL 636 | }; 637 | /* 2 nodes */ 638 | std::vector cfg = { 639 | {(-1),&NODE_ID_1}, 640 | {(-1),&NODE_ID_2}}; 641 | 642 | msg_appendentries_response_t *aer; 643 | 644 | sender = sender_new(NULL); 645 | RaftServer r; 646 | r.set_configuration(cfg,0); 647 | r.set_callbacks(&funcs,sender); 648 | 649 | /* term is different from appendentries */ 650 | r.set_current_term(2); 651 | r.set_commit_idx(1); 652 | r.set_last_applied_idx(1); 653 | // TODO at log manually? 654 | 655 | /* log idx that server doesn't have */ 656 | /* prev_log_term is less than current term (ie. 2) */ 657 | MsgAppendEntries ae(2,0,1,1,0,0); 658 | 659 | /* trigger reply */ 660 | r.recv_appendentries(1,&ae); 661 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 662 | 663 | /* reply is false */ 664 | ASSERT_TRUE(NULL != aer); 665 | ASSERT_TRUE(0 == aer->success); 666 | } 667 | 668 | /* 5.3 */ 669 | TEST(RaftFollower,recv_appendentries_delete_entries_if_conflict_with_new_entries) 670 | { 671 | 672 | void *sender; 673 | msg_appendentries_response_t *aer; 674 | raft_entry_t ety_appended; 675 | 676 | raft_cbs_t funcs = { 677 | sender_send, 678 | NULL 679 | }; 680 | 681 | /* 2 nodes */ 682 | std::vector cfg = { 683 | {(-1),&NODE_ID_1}, 684 | {(-1),&NODE_ID_2}}; 685 | 686 | RaftServer r; 687 | r.set_configuration(cfg,0); 688 | sender = sender_new(NULL); 689 | r.set_callbacks(&funcs,sender); 690 | 691 | r.set_current_term(1); 692 | 693 | raft_entry_t ety; 694 | 695 | /* increase log size */ 696 | char* str1 = const_cast("111"); 697 | ety.d_data = str1; 698 | ety.d_len = 3; 699 | ety.d_id = 1; 700 | ety.d_term = 1; 701 | r.append_entry(ety); 702 | ASSERT_TRUE(1 == r.get_log_count()); 703 | 704 | /* this log will be overwritten by the appendentries below */ 705 | raft_entry_t ety_extra; 706 | char* str2 = const_cast("222"); 707 | ety_extra.d_data = str2; 708 | ety_extra.d_len = 3; 709 | ety_extra.d_id = 2; 710 | ety_extra.d_term = 1; 711 | r.append_entry(ety_extra); 712 | ASSERT_TRUE(2 == r.get_log_count()); 713 | ASSERT_NO_THROW(ety_appended = r.get_entry_from_idx(2)); 714 | ASSERT_TRUE(!memcmp(ety_appended.d_data,str2,3)); 715 | 716 | 717 | /* include one entry */ 718 | char* str3 = const_cast("333"); 719 | /* pass a appendentry that is newer */ 720 | msg_entry_t mety(3,reinterpret_cast(str3),3); 721 | MsgAppendEntries ae(2,0,1,1,1,0); 722 | ae.addEntry(mety); 723 | 724 | r.recv_appendentries(1,&ae); 725 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 726 | ASSERT_TRUE(NULL != aer); 727 | ASSERT_TRUE(1 == aer->success); 728 | ASSERT_TRUE(2 == r.get_log_count()); 729 | ASSERT_NO_THROW(ety_appended = r.get_entry_from_idx(1)); 730 | ASSERT_TRUE(!memcmp(ety_appended.d_data,str1,3)); 731 | } 732 | 733 | TEST(RaftFollower,recv_appendentries_add_new_entries_not_already_in_log) 734 | { 735 | 736 | void *sender; 737 | raft_cbs_t funcs = { 738 | sender_send, 739 | NULL 740 | }; 741 | 742 | /* 2 nodes */ 743 | std::vector cfg = { 744 | {(-1),&NODE_ID_1}, 745 | {(-1),&NODE_ID_2}}; 746 | 747 | 748 | RaftServer r; 749 | sender = sender_new(NULL); 750 | r.set_configuration(cfg,0); 751 | r.set_current_term(1); 752 | r.set_callbacks(&funcs,sender); 753 | 754 | /* include entries */ 755 | msg_entry_t e[2]; 756 | memset(&e,0,sizeof(msg_entry_t) * 2); 757 | e[0].id(1); 758 | e[1].id(2); 759 | MsgAppendEntries ae(1,0,0,1,2,0); 760 | ae.addEntry(e[0]); 761 | ae.addEntry(e[1]); 762 | r.recv_appendentries(1,&ae); 763 | 764 | msg_appendentries_response_t *aer; 765 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 766 | ASSERT_TRUE(NULL != aer); 767 | ASSERT_TRUE(1 == aer->success); 768 | ASSERT_TRUE(2 == r.get_log_count()); 769 | } 770 | 771 | /* If leaderCommit > commitidx, set commitidx = 772 | * min(leaderCommit, last log idx) */ 773 | TEST(RaftFollower,recv_appendentries_set_commitidx_to_prevLogIdx) 774 | { 775 | 776 | void *sender; 777 | raft_cbs_t funcs = { 778 | sender_send, 779 | NULL 780 | }; 781 | 782 | /* 2 nodes */ 783 | std::vector cfg = { 784 | {(-1),&NODE_ID_1}, 785 | {(-1),&NODE_ID_2}}; 786 | 787 | sender = sender_new(NULL); 788 | RaftServer r; 789 | r.set_configuration(cfg,0); 790 | r.set_callbacks(&funcs,sender); 791 | 792 | /* include entries */ 793 | msg_entry_t e[4]; 794 | memset(&e,0,sizeof(msg_entry_t) * 4); 795 | e[0].id(1); 796 | e[1].id(2); 797 | e[2].id(3); 798 | e[3].id(4); 799 | MsgAppendEntries ae(1,0,0,1,4,0); 800 | ae.addEntry(e[0]); 801 | ae.addEntry(e[1]); 802 | ae.addEntry(e[2]); 803 | ae.addEntry(e[3]); 804 | r.recv_appendentries(1,&ae); 805 | 806 | /* receive an appendentry with commit */ 807 | ae.setTerm(1); 808 | ae.setPrevLogTerm(1); 809 | ae.setPrevLogIdx(4); 810 | ae.setLeaderCommit(5); 811 | /* receipt of appendentries changes commit idx */ 812 | r.recv_appendentries(1,&ae); 813 | 814 | msg_appendentries_response_t *aer; 815 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 816 | ASSERT_TRUE(NULL != aer); 817 | ASSERT_TRUE(1 == aer->success); 818 | /* set to 4 because commitIDX is lower */ 819 | ASSERT_TRUE(4 == r.get_commit_idx()); 820 | } 821 | 822 | TEST(RaftFollower,recv_appendentries_set_commitidx_to_LeaderCommit) 823 | { 824 | 825 | void *sender; 826 | raft_cbs_t funcs = { 827 | sender_send, 828 | NULL 829 | }; 830 | 831 | /* 2 nodes */ 832 | std::vector cfg = { 833 | {(-1),&NODE_ID_1}, 834 | {(-1),&NODE_ID_2}}; 835 | 836 | sender = sender_new(NULL); 837 | RaftServer r; 838 | r.set_configuration(cfg,0); 839 | r.set_callbacks(&funcs,sender); 840 | 841 | 842 | /* include entries */ 843 | msg_entry_t e[4]; 844 | memset(&e,0,sizeof(msg_entry_t) * 4); 845 | e[0].id(1); 846 | e[1].id(2); 847 | e[2].id(3); 848 | e[3].id(4); 849 | MsgAppendEntries ae(1,0,0,1,4,0); 850 | ae.addEntry(e[0]); 851 | ae.addEntry(e[1]); 852 | ae.addEntry(e[2]); 853 | ae.addEntry(e[3]); 854 | r.recv_appendentries(1,&ae); 855 | 856 | /* receive an appendentry with commit */ 857 | ae.setTerm(1); 858 | ae.setPrevLogTerm(1); 859 | ae.setPrevLogIdx(3); 860 | ae.setLeaderCommit(3); 861 | /* receipt of appendentries changes commit idx */ 862 | r.recv_appendentries(1,&ae); 863 | 864 | msg_appendentries_response_t *aer; 865 | aer = reinterpret_cast(sender_poll_msg_data(sender)); 866 | ASSERT_TRUE(NULL != aer); 867 | ASSERT_TRUE(1 == aer->success); 868 | /* set to 3 because leaderCommit is lower */ 869 | ASSERT_TRUE(3 == r.get_commit_idx()); 870 | } 871 | 872 | TEST(RaftFollower,becomes_candidate_when_election_timeout_occurs) 873 | { 874 | /* 2 nodes */ 875 | std::vector cfg = { 876 | {(-1),&NODE_ID_1}, 877 | {(-1),&NODE_ID_2}}; 878 | 879 | RaftServer r; 880 | raft_cbs_t funcs = { 881 | sender_send, 882 | NULL 883 | }; 884 | void* sender = sender_new(NULL); 885 | r.set_callbacks(&funcs,sender); 886 | 887 | /* 1 second election timeout */ 888 | r.set_election_timeout(1000); 889 | 890 | r.set_configuration(cfg,0); 891 | 892 | /* 1.001 seconds have passed */ 893 | r.periodic(1001); 894 | 895 | /* is a candidate now */ 896 | ASSERT_TRUE(1 == r.get_state().is_candidate()); 897 | } 898 | 899 | /* Candidate 5.2 */ 900 | TEST(RaftFollower,dont_grant_vote_if_candidate_has_a_less_complete_log) 901 | { 902 | 903 | void *sender; 904 | raft_cbs_t funcs = { 905 | sender_send, 906 | NULL 907 | }; 908 | 909 | /* 2 nodes */ 910 | std::vector cfg = { 911 | {(-1),&NODE_ID_1}, 912 | {(-1),&NODE_ID_2}}; 913 | 914 | 915 | msg_requestvote_t rv(1,1,1,1); 916 | msg_requestvote_response_t *rvr; 917 | 918 | sender = sender_new(NULL); 919 | RaftServer r; 920 | r.set_callbacks(&funcs,sender); 921 | r.set_configuration(cfg,0); 922 | 923 | /* server's term and idx are more up-to-date */ 924 | r.set_current_term(1); 925 | r.set_current_idx(2); 926 | 927 | /* vote not granted */ 928 | r.recv_requestvote(1,&rv); 929 | rvr = reinterpret_cast(sender_poll_msg_data(sender)); 930 | ASSERT_TRUE(NULL != rvr); 931 | ASSERT_TRUE(0 == rvr->vote_granted); 932 | } 933 | 934 | TEST(RaftCandidate,becomes_candidate_is_candidate) 935 | { 936 | RaftServer r; 937 | 938 | r.become_candidate(); 939 | ASSERT_TRUE(r.get_state().is_candidate()); 940 | } 941 | 942 | /* Candidate 5.2 */ 943 | TEST(RaftFollower,becoming_candidate_increments_current_term) 944 | { 945 | RaftServer r; 946 | 947 | ASSERT_TRUE(0 == r.get_current_term()); 948 | r.become_candidate(); 949 | ASSERT_TRUE(1 == r.get_current_term()); 950 | } 951 | 952 | /* Candidate 5.2 */ 953 | TEST(RaftFollower,becoming_candidate_votes_for_self) 954 | { 955 | RaftServer r; 956 | 957 | ASSERT_TRUE(-1 == r.get_voted_for()); 958 | r.become_candidate(); 959 | ASSERT_TRUE(r.get_nodeid() == r.get_voted_for()); 960 | ASSERT_TRUE(1 == r.get_nvotes_for_me()); 961 | } 962 | 963 | /* Candidate 5.2 */ 964 | TEST(RaftFollower,becoming_candidate_resets_election_timeout) 965 | { 966 | RaftServer r; 967 | r.set_election_timeout(1000); 968 | ASSERT_TRUE(0 == r.get_timeout_elapsed()); 969 | 970 | r.periodic(900); 971 | ASSERT_TRUE(900 == r.get_timeout_elapsed()); 972 | 973 | r.become_candidate(); 974 | /* time is selected randomly */ 975 | ASSERT_TRUE(r.get_timeout_elapsed() < 900); 976 | } 977 | 978 | TEST(RaftFollower,receiving_appendentries_resets_election_timeout) 979 | { 980 | raft_cbs_t funcs = { 981 | sender_send, 982 | NULL 983 | }; 984 | void* sender = sender_new(NULL); 985 | RaftServer r; 986 | r.set_callbacks(&funcs,sender); 987 | r.set_election_timeout(1000); 988 | 989 | r.periodic(900); 990 | 991 | MsgAppendEntries ae; 992 | ae.setTerm(1); 993 | r.recv_appendentries(1,&ae); 994 | ASSERT_TRUE(0 == r.get_timeout_elapsed()); 995 | } 996 | 997 | /* Candidate 5.2 */ 998 | TEST(RaftFollower,becoming_candidate_requests_votes_from_other_servers) 999 | { 1000 | 1001 | void *sender; 1002 | raft_cbs_t funcs = { 1003 | sender_send, 1004 | NULL 1005 | }; 1006 | std::vector cfg = { 1007 | {(-1),&NODE_ID_1}, 1008 | {(-1),&NODE_ID_2}, 1009 | {(-1),&NODE_ID_3}}; 1010 | msg_requestvote_t* rv; 1011 | 1012 | sender = sender_new(NULL); 1013 | RaftServer r; 1014 | r.set_callbacks(&funcs,sender); 1015 | r.set_configuration(cfg,0); 1016 | 1017 | /* set term so we can check it gets included in the outbound message */ 1018 | r.set_current_term(2); 1019 | r.set_current_idx(5); 1020 | 1021 | /* becoming candidate triggers vote requests */ 1022 | r.become_candidate(); 1023 | 1024 | /* 2 nodes = 2 vote requests */ 1025 | rv = reinterpret_cast(sender_poll_msg_data(sender)); 1026 | ASSERT_TRUE(NULL != rv); 1027 | ASSERT_TRUE(2 != rv->term()); 1028 | ASSERT_TRUE(3 == rv->term()); 1029 | /* TODO: there should be more items */ 1030 | rv = reinterpret_cast(sender_poll_msg_data(sender)); 1031 | ASSERT_TRUE(NULL != rv); 1032 | ASSERT_TRUE(3 == rv->term()); 1033 | } 1034 | 1035 | /* Candidate 5.2 */ 1036 | TEST(RaftCandidate,election_timeout_and_no_leader_results_in_new_election) 1037 | { 1038 | 1039 | void *sender; 1040 | raft_cbs_t funcs = { 1041 | sender_send, 1042 | NULL 1043 | }; 1044 | 1045 | /* 2 nodes */ 1046 | std::vector cfg = { 1047 | {(-1),&NODE_ID_1}, 1048 | {(-1),&NODE_ID_2}}; 1049 | 1050 | msg_requestvote_response_t vr; 1051 | 1052 | memset(&vr,0,sizeof(msg_requestvote_response_t)); 1053 | vr.term = 0; 1054 | vr.vote_granted = 1; 1055 | 1056 | sender = sender_new(NULL); 1057 | 1058 | RaftServer r; 1059 | r.set_configuration(cfg,0); 1060 | r.set_callbacks(&funcs,sender); 1061 | r.set_election_timeout(1000); 1062 | 1063 | /* server wants to be leader, so becomes candidate */ 1064 | r.become_candidate(); 1065 | ASSERT_TRUE(1 == r.get_current_term()); 1066 | 1067 | /* clock over (ie. 1000 + 1), causing new election */ 1068 | r.periodic(1001); 1069 | ASSERT_TRUE(2 == r.get_current_term()); 1070 | 1071 | /* receiving this vote gives the server majority */ 1072 | // r.recv_requestvote_response(1,&vr); 1073 | // ASSERT_TRUE(1 == r.get_state().is_leader()); 1074 | } 1075 | 1076 | /* Candidate 5.2 */ 1077 | TEST(RaftCandidate,receives_majority_of_votes_becomes_leader) 1078 | { 1079 | 1080 | void *sender; 1081 | raft_cbs_t funcs = { 1082 | sender_send, 1083 | NULL 1084 | }; 1085 | 1086 | /* 2 nodes */ 1087 | std::vector cfg = { 1088 | {(-1),&NODE_ID_1}, 1089 | {(-1),&NODE_ID_2}, 1090 | {(-1),&NODE_ID_3}, 1091 | {(-1),&NODE_ID_4}, 1092 | {(-1),&NODE_ID_5}}; 1093 | 1094 | msg_requestvote_response_t vr; 1095 | 1096 | sender = sender_new(NULL); 1097 | 1098 | RaftServer r; 1099 | r.set_configuration(cfg,0); 1100 | ASSERT_TRUE(5 == r.get_num_nodes()); 1101 | r.set_callbacks(&funcs,sender); 1102 | 1103 | /* vote for self */ 1104 | r.become_candidate(); 1105 | ASSERT_TRUE(1 == r.get_current_term()); 1106 | ASSERT_TRUE(1 == r.get_nvotes_for_me()); 1107 | 1108 | /* a vote for us */ 1109 | memset(&vr,0,sizeof(msg_requestvote_response_t)); 1110 | vr.term = 1; 1111 | vr.vote_granted = 1; 1112 | /* get one vote */ 1113 | r.recv_requestvote_response(1,&vr); 1114 | ASSERT_TRUE(2 == r.get_nvotes_for_me()); 1115 | ASSERT_TRUE(0 == r.get_state().is_leader()); 1116 | 1117 | /* get another vote 1118 | * now has majority (ie. 3/5 votes) */ 1119 | r.recv_requestvote_response(2,&vr); 1120 | ASSERT_TRUE(1 == r.get_state().is_leader()); 1121 | } 1122 | 1123 | /* Candidate 5.2 */ 1124 | TEST(RaftCandidate,will_not_respond_to_voterequest_if_it_has_already_voted) 1125 | { 1126 | 1127 | void *sender; 1128 | void *msg; 1129 | raft_cbs_t funcs = { 1130 | sender_send, 1131 | NULL 1132 | }; 1133 | 1134 | /* 2 nodes */ 1135 | std::vector cfg = { 1136 | {(-1),&NODE_ID_1}, 1137 | {(-1),&NODE_ID_2}}; 1138 | 1139 | msg_requestvote_response_t* rvr; 1140 | msg_requestvote_t rv(0,0,0,0); 1141 | 1142 | sender = sender_new(NULL); 1143 | RaftServer r; 1144 | r.set_configuration(cfg,0); 1145 | r.set_callbacks(&funcs,sender); 1146 | 1147 | r.vote(0); 1148 | 1149 | memset(&rv,0,sizeof(msg_requestvote_t)); 1150 | r.recv_requestvote(1,&rv); 1151 | 1152 | /* we've vote already, so won't respond with a vote granted... */ 1153 | rvr = reinterpret_cast(sender_poll_msg_data(sender)); 1154 | ASSERT_TRUE(0 == rvr->vote_granted); 1155 | } 1156 | 1157 | /* Candidate 5.2 */ 1158 | TEST(RaftCandidate,requestvote_includes_logidx) 1159 | { 1160 | 1161 | void *sender; 1162 | raft_cbs_t funcs = { 1163 | sender_send, 1164 | NULL 1165 | }; 1166 | msg_requestvote_t* rv; 1167 | 1168 | /* 2 nodes */ 1169 | std::vector cfg = { 1170 | {(-1),&NODE_ID_1}, 1171 | {(-1),&NODE_ID_2}}; 1172 | 1173 | sender = sender_new(NULL); 1174 | RaftServer r; 1175 | r.set_configuration(cfg,0); 1176 | r.get_state().set(RAFT_STATE_CANDIDATE); 1177 | 1178 | r.set_callbacks(&funcs,sender); 1179 | r.set_current_term(5); 1180 | r.set_current_idx(3); 1181 | r.send_requestvote(1); 1182 | 1183 | rv = reinterpret_cast(sender_poll_msg_data(sender)); 1184 | ASSERT_TRUE(NULL != rv); 1185 | ASSERT_TRUE(3 == rv->last_log_idx()); 1186 | ASSERT_TRUE(5 == rv->term()); 1187 | } 1188 | 1189 | /* Candidate 5.2 */ 1190 | TEST(RaftCandidate,recv_appendentries_frm_leader_results_in_follower) 1191 | { 1192 | 1193 | void *sender; 1194 | void *msg; 1195 | raft_cbs_t funcs = { 1196 | sender_send, 1197 | NULL 1198 | }; 1199 | 1200 | /* 2 nodes */ 1201 | std::vector cfg = { 1202 | {(-1),&NODE_ID_1}, 1203 | {(-1),&NODE_ID_2}}; 1204 | 1205 | sender = sender_new(NULL); 1206 | 1207 | RaftServer r; 1208 | r.set_configuration(cfg,0); 1209 | r.set_callbacks(&funcs,sender); 1210 | 1211 | r.get_state().set(RAFT_STATE_CANDIDATE); 1212 | ASSERT_TRUE(0 == r.get_state().is_follower()); 1213 | 1214 | /* receive recent appendentries */ 1215 | MsgAppendEntries ae(1,0,0,0,0,0); 1216 | r.recv_appendentries(1,&ae); 1217 | ASSERT_TRUE(1 == r.get_state().is_follower()); 1218 | } 1219 | 1220 | /* Candidate 5.2 */ 1221 | TEST(RaftCandidate,recv_appendentries_frm_invalid_leader_doesnt_result_in_follower) 1222 | { 1223 | 1224 | void *sender; 1225 | raft_cbs_t funcs = { 1226 | sender_send, 1227 | NULL 1228 | }; 1229 | 1230 | /* 2 nodes */ 1231 | std::vector cfg = { 1232 | {(-1),&NODE_ID_1}, 1233 | {(-1),&NODE_ID_2}}; 1234 | 1235 | sender = sender_new(NULL); 1236 | RaftServer r; 1237 | r.set_callbacks(&funcs,sender); 1238 | r.set_configuration(cfg,0); 1239 | 1240 | /* server's log is newer */ 1241 | r.set_current_term(1); 1242 | r.set_current_idx(2); 1243 | 1244 | /* is a candidate */ 1245 | r.get_state().set(RAFT_STATE_CANDIDATE); 1246 | ASSERT_TRUE(0 == r.get_state().is_follower()); 1247 | 1248 | /* invalid leader determined by "leaders" old log */ 1249 | MsgAppendEntries ae(1,0,1,1,0,0); 1250 | 1251 | /* appendentry from invalid leader doesn't make candidate become follower */ 1252 | r.recv_appendentries(1,&ae); 1253 | ASSERT_TRUE(1 == r.get_state().is_candidate()); 1254 | } 1255 | 1256 | TEST(RaftLeader,becomes_leader_is_leader) 1257 | { 1258 | RaftServer r; 1259 | 1260 | r.become_leader(); 1261 | ASSERT_TRUE(r.get_state().is_leader()); 1262 | } 1263 | 1264 | TEST(RaftLeader,becomes_leader_clears_voted_for) 1265 | { 1266 | RaftServer r; 1267 | r.vote(1); 1268 | ASSERT_TRUE(1 == r.get_voted_for()); 1269 | r.become_leader(); 1270 | ASSERT_TRUE(-1 == r.get_voted_for()); 1271 | } 1272 | 1273 | TEST(RaftLeader,when_becomes_leader_all_nodes_have_nextidx_equal_to_lastlog_idx_plus_1) 1274 | { 1275 | 1276 | void *sender; 1277 | raft_cbs_t funcs = { 1278 | sender_send, 1279 | NULL 1280 | }; 1281 | 1282 | /* 2 nodes */ 1283 | std::vector cfg = { 1284 | {(-1),&NODE_ID_1}, 1285 | {(-1),&NODE_ID_2}, 1286 | {(-1),&NODE_ID_3}}; 1287 | 1288 | MsgAppendEntries* ae; 1289 | 1290 | sender = sender_new(NULL); 1291 | RaftServer r; 1292 | r.set_callbacks(&funcs,sender); 1293 | r.set_configuration(cfg,0); 1294 | 1295 | /* candidate to leader */ 1296 | r.get_state().set(RAFT_STATE_CANDIDATE); 1297 | r.become_leader(); 1298 | 1299 | int i; 1300 | for (i=0; iget_next_idx()); 1305 | } 1306 | } 1307 | 1308 | /* 5.2 */ 1309 | TEST(RaftLeader,when_it_becomes_a_leader_sends_empty_appendentries) 1310 | { 1311 | 1312 | void *sender; 1313 | raft_cbs_t funcs = { 1314 | sender_send, 1315 | NULL 1316 | }; 1317 | 1318 | std::vector cfg = { 1319 | {(-1),&NODE_ID_1}, 1320 | {(-1),&NODE_ID_2}, 1321 | {(-1),&NODE_ID_3}}; 1322 | 1323 | MsgAppendEntries* ae; 1324 | 1325 | sender = sender_new(NULL); 1326 | RaftServer r; 1327 | r.set_callbacks(&funcs,sender); 1328 | r.set_configuration(cfg,0); 1329 | 1330 | /* candidate to leader */ 1331 | r.get_state().set(RAFT_STATE_CANDIDATE); 1332 | r.become_leader(); 1333 | 1334 | /* receive appendentries messages for both nodes */ 1335 | ae = reinterpret_cast(sender_poll_msg_data(sender)); 1336 | ASSERT_TRUE(NULL != ae); 1337 | ae = reinterpret_cast(sender_poll_msg_data(sender)); 1338 | ASSERT_TRUE(NULL != ae); 1339 | } 1340 | 1341 | /* 5.2 1342 | * Note: commit means it's been appended to the log, not applied to the FSM */ 1343 | TEST(RaftLeader,responds_to_entry_msg_when_entry_is_committed) 1344 | { 1345 | 1346 | void *sender; 1347 | msg_entry_response_t *cr; 1348 | raft_cbs_t funcs = { 1349 | sender_send, 1350 | NULL 1351 | }; 1352 | 1353 | /* 2 nodes */ 1354 | std::vector cfg = { 1355 | {(-1),&NODE_ID_1}, 1356 | {(-1),&NODE_ID_2}}; 1357 | 1358 | sender = sender_new(NULL); 1359 | RaftServer r; 1360 | r.set_callbacks(&funcs,sender); 1361 | r.set_configuration(cfg,0); 1362 | 1363 | /* I am the leader */ 1364 | r.get_state().set(RAFT_STATE_LEADER); 1365 | ASSERT_TRUE(0 == r.get_log_count()); 1366 | 1367 | /* entry message */ 1368 | msg_entry_t ety(1,(unsigned char*)"entry",strlen("entry")); 1369 | 1370 | /* receive entry */ 1371 | r.recv_entry(1,&ety); 1372 | ASSERT_TRUE(1 == r.get_log_count()); 1373 | 1374 | /* trigger response through commit */ 1375 | r.apply_entry(); 1376 | 1377 | /* leader sent response to entry message */ 1378 | cr = reinterpret_cast(sender_poll_msg_data(sender)); 1379 | ASSERT_TRUE(NULL != cr); 1380 | } 1381 | 1382 | /* 5.3 */ 1383 | TEST(RaftLeader,sends_appendentries_with_NextIdx_when_PrevIdx_gt_NextIdx) 1384 | { 1385 | 1386 | void *sender; 1387 | raft_cbs_t funcs = { 1388 | sender_send, 1389 | NULL 1390 | }; 1391 | 1392 | /* 2 nodes */ 1393 | std::vector cfg = { 1394 | {(-1),&NODE_ID_1}, 1395 | {(-1),&NODE_ID_2}}; 1396 | 1397 | 1398 | MsgAppendEntries* ae; 1399 | 1400 | sender = sender_new(NULL); 1401 | RaftServer r; 1402 | r.set_callbacks(&funcs,sender); 1403 | r.set_configuration(cfg,0); 1404 | 1405 | /* i'm leader */ 1406 | r.get_state().set(RAFT_STATE_LEADER); 1407 | 1408 | NodeIter p = r.get_node(0); 1409 | p->set_next_idx(4); 1410 | 1411 | /* receive appendentries messages */ 1412 | r.send_appendentries(0); 1413 | ae = reinterpret_cast(sender_poll_msg_data(sender)); 1414 | ASSERT_TRUE(NULL != ae); 1415 | } 1416 | 1417 | /* 5.3 */ 1418 | TEST(RaftLeader,retries_appendentries_with_decremented_NextIdx_log_inconsistency) 1419 | { 1420 | 1421 | void *sender; 1422 | raft_cbs_t funcs = { 1423 | sender_send, 1424 | NULL 1425 | }; 1426 | 1427 | /* 2 nodes */ 1428 | std::vector cfg = { 1429 | {(-1),&NODE_ID_1}, 1430 | {(-1),&NODE_ID_2}}; 1431 | 1432 | 1433 | MsgAppendEntries* ae; 1434 | 1435 | sender = sender_new(NULL); 1436 | RaftServer r; 1437 | r.set_callbacks(&funcs,sender); 1438 | r.set_configuration(cfg,0); 1439 | 1440 | /* i'm leader */ 1441 | r.get_state().set(RAFT_STATE_LEADER); 1442 | 1443 | /* receive appendentries messages */ 1444 | r.send_appendentries(0); 1445 | ae = reinterpret_cast(sender_poll_msg_data(sender)); 1446 | ASSERT_TRUE(NULL != ae); 1447 | } 1448 | 1449 | /* 1450 | * If there exists an N such that N > commitidx, a majority 1451 | * of matchidx[i] = N, and log[N].term == currentTerm: 1452 | * set commitidx = N (�5.2, �5.4). */ 1453 | TEST(RaftLeader,append_entry_to_log_increases_idxno) 1454 | { 1455 | /* 2 nodes */ 1456 | std::vector cfg = { 1457 | {(-1),&NODE_ID_1}, 1458 | {(-1),&NODE_ID_2}}; 1459 | 1460 | 1461 | msg_entry_t ety(1,(unsigned char*)"entry",strlen("entry")); 1462 | void *sender = sender_new(NULL); 1463 | raft_cbs_t funcs = { 1464 | sender_send, 1465 | NULL 1466 | }; 1467 | 1468 | RaftServer r; 1469 | r.set_configuration(cfg,0); 1470 | r.set_callbacks(&funcs,sender); 1471 | r.get_state().set(RAFT_STATE_LEADER); 1472 | ASSERT_TRUE(0 == r.get_log_count()); 1473 | 1474 | r.recv_entry(1,&ety); 1475 | ASSERT_TRUE(1 == r.get_log_count()); 1476 | } 1477 | 1478 | TEST(RaftLeader,increase_commit_idx_when_majority_have_entry_and_atleast_one_newer_entry) 1479 | { 1480 | 1481 | void *sender; 1482 | raft_cbs_t funcs = { 1483 | sender_send, 1484 | NULL 1485 | }; 1486 | msg_appendentries_response_t aer; 1487 | 1488 | /* 2 nodes */ 1489 | std::vector cfg = { 1490 | {(-1),&NODE_ID_1}, 1491 | {(-1),&NODE_ID_2}}; 1492 | sender = sender_new(NULL); 1493 | RaftServer r; 1494 | r.set_configuration(cfg,0); 1495 | r.set_callbacks(&funcs,sender); 1496 | 1497 | /* I'm the leader */ 1498 | r.get_state().set(RAFT_STATE_LEADER); 1499 | r.set_current_term(1); 1500 | r.set_commit_idx(0); 1501 | /* the last applied idx will became 1, and then 2 */ 1502 | r.set_last_applied_idx(0); 1503 | 1504 | /* append entries - we need two */ 1505 | raft_entry_t ety; 1506 | ety.d_term = 1; 1507 | ety.d_id = 1; 1508 | ety.d_data = "aaaa"; 1509 | ety.d_len = 4; 1510 | r.append_entry(ety); 1511 | ety.d_id = 2; 1512 | r.append_entry(ety); 1513 | 1514 | memset(&aer,0,sizeof(msg_appendentries_response_t)); 1515 | 1516 | /* FIRST entry log application */ 1517 | /* send appendentries - 1518 | * server will be waiting for response */ 1519 | r.send_appendentries(0); 1520 | r.send_appendentries(1); 1521 | /* receive mock success responses */ 1522 | aer.term = 1; 1523 | aer.success = 1; 1524 | aer.current_idx = 1; 1525 | aer.first_idx = 1; 1526 | r.recv_appendentries_response(0,&aer); 1527 | r.recv_appendentries_response(1,&aer); 1528 | /* leader will now have majority followers who have appended this log */ 1529 | ASSERT_TRUE(0 != r.get_commit_idx()); 1530 | ASSERT_TRUE(2 != r.get_commit_idx()); 1531 | ASSERT_TRUE(1 == r.get_commit_idx()); 1532 | ASSERT_TRUE(1 == r.get_last_applied_idx()); 1533 | 1534 | /* SECOND entry log application */ 1535 | /* send appendentries - 1536 | * server will be waiting for response */ 1537 | r.send_appendentries(0); 1538 | r.send_appendentries(1); 1539 | /* receive mock success responses */ 1540 | aer.term = 5; 1541 | aer.success = 1; 1542 | aer.current_idx = 2; 1543 | aer.first_idx = 2; 1544 | r.recv_appendentries_response(0,&aer); 1545 | r.recv_appendentries_response(1,&aer); 1546 | /* leader will now have majority followers who have appended this log */ 1547 | ASSERT_TRUE(2 == r.get_commit_idx()); 1548 | ASSERT_TRUE(2 == r.get_last_applied_idx()); 1549 | } 1550 | 1551 | TEST(RaftLeader,steps_down_if_received_appendentries_is_newer_than_itself) 1552 | { 1553 | 1554 | void *sender; 1555 | raft_cbs_t funcs = { 1556 | sender_send, 1557 | NULL 1558 | }; 1559 | 1560 | /* 2 nodes */ 1561 | std::vector cfg = { 1562 | {(-1),&NODE_ID_1}, 1563 | {(-1),&NODE_ID_2}}; 1564 | 1565 | sender = sender_new(NULL); 1566 | RaftServer r; 1567 | r.set_configuration(cfg,0); 1568 | 1569 | r.get_state().set(RAFT_STATE_LEADER); 1570 | r.set_current_term(5); 1571 | r.set_current_idx(5); 1572 | r.set_callbacks(&funcs,sender); 1573 | 1574 | MsgAppendEntries ae(5,0,6,5,0,0); 1575 | r.recv_appendentries(1,&ae); 1576 | 1577 | ASSERT_TRUE(1 == r.get_state().is_follower()); 1578 | } 1579 | 1580 | /* TODO: If a server receives a request with a stale term number, it rejects the request. */ 1581 | #if 0 1582 | void T_estRaft_leader_sends_appendentries_when_receive_entry_msg() 1583 | #endif 1584 | --------------------------------------------------------------------------------