├── .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 |
4 |
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 |
--------------------------------------------------------------------------------