evinrude 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +23 -0
  3. data/.gitignore +6 -0
  4. data/.yardopts +1 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/CONTRIBUTING.md +10 -0
  7. data/LICENCE +674 -0
  8. data/README.md +410 -0
  9. data/evinrude.gemspec +42 -0
  10. data/lib/evinrude.rb +1233 -0
  11. data/lib/evinrude/backoff.rb +19 -0
  12. data/lib/evinrude/cluster_configuration.rb +162 -0
  13. data/lib/evinrude/config_change_queue_entry.rb +19 -0
  14. data/lib/evinrude/config_change_queue_entry/add_node.rb +13 -0
  15. data/lib/evinrude/config_change_queue_entry/remove_node.rb +14 -0
  16. data/lib/evinrude/freedom_patches/range.rb +5 -0
  17. data/lib/evinrude/log.rb +102 -0
  18. data/lib/evinrude/log_entries.rb +3 -0
  19. data/lib/evinrude/log_entry.rb +13 -0
  20. data/lib/evinrude/log_entry/cluster_configuration.rb +15 -0
  21. data/lib/evinrude/log_entry/null.rb +6 -0
  22. data/lib/evinrude/log_entry/state_machine_command.rb +13 -0
  23. data/lib/evinrude/logging_helpers.rb +40 -0
  24. data/lib/evinrude/message.rb +19 -0
  25. data/lib/evinrude/message/append_entries_reply.rb +13 -0
  26. data/lib/evinrude/message/append_entries_request.rb +18 -0
  27. data/lib/evinrude/message/command_reply.rb +13 -0
  28. data/lib/evinrude/message/command_request.rb +18 -0
  29. data/lib/evinrude/message/install_snapshot_reply.rb +13 -0
  30. data/lib/evinrude/message/install_snapshot_request.rb +18 -0
  31. data/lib/evinrude/message/join_reply.rb +13 -0
  32. data/lib/evinrude/message/join_request.rb +18 -0
  33. data/lib/evinrude/message/node_removal_reply.rb +13 -0
  34. data/lib/evinrude/message/node_removal_request.rb +18 -0
  35. data/lib/evinrude/message/read_reply.rb +13 -0
  36. data/lib/evinrude/message/read_request.rb +18 -0
  37. data/lib/evinrude/message/vote_reply.rb +13 -0
  38. data/lib/evinrude/message/vote_request.rb +18 -0
  39. data/lib/evinrude/messages.rb +14 -0
  40. data/lib/evinrude/metrics.rb +50 -0
  41. data/lib/evinrude/network.rb +69 -0
  42. data/lib/evinrude/network/connection.rb +144 -0
  43. data/lib/evinrude/network/protocol.rb +69 -0
  44. data/lib/evinrude/node_info.rb +35 -0
  45. data/lib/evinrude/peer.rb +50 -0
  46. data/lib/evinrude/resolver.rb +96 -0
  47. data/lib/evinrude/snapshot.rb +9 -0
  48. data/lib/evinrude/state_machine.rb +15 -0
  49. data/lib/evinrude/state_machine/register.rb +25 -0
  50. data/smoke_tests/001_single_node_cluster.rb +20 -0
  51. data/smoke_tests/002_three_node_cluster.rb +43 -0
  52. data/smoke_tests/003_spill.rb +25 -0
  53. data/smoke_tests/004_stale_read.rb +67 -0
  54. data/smoke_tests/005_sleepy_master.rb +28 -0
  55. data/smoke_tests/006_join_via_follower.rb +26 -0
  56. data/smoke_tests/007_snapshot_madness.rb +97 -0
  57. data/smoke_tests/008_downsizing.rb +43 -0
  58. data/smoke_tests/009_disaster_recovery.rb +46 -0
  59. data/smoke_tests/999_final_smoke_test.rb +279 -0
  60. data/smoke_tests/run +22 -0
  61. data/smoke_tests/smoke_test_helper.rb +199 -0
  62. metadata +318 -0
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ readonly TESTSDIR="$(dirname "$(readlink -f "$0")")"
6
+ export RUBYLIB="$(dirname "$TESTSDIR")/lib"
7
+ readonly TMPDIR="$(dirname "$TESTSDIR")/tmp/smoke_test"
8
+
9
+ rm -rf "$TMPDIR"/*
10
+
11
+ for t in "$TESTSDIR"/[0-9]*.rb; do
12
+ testname="$(basename "$t" .rb)"
13
+ echo "Running $testname"
14
+
15
+ if ! ruby $t; then
16
+ echo "$testname FAILED!" >&2
17
+ echo "All the gory details are in $TMPDIR/$testname/log" >&2
18
+ exit 1
19
+ fi
20
+ done
21
+
22
+ echo "EPIC WIN!" >&2
@@ -0,0 +1,199 @@
1
+ require "evinrude"
2
+ require "logger"
3
+ require "fileutils"
4
+ require "pathname"
5
+
6
+ $tmpdir = Pathname.new($0).join("..", "..", "tmp", "smoke_test", File.basename($0, ".rb"))
7
+
8
+ FileUtils.mkdir_p($tmpdir)
9
+
10
+ def logger; default_logger; end
11
+
12
+ include Evinrude::LoggingHelpers
13
+
14
+ class ClusterNode
15
+ include Comparable
16
+
17
+ attr_reader :c, :t
18
+
19
+ def initialize(*args)
20
+ @c = Evinrude.new(*args)
21
+ @t = Thread.new do
22
+ begin
23
+ @c.run
24
+ rescue Exception => ex
25
+ log_exception(ex) { "Node thread crashed" }
26
+ @crashed = true
27
+ raise
28
+ end
29
+ end
30
+ @t.abort_on_exception = true
31
+
32
+ @crashed = false
33
+ end
34
+
35
+ def <=>(o)
36
+ @t.name.to_s <=> o.t.name.to_s
37
+ end
38
+
39
+ def crashed?
40
+ @crashed
41
+ end
42
+
43
+ def crashed!
44
+ @crashed = true
45
+ end
46
+ end
47
+
48
+ def default_logger
49
+ @default_logger ||= begin
50
+ Logger.new($tmpdir.join("log")).tap do |l|
51
+ l.formatter = ->(s, t, p, m) {
52
+ "#{t.strftime("%T.%N")} #{s[0]} #{$$}##{Thread.current.name || "??"} [#{p}] #{m}\n"
53
+ }
54
+ l.level = Logger.const_get(ENV.fetch("SMOKE_TEST_LOG_LEVEL", "DEBUG"))
55
+ end
56
+ end
57
+ end
58
+
59
+ def spawn_nodes(n, args: { shared_keys: ["s3kr1t"], logger: default_logger }, storage_base_dir: nil)
60
+ if storage_base_dir
61
+ args[:storage_dir] = File.join(storage_base_dir, "C1")
62
+ end
63
+
64
+ n1 = ClusterNode.new(**args.merge(join_hints: nil, node_name: "C1"))
65
+ n1.t.name = "C1"
66
+
67
+ until n1.c.port
68
+ n1.t.join(0.01)
69
+ end
70
+
71
+ [n1].tap do |nodes|
72
+ (2..n).each do |i|
73
+ if storage_base_dir
74
+ args[:storage_dir] = File.join(storage_base_dir, "C#{i}")
75
+ end
76
+
77
+ nodes << ClusterNode.new(**args.merge(join_hints: [{ address: n1.c.address, port: n1.c.port }], node_name: "C#{i}"))
78
+ nodes.last.t.name = "C#{i}"
79
+ end
80
+ end
81
+ end
82
+
83
+ def wait_for_stability(cluster)
84
+ loop do
85
+ cluster.each do |n|
86
+ until n.c.follower? || n.c.leader?
87
+ n.t.join(0.1)
88
+ end
89
+ n.__send__(:logger).info("wait_for_stability") { "#{n.t.name} is ready" }
90
+ end
91
+
92
+ if !cluster.any? { |n| n.c.leader? }
93
+ cluster.first.__send__(:logger).info("wait_for_stability") { "No leader yet" }
94
+ cluster.first.t.join(0.5)
95
+ else
96
+ cluster.first.__send__(:logger).info("wait_for_stability") { "Cluster is stable" }
97
+ return
98
+ end
99
+ end
100
+ end
101
+
102
+ def wait_for_consensus(cluster)
103
+ until leader = cluster.find { |n| n.c.leader? };
104
+ cluster.first.t.join(0.1)
105
+ end
106
+
107
+ until cluster.all? { |n| n.c.instance_variable_get(:@commit_index) == leader.c.instance_variable_get(:@log).last_index }
108
+ leader.t.join(0.1)
109
+ leader.__send__(:logger).debug("wait_for_consensus") do
110
+ "#{leader.t.name}.last_index=#{leader.c.instance_variable_get(:@log).last_index} " +
111
+ cluster.map { |n| "#{n.t.name}.commit_index=#{n.c.instance_variable_get(:@commit_index)}" }.join(" ")
112
+ end
113
+ end
114
+ end
115
+
116
+ def assert_equal(a, e, msg = nil)
117
+ loc = caller_locations.first
118
+ logloc = "#{File.basename(loc.path)}:#{loc.lineno}"
119
+ if e == a
120
+ logger.info(logloc) { "PASS: #{a.inspect} == #{e.inspect}" + (msg ? " (#{msg})" : "") }
121
+ else
122
+ logger.error(logloc) { "FAIL: Expected #{e.inspect}, got #{a.inspect}" + (msg ? " (#{msg})" : "") }
123
+ exit 1
124
+ end
125
+ end
126
+
127
+ def assert(v, msg = nil)
128
+ loc = caller_locations.first
129
+ logloc = "#{File.basename(loc.path)}:#{loc.lineno}"
130
+ if v
131
+ logger.info(logloc) { "PASS: #{v.inspect} is true" + (msg ? " (#{msg})" : "") }
132
+ else
133
+ logger.debug(logloc) { "FAIL: #{v.inspect} is falsy" + (msg ? " (#{msg})" : "") }
134
+ exit
135
+ end
136
+ end
137
+
138
+ module FaultInjector
139
+ class Pauser
140
+ def initialize
141
+ @pause = false
142
+ @mutex = Mutex.new
143
+ @cv = ConditionVariable.new
144
+ end
145
+
146
+ def maybe_pause
147
+ @mutex.synchronize { while @pause; @cv.wait(@mutex); end }
148
+ end
149
+
150
+ def pause!
151
+ @mutex.synchronize { @pause = true }
152
+ end
153
+
154
+ def unpause!
155
+ @mutex.synchronize { @pause = false; @cv.broadcast }
156
+ end
157
+ end
158
+
159
+ def self.injected(_)
160
+ raise RuntimeError, "This module should be prepended"
161
+ end
162
+
163
+ def self.extended(_)
164
+ raise RuntimeError, "This module should be prepended"
165
+ end
166
+
167
+ def pause!(m)
168
+ logger.debug(logloc) { "Pausing #{m}" }
169
+ unless respond_to?(m, true)
170
+ raise ArgumentError, "No method #{m.inspect} to pause"
171
+ end
172
+
173
+ unless FaultInjector.method_defined?(m, false)
174
+ logger.debug(logloc) { "Defining pause-enabled version of #{m}" }
175
+ FaultInjector.module_eval do
176
+ define_method(m) do |*a|
177
+ @pausers ||= Hash.new { |h, k| h[k] = Pauser.new }
178
+ logger.debug(logloc) { "PREPAUSE: #{m}" }
179
+ @pausers[m].maybe_pause
180
+ logger.debug(logloc) { "POSTPAUSE: #{m}" }
181
+ super(*a)
182
+ end
183
+ end
184
+ end
185
+
186
+ @pausers ||= Hash.new { |h, k| h[k] = Pauser.new }
187
+ @pausers[m].pause!
188
+ end
189
+
190
+ def unpause!(m)
191
+ logger.debug(logloc) { "Unpausing #{m}" }
192
+ unless @pausers.key?(m)
193
+ raise ArgumentError, "No paused method #{m.inspect} to unpause"
194
+ end
195
+
196
+ @pausers ||= Hash.new { |h, k| h[k] = Pauser.new }
197
+ @pausers[m].unpause!
198
+ end
199
+ end
metadata ADDED
@@ -0,0 +1,318 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: evinrude
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Palmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-dns
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: async-io
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: frankenstein
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: prometheus-client
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rbnacl
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: github-release
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: guard-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '10.4'
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 10.4.2
149
+ type: :development
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '10.4'
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 10.4.2
159
+ - !ruby/object:Gem::Dependency
160
+ name: rb-inotify
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.9'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '0.9'
173
+ - !ruby/object:Gem::Dependency
174
+ name: redcarpet
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: rspec
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ - !ruby/object:Gem::Dependency
202
+ name: simplecov
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ type: :development
209
+ prerelease: false
210
+ version_requirements: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ - !ruby/object:Gem::Dependency
216
+ name: yard
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ type: :development
223
+ prerelease: false
224
+ version_requirements: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ description:
230
+ email:
231
+ - theshed+evinrude@hezmatt.org
232
+ executables: []
233
+ extensions: []
234
+ extra_rdoc_files: []
235
+ files:
236
+ - ".editorconfig"
237
+ - ".gitignore"
238
+ - ".yardopts"
239
+ - CODE_OF_CONDUCT.md
240
+ - CONTRIBUTING.md
241
+ - LICENCE
242
+ - README.md
243
+ - evinrude.gemspec
244
+ - lib/evinrude.rb
245
+ - lib/evinrude/backoff.rb
246
+ - lib/evinrude/cluster_configuration.rb
247
+ - lib/evinrude/config_change_queue_entry.rb
248
+ - lib/evinrude/config_change_queue_entry/add_node.rb
249
+ - lib/evinrude/config_change_queue_entry/remove_node.rb
250
+ - lib/evinrude/freedom_patches/range.rb
251
+ - lib/evinrude/log.rb
252
+ - lib/evinrude/log_entries.rb
253
+ - lib/evinrude/log_entry.rb
254
+ - lib/evinrude/log_entry/cluster_configuration.rb
255
+ - lib/evinrude/log_entry/null.rb
256
+ - lib/evinrude/log_entry/state_machine_command.rb
257
+ - lib/evinrude/logging_helpers.rb
258
+ - lib/evinrude/message.rb
259
+ - lib/evinrude/message/append_entries_reply.rb
260
+ - lib/evinrude/message/append_entries_request.rb
261
+ - lib/evinrude/message/command_reply.rb
262
+ - lib/evinrude/message/command_request.rb
263
+ - lib/evinrude/message/install_snapshot_reply.rb
264
+ - lib/evinrude/message/install_snapshot_request.rb
265
+ - lib/evinrude/message/join_reply.rb
266
+ - lib/evinrude/message/join_request.rb
267
+ - lib/evinrude/message/node_removal_reply.rb
268
+ - lib/evinrude/message/node_removal_request.rb
269
+ - lib/evinrude/message/read_reply.rb
270
+ - lib/evinrude/message/read_request.rb
271
+ - lib/evinrude/message/vote_reply.rb
272
+ - lib/evinrude/message/vote_request.rb
273
+ - lib/evinrude/messages.rb
274
+ - lib/evinrude/metrics.rb
275
+ - lib/evinrude/network.rb
276
+ - lib/evinrude/network/connection.rb
277
+ - lib/evinrude/network/protocol.rb
278
+ - lib/evinrude/node_info.rb
279
+ - lib/evinrude/peer.rb
280
+ - lib/evinrude/resolver.rb
281
+ - lib/evinrude/snapshot.rb
282
+ - lib/evinrude/state_machine.rb
283
+ - lib/evinrude/state_machine/register.rb
284
+ - smoke_tests/001_single_node_cluster.rb
285
+ - smoke_tests/002_three_node_cluster.rb
286
+ - smoke_tests/003_spill.rb
287
+ - smoke_tests/004_stale_read.rb
288
+ - smoke_tests/005_sleepy_master.rb
289
+ - smoke_tests/006_join_via_follower.rb
290
+ - smoke_tests/007_snapshot_madness.rb
291
+ - smoke_tests/008_downsizing.rb
292
+ - smoke_tests/009_disaster_recovery.rb
293
+ - smoke_tests/999_final_smoke_test.rb
294
+ - smoke_tests/run
295
+ - smoke_tests/smoke_test_helper.rb
296
+ homepage: https://github.com/mpalmer/evinrude
297
+ licenses: []
298
+ metadata: {}
299
+ post_install_message:
300
+ rdoc_options: []
301
+ require_paths:
302
+ - lib
303
+ required_ruby_version: !ruby/object:Gem::Requirement
304
+ requirements:
305
+ - - ">="
306
+ - !ruby/object:Gem::Version
307
+ version: 2.5.0
308
+ required_rubygems_version: !ruby/object:Gem::Requirement
309
+ requirements:
310
+ - - ">="
311
+ - !ruby/object:Gem::Version
312
+ version: '0'
313
+ requirements: []
314
+ rubygems_version: 3.0.3
315
+ signing_key:
316
+ specification_version: 4
317
+ summary: The Raft engine
318
+ test_files: []