evinrude 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []