nogara-redis_failover 0.8.9

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.
data/spec/node_spec.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module RedisFailover
4
+ describe Node do
5
+ let(:node) { Node.new(:host => 'localhost', :port => '123') }
6
+
7
+ before(:each) do
8
+ node.extend(RedisStubSupport)
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'creates a new instance' do
13
+ node.host.should == 'localhost'
14
+ node.port.should == 123
15
+ end
16
+
17
+ it 'reports error if host missing' do
18
+ expect { Node.new }.to raise_error(InvalidNodeError)
19
+ end
20
+ end
21
+
22
+ describe '#ping' do
23
+ it 'responds properly if node is available' do
24
+ expect { node.ping }.to_not raise_error
25
+ end
26
+
27
+ it 'responds properly if node is unavailable' do
28
+ node.redis.make_unavailable!
29
+ expect { node.ping }.to raise_error(NodeUnavailableError)
30
+ end
31
+ end
32
+
33
+ describe '#master?' do
34
+ it 'responds properly if node is master' do
35
+ node.should be_master
36
+ end
37
+
38
+ it 'responds properly if node is not master' do
39
+ node.make_slave!(Node.new(:host => 'masterhost'))
40
+ node.should_not be_master
41
+ end
42
+ end
43
+
44
+
45
+ describe '#slave?' do
46
+ it 'responds properly if node is slave' do
47
+ node.should_not be_slave
48
+ end
49
+
50
+ it 'responds properly if node is not slave' do
51
+ node.make_master!
52
+ node.should be_master
53
+ end
54
+ end
55
+
56
+ describe '#wait' do
57
+ it 'should wait until node dies' do
58
+ thread = Thread.new { node.wait }
59
+ thread.should be_alive
60
+ node.redis.make_unavailable!
61
+ expect { thread.value }.to raise_error
62
+ end
63
+ end
64
+
65
+ describe '#wakeup' do
66
+ it 'should gracefully stop waiting' do
67
+ thread = Thread.new { node.wait }
68
+ thread.should be_alive
69
+ node.wakeup
70
+ sleep 2
71
+ thread.should_not be_alive
72
+ thread.value.should be_nil
73
+ end
74
+ end
75
+
76
+ describe '#perform_operation' do
77
+ it 'raises error for any operation that hangs for too long' do
78
+ expect do
79
+ node.send(:perform_operation) { 1_000_000.times { sleep 0.1 } }
80
+ end.to raise_error(NodeUnavailableError)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module RedisFailover
4
+ class LightNodeManager
5
+ def initialize
6
+ @node_states = {}
7
+ end
8
+
9
+ def notify_state(node, state)
10
+ @node_states[node] = state
11
+ end
12
+
13
+ def state_for(node)
14
+ @node_states[node]
15
+ end
16
+ end
17
+
18
+ describe NodeWatcher do
19
+ let(:node_manager) { LightNodeManager.new }
20
+ let(:node) { Node.new(:host => 'host', :port => 123).extend(RedisStubSupport) }
21
+
22
+ describe '#watch' do
23
+ context 'node is not syncing with master' do
24
+ it 'properly informs manager of unavailable node' do
25
+ watcher = NodeWatcher.new(node_manager, node, 1)
26
+ watcher.watch
27
+ sleep(3)
28
+ node.redis.make_unavailable!
29
+ sleep(3)
30
+ watcher.shutdown
31
+ node_manager.state_for(node).should == :unavailable
32
+ end
33
+
34
+ it 'properly informs manager of available node' do
35
+ node_manager.notify_state(node, :unavailable)
36
+ watcher = NodeWatcher.new(node_manager, node, 1)
37
+ watcher.watch
38
+ sleep(3)
39
+ watcher.shutdown
40
+ node_manager.state_for(node).should == :available
41
+ end
42
+ end
43
+
44
+ context 'node is syncing with master' do
45
+ it 'properly informs manager of syncing node' do
46
+ node_manager.notify_state(node, :unavailable)
47
+ node.redis.slaveof('masterhost', 9876)
48
+ node.redis.force_sync_with_master(true)
49
+ watcher = NodeWatcher.new(node_manager, node, 1)
50
+ watcher.watch
51
+ sleep(3)
52
+ watcher.shutdown
53
+ node_manager.state_for(node).should == :syncing
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspec'
2
+ require 'redis_failover'
3
+
4
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
5
+
6
+ class NullObject
7
+ def method_missing(method, *args, &block)
8
+ yield if block_given?
9
+ self
10
+ end
11
+ end
12
+
13
+ module RedisFailover
14
+ Util.logger = NullObject.new
15
+ end
16
+
17
+ def ZK.new(*args); NullObject.new; end
18
+
19
+ RSpec.configure do |config|
20
+ end
@@ -0,0 +1,15 @@
1
+ :development:
2
+ :nodes:
3
+ - localhost:6379
4
+ - localhost:6389
5
+ :zkservers:
6
+ - localhost:2181
7
+
8
+ :staging:
9
+ :nodes:
10
+ - redis01:6379
11
+ - redis02:6379
12
+ :zkservers:
13
+ - zk01:2181
14
+ - zk02:2181
15
+ - zk03:2181
@@ -0,0 +1,17 @@
1
+ :development:
2
+ :nodes:
3
+ - localhost:6379
4
+ - localhost:6389
5
+ :zkservers:
6
+ - localhost:2181
7
+ :chroot: /with/chroot_development
8
+
9
+ :staging:
10
+ :nodes:
11
+ - redis01:6379
12
+ - redis02:6379
13
+ :zkservers:
14
+ - zk01:2181
15
+ - zk02:2181
16
+ - zk03:2181
17
+ :chroot: /with/chroot_staging
@@ -0,0 +1,7 @@
1
+ :nodes:
2
+ - redis01:6379
3
+ - redis02:6379
4
+ :zkservers:
5
+ - zk01:2181
6
+ - zk02:2181
7
+ - zk03:2181
@@ -0,0 +1,8 @@
1
+ :nodes:
2
+ - redis01:6379
3
+ - redis02:6379
4
+ :zkservers:
5
+ - zk01:2181
6
+ - zk02:2181
7
+ - zk03:2181
8
+ :chroot: /with/chroot
@@ -0,0 +1,65 @@
1
+ module RedisFailover
2
+ class NodeManagerStub < NodeManager
3
+ attr_accessor :master
4
+ public :current_nodes
5
+
6
+ def discover_nodes
7
+ # only discover nodes once in testing
8
+ return if @nodes_discovered
9
+
10
+ master = Node.new(:host => 'master')
11
+ slave = Node.new(:host => 'slave')
12
+ [master, slave].each { |node| node.extend(RedisStubSupport) }
13
+ master.make_master!
14
+ slave.make_slave!(master)
15
+ @unavailable = []
16
+ @master = master
17
+ @slaves = [slave]
18
+ @nodes_discovered = true
19
+ end
20
+
21
+ def setup_zk
22
+ @zk = NullObject.new
23
+ end
24
+
25
+ def slaves
26
+ @slaves
27
+ end
28
+
29
+ def start_processing
30
+ @thread = Thread.new { start }
31
+ sleep(1.5)
32
+ end
33
+
34
+ def stop_processing
35
+ @queue << nil
36
+ @thread.value
37
+ end
38
+
39
+ def force_unavailable(node)
40
+ start_processing
41
+ node.redis.make_unavailable!
42
+ notify_state(node, :unavailable)
43
+ stop_processing
44
+ end
45
+
46
+ def force_available(node)
47
+ start_processing
48
+ node.redis.make_available!
49
+ notify_state(node, :available)
50
+ stop_processing
51
+ end
52
+
53
+ def force_syncing(node, serve_stale_reads)
54
+ start_processing
55
+ node.redis.force_sync_with_master(serve_stale_reads)
56
+ notify_state(node, :syncing)
57
+ stop_processing
58
+ end
59
+
60
+ def initialize_path; end
61
+ def delete_path; end
62
+ def create_path; end
63
+ def write_state; end
64
+ end
65
+ end
@@ -0,0 +1,105 @@
1
+ require 'ostruct'
2
+
3
+ module RedisFailover
4
+ # Test stub for Redis.
5
+ class RedisStub
6
+ class Proxy
7
+ def initialize(queue, opts = {})
8
+ @info = {'role' => 'master'}
9
+ @config = {'slave-serve-stale-data' => 'yes'}
10
+ @queue = queue
11
+ end
12
+
13
+ def blpop(*args)
14
+ @queue.pop.tap do |value|
15
+ raise value if value
16
+ end
17
+ end
18
+
19
+ def del(*args)
20
+ end
21
+
22
+ def lpush(*args)
23
+ @queue << nil
24
+ end
25
+
26
+ def slaveof(host, port)
27
+ if host == 'no' && port == 'one'
28
+ @info['role'] = 'master'
29
+ @info.delete('master_host')
30
+ @info.delete('master_port')
31
+ else
32
+ @info['role'] = 'slave'
33
+ @info['master_host'] = host
34
+ @info['master_port'] = port.to_s
35
+ end
36
+ end
37
+
38
+ def info
39
+ @info.dup
40
+ end
41
+
42
+ def change_role_to(role)
43
+ @info['role'] = role
44
+ end
45
+
46
+ def config(action, attribute)
47
+ [action, @config[attribute]]
48
+ end
49
+
50
+ def force_sync_with_master(serve_stale_reads)
51
+ @config['slave-serve-stale-data'] = serve_stale_reads ? 'yes' : 'no'
52
+ @info['master_sync_in_progress'] = '1'
53
+ end
54
+
55
+ def force_sync_done
56
+ @info['master_sync_in_progress'] = '0'
57
+ end
58
+ end
59
+
60
+ attr_reader :host, :port, :available
61
+ def initialize(opts = {})
62
+ @host = opts[:host]
63
+ @port = Integer(opts[:port])
64
+ @queue = Queue.new
65
+ @proxy = Proxy.new(@queue, opts)
66
+ @available = true
67
+ end
68
+
69
+ def method_missing(method, *args, &block)
70
+ if @available
71
+ @proxy.send(method, *args, &block)
72
+ else
73
+ raise Errno::ECONNREFUSED
74
+ end
75
+ end
76
+
77
+ def change_role_to(role)
78
+ @proxy.change_role_to(role)
79
+ end
80
+
81
+ def make_available!
82
+ @available = true
83
+ end
84
+
85
+ def make_unavailable!
86
+ @queue << Errno::ECONNREFUSED
87
+ @available = false
88
+ end
89
+
90
+ def to_s
91
+ "#{@host}:#{@port}"
92
+ end
93
+
94
+ def client
95
+ OpenStruct.new(:host => @host, :port => @port)
96
+ end
97
+ end
98
+
99
+ module RedisStubSupport
100
+ def redis
101
+ @redis ||= RedisStub.new(:host => @host, :port => @port)
102
+ end
103
+ alias_method :new_client, :redis
104
+ end
105
+ end
data/spec/util_spec.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ module RedisFailover
4
+ describe Util do
5
+ describe '.symbolize_keys' do
6
+ it 'converts hash keys to symbols' do
7
+ Util.symbolize_keys('a' => 1, 'b' => 2).should == {:a => 1, :b => 2}
8
+ end
9
+ end
10
+
11
+ describe '.different?' do
12
+ it 'handles different arrays' do
13
+ Util.different?([1,2,3], [1,5,3]).should be_true
14
+ end
15
+
16
+ it 'handles non-different arrays' do
17
+ Util.different?([1,2,3], [3,2,1]).should be_false
18
+ end
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nogara-redis_failover
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.9
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan LeCompte
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
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: '3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: redis-namespace
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: multi_json
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1'
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: '1'
62
+ - !ruby/object:Gem::Dependency
63
+ name: zk
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.6'
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: '1.6'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: yard
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
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
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Redis Failover is a ZooKeeper-based automatic master/slave failover solution
127
+ for Ruby
128
+ email:
129
+ - lecompte@gmail.com
130
+ executables:
131
+ - redis_node_manager
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - .gitignore
136
+ - .travis.yml
137
+ - .yardopts
138
+ - Changes.md
139
+ - Gemfile
140
+ - LICENSE
141
+ - README.md
142
+ - Rakefile
143
+ - bin/redis_node_manager
144
+ - examples/config.yml
145
+ - examples/multiple_environments_config.yml
146
+ - lib/redis_failover.rb
147
+ - lib/redis_failover/cli.rb
148
+ - lib/redis_failover/client.rb
149
+ - lib/redis_failover/errors.rb
150
+ - lib/redis_failover/manual_failover.rb
151
+ - lib/redis_failover/node.rb
152
+ - lib/redis_failover/node_manager.rb
153
+ - lib/redis_failover/node_watcher.rb
154
+ - lib/redis_failover/runner.rb
155
+ - lib/redis_failover/util.rb
156
+ - lib/redis_failover/version.rb
157
+ - misc/redis_failover.png
158
+ - redis_failover.gemspec
159
+ - spec/cli_spec.rb
160
+ - spec/client_spec.rb
161
+ - spec/node_manager_spec.rb
162
+ - spec/node_spec.rb
163
+ - spec/node_watcher_spec.rb
164
+ - spec/spec_helper.rb
165
+ - spec/support/config/multiple_environments.yml
166
+ - spec/support/config/multiple_environments_with_chroot.yml
167
+ - spec/support/config/single_environment.yml
168
+ - spec/support/config/single_environment_with_chroot.yml
169
+ - spec/support/node_manager_stub.rb
170
+ - spec/support/redis_stub.rb
171
+ - spec/util_spec.rb
172
+ homepage: http://github.com/ryanlecompte/redis_failover
173
+ licenses: []
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ none: false
180
+ requirements:
181
+ - - ! '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 1.8.24
193
+ signing_key:
194
+ specification_version: 3
195
+ summary: Redis Failover is a ZooKeeper-based automatic master/slave failover solution
196
+ for Ruby
197
+ test_files:
198
+ - spec/cli_spec.rb
199
+ - spec/client_spec.rb
200
+ - spec/node_manager_spec.rb
201
+ - spec/node_spec.rb
202
+ - spec/node_watcher_spec.rb
203
+ - spec/spec_helper.rb
204
+ - spec/support/config/multiple_environments.yml
205
+ - spec/support/config/multiple_environments_with_chroot.yml
206
+ - spec/support/config/single_environment.yml
207
+ - spec/support/config/single_environment_with_chroot.yml
208
+ - spec/support/node_manager_stub.rb
209
+ - spec/support/redis_stub.rb
210
+ - spec/util_spec.rb