redis_failover 0.8.3 → 0.8.4

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 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