zeevex_cluster 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +22 -0
  3. data/Rakefile +44 -0
  4. data/doc/BUGS-zookeeper.txt +60 -0
  5. data/doc/TODO.txt +85 -0
  6. data/lib/zeevex_cluster/base.rb +95 -0
  7. data/lib/zeevex_cluster/coordinator/base_key_val_store.rb +85 -0
  8. data/lib/zeevex_cluster/coordinator/memcached.rb +118 -0
  9. data/lib/zeevex_cluster/coordinator/mysql.rb +396 -0
  10. data/lib/zeevex_cluster/coordinator/redis.rb +101 -0
  11. data/lib/zeevex_cluster/coordinator.rb +29 -0
  12. data/lib/zeevex_cluster/election.rb +102 -0
  13. data/lib/zeevex_cluster/message.rb +52 -0
  14. data/lib/zeevex_cluster/nil_logger.rb +7 -0
  15. data/lib/zeevex_cluster/serializer/json_hash.rb +67 -0
  16. data/lib/zeevex_cluster/serializer.rb +27 -0
  17. data/lib/zeevex_cluster/static.rb +67 -0
  18. data/lib/zeevex_cluster/strategy/base.rb +92 -0
  19. data/lib/zeevex_cluster/strategy/cas.rb +403 -0
  20. data/lib/zeevex_cluster/strategy/static.rb +55 -0
  21. data/lib/zeevex_cluster/strategy/unclustered.rb +9 -0
  22. data/lib/zeevex_cluster/strategy/zookeeper.rb +163 -0
  23. data/lib/zeevex_cluster/strategy.rb +12 -0
  24. data/lib/zeevex_cluster/synchronized.rb +46 -0
  25. data/lib/zeevex_cluster/unclustered.rb +11 -0
  26. data/lib/zeevex_cluster/util/logging.rb +7 -0
  27. data/lib/zeevex_cluster/util.rb +15 -0
  28. data/lib/zeevex_cluster/version.rb +3 -0
  29. data/lib/zeevex_cluster.rb +29 -0
  30. data/script/election.rb +46 -0
  31. data/script/memc.rb +13 -0
  32. data/script/mysql.rb +25 -0
  33. data/script/redis.rb +14 -0
  34. data/script/repl +10 -0
  35. data/script/repl.rb +8 -0
  36. data/script/ser.rb +11 -0
  37. data/script/static.rb +34 -0
  38. data/script/testall +2 -0
  39. data/spec/cluster_static_spec.rb +49 -0
  40. data/spec/cluster_unclustered_spec.rb +32 -0
  41. data/spec/coordinator/coordinator_memcached_spec.rb +102 -0
  42. data/spec/message_spec.rb +38 -0
  43. data/spec/serializer/json_hash_spec.rb +68 -0
  44. data/spec/shared_master_examples.rb +20 -0
  45. data/spec/shared_member_examples.rb +39 -0
  46. data/spec/shared_non_master_examples.rb +8 -0
  47. data/spec/spec_helper.rb +14 -0
  48. data/zeevex_cluster.gemspec +43 -0
  49. metadata +298 -0
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'zeevex_cluster/message.rb'
3
+
4
+ #
5
+ # REQUIRED_KEYS = %w{source sequence sent_at expires_at contents content_type encoding}
6
+ #
7
+
8
+ describe ZeevexCluster::Message do
9
+ subject { ZeevexCluster::Message.new :contents => '{foo: 1}', :source => 'node1' }
10
+
11
+ context 'creation' do
12
+ it { should be_a(Hash) }
13
+ it { should have_key(:contents) }
14
+ it { should have_key(:source) }
15
+ it { should_not be_valid }
16
+
17
+ context 'default values' do
18
+ it { should have_key(:content_type) }
19
+ its(:content_type) { should == 'application/json' }
20
+ its(:encoding) { should be_nil }
21
+ end
22
+ end
23
+
24
+ context 'method-style access' do
25
+ it { should respond_to("contents") }
26
+ its(:source) { should == 'node1' }
27
+ end
28
+
29
+ context 'validation' do
30
+ it 'should not be valid without required keys' do
31
+ subject.should_not be_valid
32
+ end
33
+ it 'should be valid if required keys are added' do
34
+ subject.merge(:sequence => 1, :sent_at => Time.now, :expires_at => Time.now + 7200).should be_valid
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,68 @@
1
+ require File.join(File.dirname(__FILE__), '../spec_helper')
2
+ require 'zeevex_cluster/serializer/json_hash.rb'
3
+
4
+ describe ZeevexCluster::Serializer::JsonHash do
5
+ let :serializer do
6
+ ZeevexCluster::Serializer::JsonHash.new
7
+ end
8
+
9
+ context 'cas host tokens' do
10
+ let :now do
11
+ Time.at(1355418862)
12
+ end
13
+ let :times do
14
+ {:joined_at => now - 7200, :timestamp => now, :locked_at => now - 600}
15
+ end
16
+
17
+ let :nodehash do
18
+ times.merge({:nodename => 'mynode'})
19
+ end
20
+
21
+ let :json do
22
+ serializer.serialize(nodehash)
23
+ end
24
+
25
+ context 'serialized form' do
26
+ subject { json }
27
+ it { should be_a(String) }
28
+ it { should match /"joined_at":\{[^}]*1355411662/ }
29
+ it { should match /"locked_at":\{[^}]*1355418262/ }
30
+ it { should match /"timestamp":\{[^}]*1355418862/ }
31
+ it { should match /"nodename":\s*"mynode"/ }
32
+ it { should match /^\{.*\}$/ }
33
+ it 'should be a valid JSON string' do
34
+ JSON.parse(subject).should be_a(Hash)
35
+ end
36
+ end
37
+
38
+ context 'deserialized hash' do
39
+ subject { serializer.deserialize(json) }
40
+ it { should have(4).keys }
41
+ it { should == nodehash }
42
+ it 'should have only symbol keys' do
43
+ subject.keys.reject {|k| k.is_a?(Symbol) }.should be_empty
44
+ end
45
+ it 'should have 3 time fields' do
46
+ subject.values.select {|key| key.is_a?(Time) }.should have(3).items
47
+ end
48
+ it 'should have matching times for 3 fields' do
49
+ times.map {|(key, val)| subject[key].utc.should == val.utc }
50
+ end
51
+ end
52
+
53
+ context 'nested hashes' do
54
+ let :innerhash do
55
+ {:when => now - 7200, :members => ["a", "b"]}
56
+ end
57
+
58
+ let :tophash do
59
+ {:mlist => innerhash, :timestamp => now}
60
+ end
61
+
62
+ it 'should roundtrip' do
63
+ serializer.deserialize( serializer.serialize(tophash) ).should == tophash
64
+ end
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,20 @@
1
+ shared_examples_for 'master_node' do
2
+ it { should be_master }
3
+ it 'should have the same nodename as the master' do
4
+ subject.master.should == subject.nodename
5
+ end
6
+ it 'should execute run_if_master block' do
7
+ subject.run_if_master do
8
+ :ran
9
+ end.should == :ran
10
+ end
11
+ it 'should no longer be master after leaving' do
12
+ expect { subject.leave }.
13
+ to change { subject.master? }.
14
+ from(true).to(false)
15
+ end
16
+ #it 'should resign before leaving the cluster' do
17
+ # subject.should_receive(:resign!).and_return(true)
18
+ # subject.leave
19
+ #end
20
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # depends on a couple of let bindings
3
+ #
4
+ # - autojoined: newly created, autojoined
5
+ # - non_autojoined: newly created, autojoin => false
6
+ #
7
+ # and a method:
8
+ #
9
+ # - new_cluster(options)
10
+ #
11
+ shared_examples_for 'member_node' do
12
+ context 'when auto-joined' do
13
+ subject { new_cluster(:autojoin => true) }
14
+
15
+ it { should be_member }
16
+
17
+ it 'should have a nodename' do
18
+ subject.nodename.should_not be_nil
19
+ subject.nodename.should_not be_empty
20
+ end
21
+
22
+ it 'should become non-member after leaving' do
23
+ expect { subject.leave }.
24
+ to change { subject.member? }.from(true).to(false)
25
+ end
26
+ end
27
+
28
+ context 'when not auto-joined' do
29
+ subject { new_cluster(:autojoin => false) }
30
+
31
+ it { should_not be_member }
32
+
33
+ it 'should become member after joining' do
34
+ expect { subject.join }.
35
+ to change { subject.member? }.from(false).to(true)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,8 @@
1
+ shared_examples_for 'non_master_node' do
2
+ it { should_not be_master }
3
+ it 'should not execute run_if_master block' do
4
+ subject.run_if_master do
5
+ :ran
6
+ end.should == false
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ require 'rspec'
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ require 'zeevex_cluster'
5
+ require 'zeevex_cluster/static'
6
+ require 'zeevex_cluster/unclustered'
7
+ require 'zeevex_cluster/unclustered'
8
+
9
+ require 'pry'
10
+ require 'timeout'
11
+
12
+ require File.expand_path(File.dirname(__FILE__) + '/shared_master_examples.rb')
13
+ require File.expand_path(File.dirname(__FILE__) + '/shared_non_master_examples.rb')
14
+ require File.expand_path(File.dirname(__FILE__) + '/shared_member_examples.rb')
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "zeevex_cluster/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "zeevex_cluster"
7
+ s.version = ZeevexCluster::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Robert Sanders"]
10
+ s.email = ["robert@zeevex.com"]
11
+ s.homepage = "http://github.com/zeevex/zeevex_cluster"
12
+ s.summary = %q{Use a shared coordinator service to reliably elect a master in a cluster of processes.}
13
+ s.description = %q{Using a shared data storage service like MySQL, Memcache, Redis, or ZooKeeper, one process of many can be elected a cluster master. Notification of cluster membership changes is also provided.}
14
+
15
+ s.rubyforge_project = "zeevex_cluster"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'state_machine'
23
+ s.add_dependency 'json'
24
+ s.add_dependency 'zeevex_proxy'
25
+ s.add_dependency 'zeevex_threadsafe'
26
+ s.add_dependency 'countdownlatch', '~> 1.0.0'
27
+ s.add_dependency 'atomic', '~> 1.0.0'
28
+
29
+ s.add_dependency 'hookem', '~> 0.0.1'
30
+
31
+ ## other headius utils
32
+ # s.add_dependency 'thread_safe'
33
+
34
+ s.add_development_dependency 'rspec', '~> 2.9.0'
35
+ s.add_development_dependency 'rake'
36
+ s.add_development_dependency 'pry'
37
+
38
+ s.add_development_dependency 'memcache-client', '> 1.7.0'
39
+ s.add_development_dependency 'redis'
40
+
41
+ # s.add_development_dependency 'zk'
42
+ # s.add_development_dependency 'zk-group'
43
+ end
metadata ADDED
@@ -0,0 +1,298 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zeevex_cluster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Sanders
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: state_machine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: zeevex_proxy
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: zeevex_threadsafe
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: countdownlatch
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.0.0
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.0.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: atomic
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 1.0.0
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 1.0.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: hookem
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.0.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 2.9.0
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 2.9.0
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: pry
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: memcache-client
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>'
180
+ - !ruby/object:Gem::Version
181
+ version: 1.7.0
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>'
188
+ - !ruby/object:Gem::Version
189
+ version: 1.7.0
190
+ - !ruby/object:Gem::Dependency
191
+ name: redis
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ description: Using a shared data storage service like MySQL, Memcache, Redis, or ZooKeeper,
207
+ one process of many can be elected a cluster master. Notification of cluster membership
208
+ changes is also provided.
209
+ email:
210
+ - robert@zeevex.com
211
+ executables: []
212
+ extensions: []
213
+ extra_rdoc_files: []
214
+ files:
215
+ - .gitignore
216
+ - Gemfile
217
+ - Rakefile
218
+ - doc/BUGS-zookeeper.txt
219
+ - doc/TODO.txt
220
+ - lib/zeevex_cluster.rb
221
+ - lib/zeevex_cluster/base.rb
222
+ - lib/zeevex_cluster/coordinator.rb
223
+ - lib/zeevex_cluster/coordinator/base_key_val_store.rb
224
+ - lib/zeevex_cluster/coordinator/memcached.rb
225
+ - lib/zeevex_cluster/coordinator/mysql.rb
226
+ - lib/zeevex_cluster/coordinator/redis.rb
227
+ - lib/zeevex_cluster/election.rb
228
+ - lib/zeevex_cluster/message.rb
229
+ - lib/zeevex_cluster/nil_logger.rb
230
+ - lib/zeevex_cluster/serializer.rb
231
+ - lib/zeevex_cluster/serializer/json_hash.rb
232
+ - lib/zeevex_cluster/static.rb
233
+ - lib/zeevex_cluster/strategy.rb
234
+ - lib/zeevex_cluster/strategy/base.rb
235
+ - lib/zeevex_cluster/strategy/cas.rb
236
+ - lib/zeevex_cluster/strategy/static.rb
237
+ - lib/zeevex_cluster/strategy/unclustered.rb
238
+ - lib/zeevex_cluster/strategy/zookeeper.rb
239
+ - lib/zeevex_cluster/synchronized.rb
240
+ - lib/zeevex_cluster/unclustered.rb
241
+ - lib/zeevex_cluster/util.rb
242
+ - lib/zeevex_cluster/util/logging.rb
243
+ - lib/zeevex_cluster/version.rb
244
+ - script/election.rb
245
+ - script/memc.rb
246
+ - script/mysql.rb
247
+ - script/redis.rb
248
+ - script/repl
249
+ - script/repl.rb
250
+ - script/ser.rb
251
+ - script/static.rb
252
+ - script/testall
253
+ - spec/cluster_static_spec.rb
254
+ - spec/cluster_unclustered_spec.rb
255
+ - spec/coordinator/coordinator_memcached_spec.rb
256
+ - spec/message_spec.rb
257
+ - spec/serializer/json_hash_spec.rb
258
+ - spec/shared_master_examples.rb
259
+ - spec/shared_member_examples.rb
260
+ - spec/shared_non_master_examples.rb
261
+ - spec/spec_helper.rb
262
+ - zeevex_cluster.gemspec
263
+ homepage: http://github.com/zeevex/zeevex_cluster
264
+ licenses: []
265
+ post_install_message:
266
+ rdoc_options: []
267
+ require_paths:
268
+ - lib
269
+ required_ruby_version: !ruby/object:Gem::Requirement
270
+ none: false
271
+ requirements:
272
+ - - ! '>='
273
+ - !ruby/object:Gem::Version
274
+ version: '0'
275
+ required_rubygems_version: !ruby/object:Gem::Requirement
276
+ none: false
277
+ requirements:
278
+ - - ! '>='
279
+ - !ruby/object:Gem::Version
280
+ version: '0'
281
+ requirements: []
282
+ rubyforge_project: zeevex_cluster
283
+ rubygems_version: 1.8.23
284
+ signing_key:
285
+ specification_version: 3
286
+ summary: Use a shared coordinator service to reliably elect a master in a cluster
287
+ of processes.
288
+ test_files:
289
+ - spec/cluster_static_spec.rb
290
+ - spec/cluster_unclustered_spec.rb
291
+ - spec/coordinator/coordinator_memcached_spec.rb
292
+ - spec/message_spec.rb
293
+ - spec/serializer/json_hash_spec.rb
294
+ - spec/shared_master_examples.rb
295
+ - spec/shared_member_examples.rb
296
+ - spec/shared_non_master_examples.rb
297
+ - spec/spec_helper.rb
298
+ has_rdoc: