nogara-redis_failover 0.9.1 → 0.9.7
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/.gitignore +1 -1
- data/Changes.md +32 -0
- data/README.md +3 -1
- data/lib/redis_failover/client.rb +17 -7
- data/lib/redis_failover/errors.rb +7 -0
- data/lib/redis_failover/node.rb +4 -5
- data/lib/redis_failover/node_manager.rb +165 -70
- data/lib/redis_failover/node_watcher.rb +7 -3
- data/lib/redis_failover/runner.rb +7 -9
- data/lib/redis_failover/version.rb +1 -1
- data/spec/client_spec.rb +6 -0
- data/spec/node_manager_spec.rb +24 -0
- data/spec/support/node_manager_stub.rb +3 -2
- metadata +2 -2
data/.gitignore
CHANGED
data/Changes.md
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
0.9.7
|
2
|
+
-----------
|
3
|
+
- Stubbed Client#client to return itself, fixes a fork reconnect bug with Resque (dbalatero)
|
4
|
+
|
5
|
+
0.9.6
|
6
|
+
-----------
|
7
|
+
- Handle the node discovery error condition where the znode points to a master that is now a slave.
|
8
|
+
|
9
|
+
0.9.5
|
10
|
+
-----------
|
11
|
+
- Introduce a safer master node discovery process for the Node Manager (#34)
|
12
|
+
- Improved shutdown process for Node Manager
|
13
|
+
|
14
|
+
0.9.4
|
15
|
+
-----------
|
16
|
+
- Preserve original master by reading from existing znode state.
|
17
|
+
- Prevent Timeout::Error from bringing down the process (#32) (@eric)
|
18
|
+
|
19
|
+
0.9.3
|
20
|
+
-----------
|
21
|
+
- Add lock assert for Node Manager.
|
22
|
+
|
23
|
+
0.9.2
|
24
|
+
-----------
|
25
|
+
- Improved exception handling in NodeWatcher.
|
26
|
+
|
27
|
+
0.9.1
|
28
|
+
-----------
|
29
|
+
- Improve nested exception handling.
|
30
|
+
- Fix manual failover support when znode does not exist first.
|
31
|
+
- Various fixes to work better with 1.8.7.
|
32
|
+
|
1
33
|
0.9.0
|
2
34
|
-----------
|
3
35
|
- Make Node Manager's lock path vary with its main znode. (Bira)
|
data/README.md
CHANGED
@@ -155,7 +155,7 @@ redis_failover uses YARD for its API documentation. Refer to the generated [API
|
|
155
155
|
|
156
156
|
## Requirements
|
157
157
|
|
158
|
-
- redis_failover is actively tested against MRI 1.9.2/1.9.3 and JRuby 1.6.7 (1.9 mode only). Other rubies may work, although I don't actively test against them.
|
158
|
+
- redis_failover is actively tested against MRI 1.8.7/1.9.2/1.9.3 and JRuby 1.6.7 (1.9 mode only). Other rubies may work, although I don't actively test against them.
|
159
159
|
- redis_failover requires a ZooKeeper service cluster to ensure reliability and data consistency. ZooKeeper is very simple and easy to get up and running. Please refer to this [Quick ZooKeeper Guide](https://github.com/ryanlecompte/redis_failover/wiki/Quick-ZooKeeper-Guide) to get up and running quickly if you don't already have ZooKeeper as a part of your environment.
|
160
160
|
|
161
161
|
## Considerations
|
@@ -175,6 +175,8 @@ redis_failover uses YARD for its API documentation. Refer to the generated [API
|
|
175
175
|
- To learn more about ZooKeeper, see the official [ZooKeeper](http://zookeeper.apache.org/) site.
|
176
176
|
- See the [Quick ZooKeeper Guide](https://github.com/ryanlecompte/redis_failover/wiki/Quick-ZooKeeper-Guide) for a quick guide to getting ZooKeeper up and running with redis_failover.
|
177
177
|
- To learn more about how ZooKeeper handles network partitions, see [ZooKeeper Failure Scenarios](http://wiki.apache.org/hadoop/ZooKeeper/FailureScenarios)
|
178
|
+
- Slides for a [lightning talk](http://www.slideshare.net/ryanlecompte/handling-redis-failover-with-zookeeper) that I gave at BaRuCo 2012.
|
179
|
+
- Feel free to join #zk-gem on the IRC freenode network. We're usually hanging out there talking about ZooKeeper and redis_failover.
|
178
180
|
|
179
181
|
|
180
182
|
## License
|
@@ -64,10 +64,18 @@ module RedisFailover
|
|
64
64
|
build_clients
|
65
65
|
end
|
66
66
|
|
67
|
-
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
67
|
+
# Stubs this method to return this RedisFailover::Client object.
|
68
|
+
#
|
69
|
+
# Some libraries (Resque) assume they can access the `client` via this method,
|
70
|
+
# but we don't want to actually ever expose the internal Redis connections.
|
71
|
+
#
|
72
|
+
# By returning `self` here, we can add stubs for functionality like #reconnect,
|
73
|
+
# and everything will Just Work.
|
74
|
+
#
|
75
|
+
# Takes an *args array for safety only.
|
76
|
+
#
|
77
|
+
# @return [RedisFailover::Client]
|
78
|
+
def client(*args)
|
71
79
|
self
|
72
80
|
end
|
73
81
|
|
@@ -190,6 +198,8 @@ module RedisFailover
|
|
190
198
|
else
|
191
199
|
logger.error("Unknown ZK node event: #{event.inspect}")
|
192
200
|
end
|
201
|
+
ensure
|
202
|
+
@zk.stat(@znode, :watch => true)
|
193
203
|
end
|
194
204
|
|
195
205
|
# Determines if a method is a known redis operation.
|
@@ -250,7 +260,7 @@ module RedisFailover
|
|
250
260
|
# @raise [NoMasterError] if no master fallback is available
|
251
261
|
def slave
|
252
262
|
# pick a slave, if none available fallback to master
|
253
|
-
if slave = @lock.synchronize { @slaves.
|
263
|
+
if slave = @lock.synchronize { @slaves.shuffle.first }
|
254
264
|
verify_role!(slave, :slave)
|
255
265
|
return slave
|
256
266
|
end
|
@@ -266,7 +276,7 @@ module RedisFailover
|
|
266
276
|
return unless nodes_changed?(nodes)
|
267
277
|
|
268
278
|
purge_clients
|
269
|
-
logger.info("Building new clients for nodes #{nodes}")
|
279
|
+
logger.info("Building new clients for nodes #{nodes.inspect}")
|
270
280
|
new_master = new_clients_for(nodes[:master]).first if nodes[:master]
|
271
281
|
new_slaves = new_clients_for(*nodes[:slaves])
|
272
282
|
@master = new_master
|
@@ -300,7 +310,7 @@ module RedisFailover
|
|
300
310
|
def fetch_nodes
|
301
311
|
data = @zk.get(@znode, :watch => true).first
|
302
312
|
nodes = symbolize_keys(decode(data))
|
303
|
-
logger.debug("Fetched nodes: #{nodes}")
|
313
|
+
logger.debug("Fetched nodes: #{nodes.inspect}")
|
304
314
|
|
305
315
|
nodes
|
306
316
|
rescue Zookeeper::Exceptions::InheritedConnectionError => ex
|
@@ -25,6 +25,13 @@ module RedisFailover
|
|
25
25
|
class NoMasterError < Error
|
26
26
|
end
|
27
27
|
|
28
|
+
# Raised when more than one master is found on startup.
|
29
|
+
class MultipleMastersError < Error
|
30
|
+
def initialize(nodes)
|
31
|
+
super("Multiple nodes with master role: #{nodes.map(&:to_s)}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
28
35
|
# Raised when no slave is currently available.
|
29
36
|
class NoSlaveError < Error
|
30
37
|
end
|
data/lib/redis_failover/node.rb
CHANGED
@@ -118,7 +118,6 @@ module RedisFailover
|
|
118
118
|
end
|
119
119
|
alias_method :eql?, :==
|
120
120
|
|
121
|
-
|
122
121
|
# @return [Integer] a hash value for this node
|
123
122
|
def hash
|
124
123
|
to_s.hash
|
@@ -175,14 +174,14 @@ module RedisFailover
|
|
175
174
|
redis = new_client
|
176
175
|
yield redis
|
177
176
|
end
|
178
|
-
rescue
|
179
|
-
raise NodeUnavailableError,
|
177
|
+
rescue Exception => ex
|
178
|
+
raise NodeUnavailableError, "#{ex.class}: #{ex.message}", ex.backtrace
|
180
179
|
ensure
|
181
180
|
if redis
|
182
181
|
begin
|
183
182
|
redis.client.disconnect
|
184
|
-
rescue
|
185
|
-
raise NodeUnavailableError,
|
183
|
+
rescue Exception => ex
|
184
|
+
raise NodeUnavailableError, "#{ex.class}: #{ex.message}", ex.backtrace
|
186
185
|
end
|
187
186
|
end
|
188
187
|
end
|
@@ -10,7 +10,22 @@ module RedisFailover
|
|
10
10
|
include Util
|
11
11
|
|
12
12
|
# Number of seconds to wait before retrying bootstrap process.
|
13
|
-
TIMEOUT =
|
13
|
+
TIMEOUT = 3
|
14
|
+
|
15
|
+
# ZK Errors that the Node Manager cares about.
|
16
|
+
ZK_ERRORS = [
|
17
|
+
ZK::Exceptions::LockAssertionFailedError,
|
18
|
+
ZK::Exceptions::InterruptedSession,
|
19
|
+
ZKDisconnectedError
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
# Errors that can happen during the node discovery process.
|
23
|
+
NODE_DISCOVERY_ERRORS = [
|
24
|
+
InvalidNodeRoleError,
|
25
|
+
NodeUnavailableError,
|
26
|
+
NoMasterError,
|
27
|
+
MultipleMastersError
|
28
|
+
].freeze
|
14
29
|
|
15
30
|
# Creates a new instance.
|
16
31
|
#
|
@@ -26,13 +41,11 @@ module RedisFailover
|
|
26
41
|
@znode = @options[:znode_path] || Util::DEFAULT_ZNODE_PATH
|
27
42
|
@manual_znode = ManualFailover::ZNODE_PATH
|
28
43
|
@mutex = Mutex.new
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# or fails, another Node Manager process will grab the lock and
|
35
|
-
# become the
|
44
|
+
@shutdown = false
|
45
|
+
@leader = false
|
46
|
+
@master = nil
|
47
|
+
@slaves = []
|
48
|
+
@unavailable = []
|
36
49
|
@lock_path = "#{@znode}_lock".freeze
|
37
50
|
end
|
38
51
|
|
@@ -40,23 +53,22 @@ module RedisFailover
|
|
40
53
|
#
|
41
54
|
# @note This method does not return until the manager terminates.
|
42
55
|
def start
|
56
|
+
return unless running?
|
43
57
|
@queue = Queue.new
|
44
|
-
@leader = false
|
45
58
|
setup_zk
|
46
59
|
logger.info('Waiting to become master Node Manager ...')
|
47
|
-
|
60
|
+
with_lock do
|
48
61
|
@leader = true
|
49
62
|
logger.info('Acquired master Node Manager lock')
|
50
|
-
discover_nodes
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
if discover_nodes
|
64
|
+
initialize_path
|
65
|
+
spawn_watchers
|
66
|
+
handle_state_reports
|
67
|
+
end
|
54
68
|
end
|
55
|
-
rescue
|
69
|
+
rescue *ZK_ERRORS => ex
|
56
70
|
logger.error("ZK error while attempting to manage nodes: #{ex.inspect}")
|
57
|
-
|
58
|
-
shutdown
|
59
|
-
sleep(TIMEOUT)
|
71
|
+
reset
|
60
72
|
retry
|
61
73
|
end
|
62
74
|
|
@@ -69,12 +81,21 @@ module RedisFailover
|
|
69
81
|
@queue << [node, state]
|
70
82
|
end
|
71
83
|
|
72
|
-
# Performs a
|
73
|
-
def
|
74
|
-
@
|
75
|
-
@queue << nil
|
84
|
+
# Performs a reset of the manager.
|
85
|
+
def reset
|
86
|
+
@leader = false
|
76
87
|
@watchers.each(&:shutdown) if @watchers
|
88
|
+
@queue.clear
|
77
89
|
@zk.close! if @zk
|
90
|
+
@zk_lock = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Initiates a graceful shutdown.
|
94
|
+
def shutdown
|
95
|
+
logger.info('Shutting down ...')
|
96
|
+
@mutex.synchronize do
|
97
|
+
@shutdown = true
|
98
|
+
end
|
78
99
|
end
|
79
100
|
|
80
101
|
private
|
@@ -86,10 +107,8 @@ module RedisFailover
|
|
86
107
|
@zk.on_expired_session { notify_state(:zk_disconnected, nil) }
|
87
108
|
|
88
109
|
@zk.register(@manual_znode) do |event|
|
89
|
-
|
90
|
-
|
91
|
-
schedule_manual_failover
|
92
|
-
end
|
110
|
+
if event.node_created? || event.node_changed?
|
111
|
+
perform_manual_failover
|
93
112
|
end
|
94
113
|
end
|
95
114
|
|
@@ -99,21 +118,24 @@ module RedisFailover
|
|
99
118
|
|
100
119
|
# Handles periodic state reports from {RedisFailover::NodeWatcher} instances.
|
101
120
|
def handle_state_reports
|
102
|
-
while state_report = @queue.pop
|
121
|
+
while running? && (state_report = @queue.pop)
|
103
122
|
begin
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
123
|
+
@mutex.synchronize do
|
124
|
+
return unless running?
|
125
|
+
@zk_lock.assert!
|
126
|
+
node, state = state_report
|
127
|
+
case state
|
128
|
+
when :unavailable then handle_unavailable(node)
|
129
|
+
when :available then handle_available(node)
|
130
|
+
when :syncing then handle_syncing(node)
|
131
|
+
when :zk_disconnected then raise ZKDisconnectedError
|
132
|
+
else raise InvalidNodeStateError.new(node, state)
|
133
|
+
end
|
134
|
+
|
135
|
+
# flush current state
|
136
|
+
write_state
|
112
137
|
end
|
113
|
-
|
114
|
-
# flush current state
|
115
|
-
write_state
|
116
|
-
rescue ZK::Exceptions::InterruptedSession, ZKDisconnectedError
|
138
|
+
rescue *ZK_ERRORS
|
117
139
|
# fail hard if this is a ZK connection-related error
|
118
140
|
raise
|
119
141
|
rescue => ex
|
@@ -148,7 +170,7 @@ module RedisFailover
|
|
148
170
|
reconcile(node)
|
149
171
|
|
150
172
|
# no-op if we already know about this node
|
151
|
-
return if @master == node || @slaves.include?(node)
|
173
|
+
return if @master == node || (@master && @slaves.include?(node))
|
152
174
|
logger.info("Handling available node: #{node}")
|
153
175
|
|
154
176
|
if @master
|
@@ -188,7 +210,7 @@ module RedisFailover
|
|
188
210
|
logger.info("Handling manual failover")
|
189
211
|
|
190
212
|
# make current master a slave, and promote new master
|
191
|
-
@slaves << @master
|
213
|
+
@slaves << @master if @master
|
192
214
|
@slaves.delete(node)
|
193
215
|
promote_new_master(node)
|
194
216
|
end
|
@@ -218,21 +240,69 @@ module RedisFailover
|
|
218
240
|
end
|
219
241
|
|
220
242
|
# Discovers the current master and slave nodes.
|
243
|
+
# @return [Boolean] true if nodes successfully discovered, false otherwise
|
221
244
|
def discover_nodes
|
222
|
-
@
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
245
|
+
@mutex.synchronize do
|
246
|
+
return false unless running?
|
247
|
+
nodes = @options[:nodes].map { |opts| Node.new(opts) }.uniq
|
248
|
+
if @master = find_existing_master
|
249
|
+
logger.info("Using master #{@master} from existing znode config.")
|
250
|
+
elsif @master = guess_master(nodes)
|
251
|
+
logger.info("Guessed master #{@master} from known redis nodes.")
|
252
|
+
end
|
253
|
+
@slaves = nodes - [@master]
|
254
|
+
logger.info("Managing master (#{@master}) and slaves " +
|
255
|
+
"(#{@slaves.map(&:to_s).join(', ')})")
|
256
|
+
# ensure that slaves are correctly pointing to this master
|
257
|
+
redirect_slaves_to(@master)
|
258
|
+
true
|
259
|
+
end
|
260
|
+
rescue *NODE_DISCOVERY_ERRORS => ex
|
261
|
+
msg = <<-MSG.gsub(/\s+/, ' ')
|
262
|
+
Failed to discover master node: #{ex.inspect}
|
263
|
+
In order to ensure a safe startup, redis_failover requires that all redis
|
264
|
+
nodes be accessible, and only a single node indicating that it's the master.
|
265
|
+
In order to fix this, you can perform a manual failover via redis_failover,
|
266
|
+
or manually fix the individual redis servers. This discovery process will
|
267
|
+
retry in #{TIMEOUT}s.
|
268
|
+
MSG
|
269
|
+
logger.warn(msg)
|
270
|
+
sleep(TIMEOUT)
|
271
|
+
retry
|
272
|
+
end
|
273
|
+
|
274
|
+
# Seeds the initial node master from an existing znode config.
|
275
|
+
def find_existing_master
|
276
|
+
if data = @zk.get(@znode).first
|
277
|
+
nodes = symbolize_keys(decode(data))
|
278
|
+
master = node_from(nodes[:master])
|
279
|
+
logger.info("Master from existing znode config: #{master || 'none'}")
|
280
|
+
# Check for case where a node previously thought to be the master was
|
281
|
+
# somehow manually reconfigured to be a slave outside of the node manager's
|
282
|
+
# control.
|
283
|
+
if master && master.slave?
|
284
|
+
raise InvalidNodeRoleError.new(master, :master, :slave)
|
285
|
+
end
|
286
|
+
master
|
287
|
+
end
|
288
|
+
rescue ZK::Exceptions::NoNode
|
289
|
+
# blank slate, no last known master
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
|
293
|
+
# Creates a Node instance from a string.
|
294
|
+
#
|
295
|
+
# @param [String] node_string a string representation of a node (e.g., host:port)
|
296
|
+
# @return [Node] the Node representation
|
297
|
+
def node_from(node_string)
|
298
|
+
return if node_string.nil?
|
299
|
+
host, port = node_string.split(':', 2)
|
300
|
+
Node.new(:host => host, :port => port, :password => @options[:password])
|
231
301
|
end
|
232
302
|
|
233
303
|
# Spawns the {RedisFailover::NodeWatcher} instances for each managed node.
|
234
304
|
def spawn_watchers
|
235
|
-
@watchers = [@master, @slaves, @unavailable].flatten.map do |node|
|
305
|
+
@watchers = [@master, @slaves, @unavailable].flatten.compact.map do |node|
|
236
306
|
NodeWatcher.new(self, node, @options[:max_failures] || 3)
|
237
307
|
end
|
238
308
|
@watchers.each(&:watch)
|
@@ -242,14 +312,11 @@ module RedisFailover
|
|
242
312
|
#
|
243
313
|
# @param [Array<Node>] nodes the nodes to search
|
244
314
|
# @return [Node] the found master node, nil if not found
|
245
|
-
def
|
246
|
-
nodes.
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
false
|
251
|
-
end
|
252
|
-
end
|
315
|
+
def guess_master(nodes)
|
316
|
+
master_nodes = nodes.select { |node| node.master? }
|
317
|
+
raise NoMasterError if master_nodes.empty?
|
318
|
+
raise MultipleMastersError.new(master_nodes) if master_nodes.size > 1
|
319
|
+
master_nodes.first
|
253
320
|
end
|
254
321
|
|
255
322
|
# Redirects all slaves to the specified node.
|
@@ -336,19 +403,47 @@ module RedisFailover
|
|
336
403
|
@zk.set(@znode, encode(current_nodes))
|
337
404
|
end
|
338
405
|
|
339
|
-
#
|
340
|
-
def
|
341
|
-
|
342
|
-
|
343
|
-
|
406
|
+
# Executes a block wrapped in a ZK exclusive lock.
|
407
|
+
def with_lock
|
408
|
+
@zk_lock = @zk.locker(@lock_path)
|
409
|
+
while running? && !@zk_lock.lock
|
410
|
+
sleep(TIMEOUT)
|
411
|
+
end
|
344
412
|
|
345
|
-
|
346
|
-
|
347
|
-
else
|
348
|
-
host, port = new_master.split(':', 2)
|
349
|
-
Node.new(:host => host, :port => port, :password => @options[:password])
|
413
|
+
if running?
|
414
|
+
yield
|
350
415
|
end
|
351
|
-
|
416
|
+
ensure
|
417
|
+
@zk_lock.unlock! if @zk_lock
|
418
|
+
end
|
419
|
+
|
420
|
+
# Perform a manual failover to a redis node.
|
421
|
+
def perform_manual_failover
|
422
|
+
@mutex.synchronize do
|
423
|
+
return unless running? && @leader && @zk_lock
|
424
|
+
@zk_lock.assert!
|
425
|
+
new_master = @zk.get(@manual_znode, :watch => true).first
|
426
|
+
return unless new_master && new_master.size > 0
|
427
|
+
logger.info("Received manual failover request for: #{new_master}")
|
428
|
+
logger.info("Current nodes: #{current_nodes.inspect}")
|
429
|
+
node = new_master == ManualFailover::ANY_SLAVE ?
|
430
|
+
@slaves.shuffle.first : node_from(new_master)
|
431
|
+
if node
|
432
|
+
handle_manual_failover(node)
|
433
|
+
else
|
434
|
+
logger.error('Failed to perform manual failover, no candidate found.')
|
435
|
+
end
|
436
|
+
end
|
437
|
+
rescue => ex
|
438
|
+
logger.error("Error handling a manual failover: #{ex.inspect}")
|
439
|
+
logger.error(ex.backtrace.join("\n"))
|
440
|
+
ensure
|
441
|
+
@zk.stat(@manual_znode, :watch => true)
|
442
|
+
end
|
443
|
+
|
444
|
+
# @return [Boolean] true if running, false otherwise
|
445
|
+
def running?
|
446
|
+
!@shutdown
|
352
447
|
end
|
353
448
|
end
|
354
449
|
end
|
@@ -35,8 +35,8 @@ module RedisFailover
|
|
35
35
|
@done = true
|
36
36
|
@node.wakeup
|
37
37
|
@monitor_thread.join if @monitor_thread
|
38
|
-
rescue
|
39
|
-
|
38
|
+
rescue => ex
|
39
|
+
logger.warn("Failed to gracefully shutdown watcher for #{@node}")
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
@@ -59,12 +59,16 @@ module RedisFailover
|
|
59
59
|
notify(:available)
|
60
60
|
@node.wait
|
61
61
|
end
|
62
|
-
rescue NodeUnavailableError
|
62
|
+
rescue NodeUnavailableError => ex
|
63
|
+
logger.debug("Failed to communicate with node #{@node}: #{ex.inspect}")
|
63
64
|
failures += 1
|
64
65
|
if failures >= @max_failures
|
65
66
|
notify(:unavailable)
|
66
67
|
failures = 0
|
67
68
|
end
|
69
|
+
rescue Exception => ex
|
70
|
+
logger.error("Unexpected error while monitoring node #{@node}: #{ex.inspect}")
|
71
|
+
logger.error(ex.backtrace.join("\n"))
|
68
72
|
end
|
69
73
|
end
|
70
74
|
end
|
@@ -8,22 +8,20 @@ module RedisFailover
|
|
8
8
|
# Node Manager is gracefully stopped
|
9
9
|
def self.run(options)
|
10
10
|
options = CLI.parse(options)
|
11
|
-
|
12
|
-
trap_signals
|
13
|
-
|
14
|
-
@node_manager_thread.join
|
11
|
+
node_manager = NodeManager.new(options)
|
12
|
+
trap_signals(node_manager)
|
13
|
+
node_manager.start
|
15
14
|
end
|
16
15
|
|
17
16
|
# Traps shutdown signals.
|
18
|
-
|
17
|
+
# @param [NodeManager] node_manager the node manager
|
18
|
+
def self.trap_signals(node_manager)
|
19
19
|
[:INT, :TERM].each do |signal|
|
20
20
|
trap(signal) do
|
21
|
-
|
22
|
-
@node_manager.shutdown
|
23
|
-
@node_manager_thread.join
|
24
|
-
exit(0)
|
21
|
+
node_manager.shutdown
|
25
22
|
end
|
26
23
|
end
|
27
24
|
end
|
25
|
+
private_class_method :trap_signals
|
28
26
|
end
|
29
27
|
end
|
data/spec/client_spec.rb
CHANGED
data/spec/node_manager_spec.rb
CHANGED
@@ -108,5 +108,29 @@ module RedisFailover
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
|
+
|
112
|
+
describe '#guess_master' do
|
113
|
+
let(:node1) { Node.new(:host => 'node1').extend(RedisStubSupport) }
|
114
|
+
let(:node2) { Node.new(:host => 'node2').extend(RedisStubSupport) }
|
115
|
+
let(:node3) { Node.new(:host => 'node3').extend(RedisStubSupport) }
|
116
|
+
|
117
|
+
it 'raises error when no master is found' do
|
118
|
+
node1.make_slave!(node3)
|
119
|
+
node2.make_slave!(node3)
|
120
|
+
expect { manager.guess_master([node1, node2]) }.to raise_error(NoMasterError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'raises error when multiple masters found' do
|
124
|
+
node1.make_master!
|
125
|
+
node2.make_master!
|
126
|
+
expect { manager.guess_master([node1, node2]) }.to raise_error(MultipleMastersError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'raises error when a node can not be reached' do
|
130
|
+
node1.make_master!
|
131
|
+
node2.redis.make_unavailable!
|
132
|
+
expect { manager.guess_master([node1, node2]) }.to raise_error(NodeUnavailableError)
|
133
|
+
end
|
134
|
+
end
|
111
135
|
end
|
112
136
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module RedisFailover
|
2
2
|
class NodeManagerStub < NodeManager
|
3
3
|
attr_accessor :master
|
4
|
-
|
4
|
+
# HACK - this will go away once we refactor the tests to use a real ZK/Redis server.
|
5
|
+
public :current_nodes, :guess_master
|
5
6
|
|
6
7
|
def discover_nodes
|
7
8
|
# only discover nodes once in testing
|
8
|
-
return if @nodes_discovered
|
9
|
+
return true if @nodes_discovered
|
9
10
|
|
10
11
|
master = Node.new(:host => 'master')
|
11
12
|
slave = Node.new(:host => 'slave')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nogara-redis_failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|