redis_failover 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ tags
19
+
data/Changes.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.8.4
2
+ -----------
3
+ - Lock-down gemspec to redis 2.2.x in light of upcoming redis 3.x release. Once sufficient testing
4
+ has been done with the 3.x release, I will relax the constraint in the gemspec.
5
+ - Add environment-scoped configuration file support (oleriesenberg)
6
+
1
7
  0.8.3
2
8
  -----------
3
9
  - Added a way to gracefully shutdown/reconnect a RedisFailover::Client. (#13)
data/README.md CHANGED
@@ -67,6 +67,7 @@ following options:
67
67
  --znode-path PATH Znode path override for storing redis server list
68
68
  --max-failures COUNT Max failures before manager marks node unavailable
69
69
  -C, --config PATH Path to YAML configuration file
70
+ -E, --environment ENV Config environment to use
70
71
  -h, --help Display all options
71
72
 
72
73
  To start the daemon for a simple master/slave configuration, use the following:
@@ -93,6 +94,9 @@ You would then simpy start the Node Manager via the following:
93
94
 
94
95
  redis_node_manager -C config.yml
95
96
 
97
+ You can also scope the configuration to a particular environment (e.g., staging/development). See the examples
98
+ directory for configuration file samples.
99
+
96
100
  The Node Manager will automatically discover the master/slaves upon startup. Note that it is
97
101
  a good idea to run more than one instance of the Node Manager daemon in your environment. At
98
102
  any moment, a single Node Manager process will be designated to monitor the redis servers. If
@@ -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
@@ -36,10 +36,14 @@ module RedisFailover
36
36
  options[:max_failures] = Integer(max)
37
37
  end
38
38
 
39
- opts.on '-C', '--config PATH', "Path to YAML configuration file" do |file|
39
+ opts.on('-C', '--config PATH', 'Path to YAML config file') do |file|
40
40
  options[:config_file] = file
41
41
  end
42
42
 
43
+ opts.on('-E', '--environment ENV', 'Config environment to use') do |config_env|
44
+ options[:config_environment] = config_env
45
+ end
46
+
43
47
  opts.on('-h', '--help', 'Display all options') do
44
48
  puts opts
45
49
  exit
@@ -48,7 +52,7 @@ module RedisFailover
48
52
 
49
53
  parser.parse(source)
50
54
  if config_file = options[:config_file]
51
- options = from_file(config_file)
55
+ options = from_file(config_file, options[:config_environment])
52
56
  end
53
57
 
54
58
  if required_options_missing?(options)
@@ -69,12 +73,20 @@ module RedisFailover
69
73
  # Parses options from a YAML file.
70
74
  #
71
75
  # @param [String] file the filename
76
+ # @params [String] env the environment
72
77
  # @return [Hash] the parsed options
73
- def self.from_file(file)
78
+ def self.from_file(file, env = nil)
74
79
  unless File.exists?(file)
75
80
  raise ArgumentError, "File #{file} can't be found"
76
81
  end
77
82
  options = YAML.load_file(file)
83
+
84
+ if env
85
+ options = options.fetch(env.to_sym) do
86
+ raise ArgumentError, "Environment #{env} can't be found in config"
87
+ end
88
+ end
89
+
78
90
  options[:nodes] = options[:nodes].join(',')
79
91
  options[:zkservers] = options[:zkservers].join(',')
80
92
 
@@ -100,4 +112,4 @@ module RedisFailover
100
112
  options
101
113
  end
102
114
  end
103
- end
115
+ end
@@ -1,15 +1,18 @@
1
1
  module RedisFailover
2
- # Redis failover-aware client. RedisFailover::Client is a wrapper over a set of underlying redis
3
- # clients, which means all normal redis operations can be performed on an instance of this class.
4
- # The class only requires a set of ZooKeeper server addresses to function properly. The client
5
- # will automatically retry failed operations, and handle failover to a new master. The client
6
- # registers and listens for watcher events from the Node Manager. When these events are received,
7
- # the client fetches the latest set of redis nodes from ZooKeeper and rebuilds its internal
8
- # Redis clients appropriately. RedisFailover::Client also directs write operations to the master,
9
- # and all read operations to the slaves.
2
+ # Redis failover-aware client. RedisFailover::Client is a wrapper over a set
3
+ # of underlying redis clients, which means all normal redis operations can be
4
+ # performed on an instance of this class. The class only requires a set of
5
+ # ZooKeeper server addresses to function properly. The client will automatically
6
+ # retry failed operations, and handle failover to a new master. The client
7
+ # registers and listens for watcher events from the Node Manager. When these
8
+ # events are received, the client fetches the latest set of redis nodes from
9
+ # ZooKeeper and rebuilds its internal Redis clients appropriately.
10
+ # RedisFailover::Client also directs write operations to the master, and all
11
+ # read operations to the slaves.
10
12
  #
11
13
  # @example Usage
12
- # client = RedisFailover::Client.new(:zkservers => 'localhost:2181,localhost:2182,localhost:2183')
14
+ # zk_servers = 'localhost:2181,localhost:2182,localhost:2183'
15
+ # client = RedisFailover::Client.new(:zkservers => zk_servers)
13
16
  # client.set('foo', 1) # will be directed to master
14
17
  # client.get('foo') # will be directed to a slave
15
18
  #
@@ -86,13 +89,13 @@ module RedisFailover
86
89
  # Creates a new failover redis client.
87
90
  #
88
91
  # @param [Hash] options the options used to initialize the client instance
89
- # @option options [String] :zkservers comma-separated ZooKeeper host:port pairs (required)
90
- # @option options [String] :znode_path znode path override for redis server list
92
+ # @option options [String] :zkservers comma-separated ZooKeeper host:port
93
+ # @option options [String] :znode_path znode path override for redis nodes
91
94
  # @option options [String] :password password for redis nodes
92
95
  # @option options [String] :db database to use for redis nodes
93
96
  # @option options [String] :namespace namespace for redis nodes
94
97
  # @option options [Logger] :logger logger override
95
- # @option options [Boolean] :retry_failure indicates if failures should be retried
98
+ # @option options [Boolean] :retry_failure indicates if failures are retried
96
99
  # @option options [Integer] :max_retries max retries for a failure
97
100
  # @return [RedisFailover::Client]
98
101
  def initialize(options = {})
@@ -124,7 +127,7 @@ module RedisFailover
124
127
  # Determines whether or not an unknown method can be handled.
125
128
  #
126
129
  # @param [Symbol] method the method to check
127
- # @param [Boolean] include_private determines if private methods should be checked
130
+ # @param [Boolean] include_private determines if private methods are checked
128
131
  # @return [Boolean] indicates if the method can be handled
129
132
  def respond_to_missing?(method, include_private)
130
133
  redis_operation?(method) || super
@@ -144,7 +147,7 @@ module RedisFailover
144
147
  # @option options [String] :host the host of the failover candidate
145
148
  # @option options [String] :port the port of the failover candidate
146
149
  def manual_failover(options = {})
147
- Manual.failover(zk, options)
150
+ ManualFailover.new(zk, options).perform
148
151
  self
149
152
  end
150
153
 
@@ -295,7 +298,7 @@ module RedisFailover
295
298
 
296
299
  nodes
297
300
  rescue Zookeeper::Exceptions::InheritedConnectionError => ex
298
- logger.debug { "Caught #{ex.class} '#{ex.message}' reconstructing the zk instance" }
301
+ logger.debug { "Caught #{ex.class} '#{ex.message}' - reopening ZK client" }
299
302
  @zk.reopen
300
303
  retry
301
304
  end
@@ -395,7 +398,7 @@ module RedisFailover
395
398
  end
396
399
  end
397
400
 
398
- # Disconnects current redis clients and resets this client's view of the world.
401
+ # Disconnects current redis clients.
399
402
  def purge_clients
400
403
  @lock.synchronize do
401
404
  logger.info("Purging current redis clients")
@@ -44,4 +44,4 @@ module RedisFailover
44
44
  super("Operation `#{operation}` is currently unsupported")
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -1,34 +1,38 @@
1
1
  module RedisFailover
2
2
  # Provides manual failover support to a new master.
3
- module Manual
4
- extend self
5
-
3
+ class ManualFailover
6
4
  # Path for manual failover communication.
7
5
  ZNODE_PATH = '/redis_failover_manual'.freeze
8
6
 
9
7
  # Denotes that any slave can be used as a candidate for promotion.
10
8
  ANY_SLAVE = "ANY_SLAVE".freeze
11
9
 
12
- # Performs a manual failover. If options is empty, a random slave will
13
- # be used as a failover candidate.
10
+ # Creates a new instance.
14
11
  #
15
12
  # @param [ZK] zk the ZooKeeper client
16
13
  # @param [Hash] options the options used for manual failover
17
14
  # @option options [String] :host the host of the failover candidate
18
15
  # @option options [String] :port the port of the failover candidate
19
- def failover(zk, options = {})
20
- create_path(zk)
21
- node = options.empty? ? ANY_SLAVE : "#{options[:host]}:#{options[:port]}"
22
- zk.set(ZNODE_PATH, node)
16
+ # @note
17
+ # If options is empty, a random slave will be used
18
+ # as a failover candidate.
19
+ def initialize(zk, options = {})
20
+ @zk = zk
21
+ @options = options
22
+ end
23
+
24
+ # Performs a manual failover.
25
+ def perform
26
+ create_path
27
+ node = @options.empty? ? ANY_SLAVE : "#{@options[:host]}:#{@options[:port]}"
28
+ @zk.set(ZNODE_PATH, node)
23
29
  end
24
30
 
25
31
  private
26
32
 
27
33
  # Creates the znode path used for coordinating manual failovers.
28
- #
29
- # @param [ZK] zk the ZooKeeper cilent
30
- def create_path(zk)
31
- zk.create(ZNODE_PATH)
34
+ def create_path
35
+ @zk.create(ZNODE_PATH)
32
36
  rescue ZK::Exceptions::NodeExists
33
37
  # best effort
34
38
  end
@@ -23,22 +23,22 @@ module RedisFailover
23
23
  # Creates a new instance.
24
24
  #
25
25
  # @param [Hash] options the options used to initialize the manager
26
- # @option options [String] :zkservers comma-separated ZooKeeper host:port pairs (required)
27
- # @option options [String] :znode_path znode path override for redis server list
26
+ # @option options [String] :zkservers comma-separated ZK host:port pairs
27
+ # @option options [String] :znode_path znode path override for redis nodes
28
28
  # @option options [String] :password password for redis nodes
29
29
  # @option options [Array<String>] :nodes the nodes to manage
30
- # @option options [String] :max_failures the max failures for a particular node
30
+ # @option options [String] :max_failures the max failures for a node
31
31
  def initialize(options)
32
32
  logger.info("Redis Node Manager v#{VERSION} starting (#{RUBY_DESCRIPTION})")
33
33
  @options = options
34
34
  @znode = @options[:znode_path] || Util::DEFAULT_ZNODE_PATH
35
- @manual_znode = Manual::ZNODE_PATH
35
+ @manual_znode = ManualFailover::ZNODE_PATH
36
36
  @mutex = Mutex.new
37
37
  end
38
38
 
39
39
  # Starts the node manager.
40
40
  #
41
- # @note This is a blocking method, it does not return until the manager terminates.
41
+ # @note This method does not return until the manager terminates.
42
42
  def start
43
43
  @queue = Queue.new
44
44
  @leader = false
@@ -60,8 +60,8 @@ module RedisFailover
60
60
  retry
61
61
  end
62
62
 
63
- # Notifies the manager of a state change. Used primarily by {RedisFailover::NodeWatcher}
64
- # to inform the manager of watched node states.
63
+ # Notifies the manager of a state change. Used primarily by
64
+ # {RedisFailover::NodeWatcher} to inform the manager of watched node states.
65
65
  #
66
66
  # @param [Node] node the node
67
67
  # @param [Symbol] state the state
@@ -202,7 +202,7 @@ module RedisFailover
202
202
  # make a specific node or slave the new master
203
203
  candidate = node || @slaves.pop
204
204
  unless candidate
205
- logger.error('Failed to promote a new master since no candidate available.')
205
+ logger.error('Failed to promote a new master, no candidate available.')
206
206
  return
207
207
  end
208
208
 
@@ -338,7 +338,7 @@ module RedisFailover
338
338
  new_master = @zk.get(@manual_znode, :watch => true).first
339
339
  logger.info("Received manual failover request for: #{new_master}")
340
340
 
341
- node = if new_master == Manual::ANY_SLAVE
341
+ node = if new_master == ManualFailover::ANY_SLAVE
342
342
  @slaves.sample
343
343
  else
344
344
  host, port = new_master.split(':', 2)
@@ -5,7 +5,7 @@ module RedisFailover
5
5
  module Util
6
6
  extend self
7
7
 
8
- # Default node in ZooKeeper that contains the current list of available redis nodes.
8
+ # Default node in ZK that contains the current list of available redis nodes.
9
9
  DEFAULT_ZNODE_PATH = '/redis_failover_nodes'.freeze
10
10
 
11
11
  # Connectivity errors that the redis client raises.
@@ -1,3 +1,3 @@
1
1
  module RedisFailover
2
- VERSION = "0.8.3"
2
+ VERSION = '0.8.4'
3
3
  end
@@ -15,7 +15,8 @@ require 'redis_failover/node'
15
15
  require 'redis_failover/errors'
16
16
  require 'redis_failover/client'
17
17
  require 'redis_failover/runner'
18
- require 'redis_failover/manual'
19
18
  require 'redis_failover/version'
20
19
  require 'redis_failover/node_manager'
21
20
  require 'redis_failover/node_watcher'
21
+ require 'redis_failover/manual_failover'
22
+
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = RedisFailover::VERSION
17
17
 
18
- gem.add_dependency('redis')
18
+ gem.add_dependency('redis', '~> 2.2')
19
19
  gem.add_dependency('redis-namespace')
20
20
  gem.add_dependency('multi_json', '~> 1')
21
21
  gem.add_dependency('zk', '~> 1.4')
data/spec/cli_spec.rb CHANGED
@@ -35,6 +35,17 @@ module RedisFailover
35
35
  '1'])
36
36
  opts[:max_failures].should == 1
37
37
  end
38
+
39
+ it 'properly parses the config file' do
40
+ opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/single_environment.yml"])
41
+ opts[:zkservers].should == 'zk01:2181,zk02:2181,zk03:2181'
42
+
43
+ opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/multiple_environments.yml", '-E', 'development'])
44
+ opts[:zkservers].should == 'localhost:2181'
45
+
46
+ opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/multiple_environments.yml", '-E', 'staging'])
47
+ opts[:zkservers].should == 'zk01:2181,zk02:2181,zk03:2181'
48
+ end
38
49
  end
39
50
  end
40
51
  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,7 @@
1
+ :nodes:
2
+ - redis01:6379
3
+ - redis02:6379
4
+ :zkservers:
5
+ - zk01:2181
6
+ - zk02:2181
7
+ - zk03:2181
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_failover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.8.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-11 00:00:00.000000000 Z
12
+ date: 2012-06-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: '2.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: '2.2'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: redis-namespace
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -142,11 +142,12 @@ files:
142
142
  - Rakefile
143
143
  - bin/redis_node_manager
144
144
  - examples/config.yml
145
+ - examples/multiple_environments_config.yml
145
146
  - lib/redis_failover.rb
146
147
  - lib/redis_failover/cli.rb
147
148
  - lib/redis_failover/client.rb
148
149
  - lib/redis_failover/errors.rb
149
- - lib/redis_failover/manual.rb
150
+ - lib/redis_failover/manual_failover.rb
150
151
  - lib/redis_failover/node.rb
151
152
  - lib/redis_failover/node_manager.rb
152
153
  - lib/redis_failover/node_watcher.rb
@@ -160,6 +161,8 @@ files:
160
161
  - spec/node_spec.rb
161
162
  - spec/node_watcher_spec.rb
162
163
  - spec/spec_helper.rb
164
+ - spec/support/config/multiple_environments.yml
165
+ - spec/support/config/single_environment.yml
163
166
  - spec/support/node_manager_stub.rb
164
167
  - spec/support/redis_stub.rb
165
168
  - spec/util_spec.rb
@@ -177,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
180
  version: '0'
178
181
  segments:
179
182
  - 0
180
- hash: -651053748036205721
183
+ hash: 2968723262927828498
181
184
  required_rubygems_version: !ruby/object:Gem::Requirement
182
185
  none: false
183
186
  requirements:
@@ -186,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
189
  version: '0'
187
190
  segments:
188
191
  - 0
189
- hash: -651053748036205721
192
+ hash: 2968723262927828498
190
193
  requirements: []
191
194
  rubyforge_project:
192
195
  rubygems_version: 1.8.23
@@ -201,6 +204,8 @@ test_files:
201
204
  - spec/node_spec.rb
202
205
  - spec/node_watcher_spec.rb
203
206
  - spec/spec_helper.rb
207
+ - spec/support/config/multiple_environments.yml
208
+ - spec/support/config/single_environment.yml
204
209
  - spec/support/node_manager_stub.rb
205
210
  - spec/support/redis_stub.rb
206
211
  - spec/util_spec.rb