herdis 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -116,21 +116,25 @@ module Herdis
116
116
  end
117
117
 
118
118
  def method_missing(meth, *args, &block)
119
- begin
120
- @dredis.send(meth, *args, &block)
121
- rescue DeadClusterException => e
122
- refresh_cluster
123
- retry
124
- rescue Errno::ECONNREFUSED => e
125
- refresh_cluster
126
- retry
127
- rescue RuntimeError => e
128
- if e.message == "ERR operation not permitted"
119
+ if @dredis
120
+ begin
121
+ @dredis.send(meth, *args, &block)
122
+ rescue DeadClusterException => e
129
123
  refresh_cluster
130
124
  retry
131
- else
132
- raise e
125
+ rescue Errno::ECONNREFUSED => e
126
+ refresh_cluster
127
+ retry
128
+ rescue RuntimeError => e
129
+ if e.message == "ERR operation not permitted"
130
+ refresh_cluster
131
+ retry
132
+ else
133
+ raise e
134
+ end
133
135
  end
136
+ else
137
+ super.send(meth, *args, &block)
134
138
  end
135
139
  end
136
140
 
@@ -7,7 +7,11 @@ module Herdis
7
7
  include Common
8
8
 
9
9
  def response(env)
10
- [Herdis::Plugins::ShepherdConnection.shepherd.status, {}, ""]
10
+ if Herdis::Plugins::ShepherdConnection.shepherd.nil?
11
+ [404, {}, ""]
12
+ else
13
+ [Herdis::Plugins::ShepherdConnection.shepherd.status, {}, ""]
14
+ end
11
15
  end
12
16
 
13
17
  end
@@ -0,0 +1,18 @@
1
+
2
+ module Herdis
3
+
4
+ module Handlers
5
+
6
+ class ShutdownCluster < Goliath::API
7
+ include Common
8
+
9
+ def response(env)
10
+ Herdis::Plugins::ShepherdConnection.shutdown_cluster
11
+ [200, {}, ""]
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -13,6 +13,11 @@ module Herdis
13
13
  @@shepherd = nil
14
14
  end
15
15
 
16
+ def self.shutdown_cluster
17
+ @@shepherd.shutdown_cluster unless @@shepherd.nil?
18
+ @@shepherd = nil
19
+ end
20
+
16
21
  def initialize(port, config, status, logger)
17
22
  @port = port
18
23
  @logger = logger
@@ -23,6 +28,7 @@ module Herdis
23
28
  copy_from_env(opts, :first_port, :to_i)
24
29
  copy_from_env(opts, :dir)
25
30
  copy_from_env(opts, :host)
31
+ copy_from_env(opts, :restart)
26
32
  copy_from_env(opts, :port, :to_i)
27
33
  copy_from_env(opts, :shepherd_id)
28
34
  copy_from_env(opts, :inmemory)
@@ -22,6 +22,7 @@ require 'herdis/handlers/shards'
22
22
  require 'herdis/handlers/join_cluster'
23
23
  require 'herdis/handlers/remove_shepherd'
24
24
  require 'herdis/handlers/shutdown'
25
+ require 'herdis/handlers/shutdown_cluster'
25
26
  require 'herdis/handlers/ping'
26
27
  require 'herdis/handlers/info'
27
28
  require 'herdis/handlers/sanity'
@@ -44,6 +45,8 @@ module Herdis
44
45
  get '/sanity', Herdis::Handlers::Sanity
45
46
  get '/shards', Herdis::Handlers::Shards
46
47
 
48
+ delete '/cluster', Herdis::Handlers::ShutdownCluster
49
+
47
50
  post '/', Herdis::Handlers::JoinCluster
48
51
 
49
52
  post '/:shepherd_id/shards', Herdis::Handlers::AddShards
@@ -6,6 +6,12 @@ module Herdis
6
6
  CHECK_SLAVE_TIMER = (ENV["SHEPHERD_CHECK_SLAVE_TIMER"] || 10).to_f
7
7
  CHECK_PREDECESSOR_TIMER = (ENV["SHEPHERD_CHECK_PREDECESSOR_TIMER"] || 1).to_f
8
8
 
9
+ class MockLogger
10
+ def method_missing(meth, *args)
11
+ STDERR.puts("#{meth}: #{args.inspect}")
12
+ end
13
+ end
14
+
9
15
  class Shard
10
16
 
11
17
  attr_reader :shepherd
@@ -49,6 +55,7 @@ module Herdis
49
55
  connection.config("set", "requirepass", "")
50
56
  shepherd.slaves.delete(id.to_s)
51
57
  shepherd.masters[id.to_s] = self
58
+ shepherd.save_config!
52
59
  rescue RuntimeError => e
53
60
  if e.message == "LOADING Redis is loading the dataset in memory"
54
61
  EM::Synchrony.sleep(0.1)
@@ -66,6 +73,7 @@ module Herdis
66
73
  initialize_redis
67
74
  shepherd.masters.delete(id.to_s)
68
75
  shepherd.slaves[id.to_s] = self
76
+ shepherd.save_config!
69
77
  end
70
78
  end
71
79
  def initialize_redis
@@ -83,21 +91,31 @@ module Herdis
83
91
  end
84
92
  io = IO.popen("#{shepherd.redis} -", "w")
85
93
  write_configuration(io)
86
- unless master
87
- initialization = Proc.new do |p|
94
+ initialization = Proc.new do |p|
95
+ unless master
88
96
  begin
89
- connection.set("#{self.class.name}.id", id)
90
- connection.set("#{self.class.name}.created_at", Time.now.to_i)
91
- connection.set("#{self.class.name}.created_by", shepherd.shepherd_id)
97
+ unless connection.get("#{self.class.name}.id")
98
+ connection.set("#{self.class.name}.id", id)
99
+ connection.set("#{self.class.name}.created_at", Time.now.to_i)
100
+ connection.set("#{self.class.name}.created_by", shepherd.shepherd_id)
101
+ end
92
102
  rescue Errno::ECONNREFUSED => e
93
103
  EM.add_timer(0.1) do
94
104
  p.call(p)
95
105
  end
106
+ rescue RuntimeError => e
107
+ if e.message == "ERR operation not permitted"
108
+ EM.add_timer(0.1) do
109
+ p.call(p)
110
+ end
111
+ else
112
+ raise e
113
+ end
96
114
  end
97
115
  end
98
- EM.add_timer(0.1) do
99
- initialization.call(initialization)
100
- end
116
+ end
117
+ EM.add_timer(0.1) do
118
+ initialization.call(initialization)
101
119
  end
102
120
  end
103
121
  def write_configuration(io)
@@ -130,50 +148,118 @@ module Herdis
130
148
  attr_reader :port
131
149
  attr_reader :logger
132
150
  attr_reader :host
133
-
151
+ attr_reader :shutdown
152
+
134
153
  attr_reader :masters
135
154
  attr_reader :slaves
136
155
  attr_reader :shepherds
137
156
 
138
157
  def initialize(options = {})
158
+ @shutdown = false
139
159
  @dir = options.delete(:dir) || File.join(ENV["HOME"], ".herdis")
160
+ Dir.mkdir(dir) unless Dir.exists?(dir)
140
161
  @host = options.delete(:host) || "localhost"
141
162
  @redis = options.delete(:redis) || "redis-server"
142
163
  @port = options.delete(:port) || 9000
143
- @logger = options.delete(:logger)
164
+ @logger = options.delete(:logger) || MockLogger.new
144
165
  @first_port = options.delete(:first_port) || 9080
145
166
  @inmemory = options.delete(:inmemory)
146
167
  @redundancy = options.delete(:redundancy) || 2
147
- @shepherd_id = options.delete(:shepherd_id) || rand(1 << 256).to_s(36)
148
- Dir.mkdir(dir) unless Dir.exists?(dir)
149
-
150
- @shepherds = {}
151
- @slaves = {}
152
- @masters = {}
153
-
168
+
169
+ restart = options.delete(:restart) || false
170
+ if restart && File.exists?(config_file)
171
+ old_config = open(config_file) do |input|
172
+ Yajl::Parser.parse(input.read)
173
+ end
174
+ @shepherd_id = old_config["shepherd_id"]
175
+ @shepherds = old_config["shepherds"]
176
+ logger.info("#{shepherd_id} *** restoring old config")
177
+ logger.info("#{shepherd_id} *** siblings: #{shepherds.keys}")
178
+ logger.info("#{shepherd_id} *** masters: #{old_config["masters"]}")
179
+ logger.info("#{shepherd_id} *** slaves: #{old_config["slaves"].keys}")
180
+ @slaves = {}
181
+ @masters = {}
182
+ @ready_check_timer = EM.add_periodic_timer(CHECK_PREDECESSOR_TIMER) do
183
+ Fiber.new do
184
+ check_ready(old_config)
185
+ end.resume
186
+ end
187
+ else
188
+ @shepherd_id = options.delete(:shepherd_id) || rand(1 << 256).to_s(36)
189
+ @shepherds = {}
190
+ @slaves = {}
191
+ @masters = {}
192
+
193
+ if connect_to = options.delete(:connect_to)
194
+ join_cluster(connect_to)
195
+ else
196
+ Herdis::Common::SHARDS.times do |shard_id|
197
+ create_master_shard(shard_id)
198
+ end
199
+ @shepherds[shepherd_id] = shepherd_status
200
+ end
201
+ end
202
+
154
203
  at_exit do
155
204
  shutdown
156
205
  end
206
+
207
+ end
157
208
 
158
- if connect_to = options.delete(:connect_to)
159
- join_cluster(connect_to)
160
- else
161
- Herdis::Common::SHARDS.times do |shard_id|
162
- create_master_shard(shard_id)
209
+ def save_config!
210
+ tmpfile = "#{config_file}.new"
211
+ open(tmpfile, "w") do |output|
212
+ output.write(Yajl::Encoder.encode({
213
+ :shepherd_id => shepherd_id,
214
+ :shepherds => shepherds,
215
+ :masters => masters.keys,
216
+ :slaves => slaves.inject({}) do |sum, id_and_slave|
217
+ sum.merge(id_and_slave.first => id_and_slave.last.master)
218
+ end
219
+ }, :pretty => true))
220
+ end
221
+ File.rename(tmpfile, config_file)
222
+ end
223
+
224
+ def config_file
225
+ File.join(dir, "shepherd.json")
226
+ end
227
+
228
+ def check_ready(old_config)
229
+ shepherds.each do |shepherd_id, shepherd|
230
+ if EM::HttpRequest.new(shepherd["url"]).head.response_header.status != 204
231
+ logger.info("#{self.shepherd_id} *** still waiting for #{shepherd_id}")
232
+ return
163
233
  end
164
- @shepherds[shepherd_id] = shepherd_status
165
234
  end
235
+ @ready_check_timer.cancel
236
+ old_config["slaves"].each do |shard_id, external_uri|
237
+ create_slave_shard(shard_id.to_s, URI.parse(external_uri))
238
+ end
239
+ old_config["masters"].each do |shard_id|
240
+ create_master_shard(shard_id.to_s)
241
+ end
242
+ logger.info("#{shepherd_id} *** running again")
243
+ logger.info("#{shepherd_id} *** siblings: #{shepherds.keys}")
244
+ logger.info("#{shepherd_id} *** masters: #{masters.keys}")
245
+ logger.info("#{shepherd_id} *** slaves: #{slaves.keys}")
246
+ ensure_slave_check
247
+ ensure_predecessor_check
166
248
  end
167
249
 
168
250
  def ensure_slave_check
169
251
  @check_slave_timer ||= EM.add_periodic_timer(CHECK_SLAVE_TIMER) do
170
- check_slaves
252
+ Fiber.new do
253
+ check_slaves
254
+ end.resume
171
255
  end
172
256
  end
173
257
 
174
258
  def ensure_predecessor_check
175
259
  @check_predecessor_timer ||= EM.add_periodic_timer(CHECK_PREDECESSOR_TIMER) do
176
- check_predecessor
260
+ Fiber.new do
261
+ check_predecessor
262
+ end.resume
177
263
  end
178
264
  end
179
265
 
@@ -187,7 +273,7 @@ module Herdis
187
273
  default_options.rmerge(options)))
188
274
  end
189
275
  end
190
- yield
276
+ yield if block_given?
191
277
  Fiber.new do
192
278
  multi.perform while !multi.finished?
193
279
  end.resume
@@ -195,6 +281,8 @@ module Herdis
195
281
 
196
282
  def join_cluster(url)
197
283
  shutdown
284
+ @shutdown = false
285
+ logger.info("#{shepherd_id} *** joining #{url}")
198
286
  @shepherds = Yajl::Parser.parse(EM::HttpRequest.new(url).get(:path => "/cluster",
199
287
  :head => {"Content-Type" => "application/json"}).response)
200
288
  add_shepherd(shepherd_status)
@@ -203,6 +291,7 @@ module Herdis
203
291
  def add_shepherd(shepherd_status)
204
292
  unless shepherd_status == shepherds[shepherd_status["id"]]
205
293
  shepherds[shepherd_status["id"]] = shepherd_status
294
+ save_config!
206
295
  to_each_sibling(:aput,
207
296
  :path => "/#{shepherd_status["id"]}",
208
297
  :body => Yajl::Encoder.encode(shepherd_status)) do
@@ -214,6 +303,7 @@ module Herdis
214
303
  def remove_shepherd(shepherd_id)
215
304
  if shepherds.include?(shepherd_id)
216
305
  shepherds.delete(shepherd_id)
306
+ save_config!
217
307
  to_each_sibling(:adelete,
218
308
  :path => "/#{shepherd_id}") do
219
309
  check_shards
@@ -346,12 +436,26 @@ module Herdis
346
436
  end
347
437
  end
348
438
 
439
+ def shutdown_cluster
440
+ shutdown
441
+ logger.info("#{shepherd_id} *** shutting down cluster")
442
+ to_each_sibling(:adelete,
443
+ :path => "/cluster")
444
+ end
445
+
349
446
  def shutdown
350
- masters.keys.each do |shard_id|
351
- shutdown_shard(shard_id)
352
- end
353
- slaves.keys.each do |shard_id|
354
- shutdown_slave(shard_id)
447
+ unless @shutdown
448
+ logger.info("#{shepherd_id} *** shutting down")
449
+ @shutdown = true
450
+ save_config!
451
+ @check_slave_timer.cancel if @check_slave_timer
452
+ @check_predecessor_timer.cancel if @check_predecessor_timer
453
+ masters.keys.each do |shard_id|
454
+ shutdown_shard(shard_id)
455
+ end
456
+ slaves.keys.each do |shard_id|
457
+ shutdown_slave(shard_id)
458
+ end
355
459
  end
356
460
  end
357
461
 
@@ -408,19 +512,25 @@ module Herdis
408
512
  slaves[shard_id.to_s].enslave!(external_shards[shard_id.to_s])
409
513
  end
410
514
 
411
- logger.debug "#{shepherd_id} *** killing masters #{masters_needing_to_be_shut_down.inspect}" unless masters_needing_to_be_shut_down.empty?
412
- masters_needing_to_be_shut_down.each do |shard_id|
413
- raise "Already liberated, enslaved or directed #{shard_id}!" if handled.include?(shard_id.to_s)
414
- handled.add(shard_id.to_s)
415
- shutdown_shard(shard_id)
416
- end
417
- remove_shards(shepherd_id, masters_needing_to_be_shut_down, false)
418
-
419
- logger.debug "#{shepherd_id} *** killing slaves #{slaves_needing_to_be_shut_down.inspect}" unless slaves_needing_to_be_shut_down.empty?
420
- slaves_needing_to_be_shut_down.each do |shard_id|
421
- raise "Already liberated, enslaved, directed or shut down #{shard_id}!" if handled.include?(shard_id.to_s)
422
- handled.add(shard_id.to_s)
423
- shutdown_slave(shard_id)
515
+ unless masters_needing_to_be_shut_down.empty?
516
+ logger.debug "#{shepherd_id} *** killing masters #{masters_needing_to_be_shut_down.inspect}"
517
+ masters_needing_to_be_shut_down.each do |shard_id|
518
+ raise "Already liberated, enslaved or directed #{shard_id}!" if handled.include?(shard_id.to_s)
519
+ handled.add(shard_id.to_s)
520
+ shutdown_shard(shard_id)
521
+ end
522
+ save_config!
523
+ remove_shards(shepherd_id, masters_needing_to_be_shut_down, false)
524
+ end
525
+
526
+ unless slaves_needing_to_be_shut_down.empty?
527
+ logger.debug "#{shepherd_id} *** killing slaves #{slaves_needing_to_be_shut_down.inspect}"
528
+ slaves_needing_to_be_shut_down.each do |shard_id|
529
+ raise "Already liberated, enslaved, directed or shut down #{shard_id}!" if handled.include?(shard_id.to_s)
530
+ handled.add(shard_id.to_s)
531
+ shutdown_slave(shard_id)
532
+ end
533
+ save_config!
424
534
  end
425
535
 
426
536
  logger.debug "#{shepherd_id} *** creating slaves #{new_slaves_needed.inspect}" unless new_slaves_needed.empty?
@@ -450,6 +560,7 @@ module Herdis
450
560
 
451
561
  def create_master_shard(shard_id)
452
562
  masters[shard_id.to_s] = create_shard(shard_id)
563
+ save_config!
453
564
  end
454
565
 
455
566
  def status
@@ -509,7 +620,7 @@ module Herdis
509
620
  pre = predecessor
510
621
  if pre && pre["id"] != shepherd_id
511
622
  Fiber.new do
512
- if EM::HttpRequest.new(pre["url"]).head.response_header.status != 204
623
+ if EM::HttpRequest.new(pre["url"]).head(:connect_timeout => 10, :inactivity_timeout => 20).response_header.status != 204
513
624
  logger.warn("#{shepherd_id} *** dropping #{pre["id"]} due to failure to respond to ping")
514
625
  remove_shepherd(pre["id"])
515
626
  end
@@ -519,6 +630,7 @@ module Herdis
519
630
 
520
631
  def create_slave_shard(shard_id, external_uri)
521
632
  slaves[shard_id.to_s] = create_shard(shard_id, :master => external_uri)
633
+ save_config!
522
634
  end
523
635
 
524
636
  def create_shard(shard_id, options = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: herdis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-22 00:00:00.000000000 Z
12
+ date: 2012-03-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hiredis
16
- requirement: &70143808249500 !ruby/object:Gem::Requirement
16
+ requirement: &70336754636440 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70143808249500
24
+ version_requirements: *70336754636440
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: em-synchrony
27
- requirement: &70143808248940 !ruby/object:Gem::Requirement
27
+ requirement: &70336754635880 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70143808248940
35
+ version_requirements: *70336754635880
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: em-http-request
38
- requirement: &70143808248500 !ruby/object:Gem::Requirement
38
+ requirement: &70336754635440 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70143808248500
46
+ version_requirements: *70336754635440
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: redis
49
- requirement: &70143808248080 !ruby/object:Gem::Requirement
49
+ requirement: &70336754635020 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70143808248080
57
+ version_requirements: *70336754635020
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: yajl-ruby
60
- requirement: &70143808247640 !ruby/object:Gem::Requirement
60
+ requirement: &70336754634580 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70143808247640
68
+ version_requirements: *70336754634580
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: goliath
71
- requirement: &70143808247200 !ruby/object:Gem::Requirement
71
+ requirement: &70336754634140 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *70143808247200
79
+ version_requirements: *70336754634140
80
80
  description: ! 'A Redis herder for simplifying Redis presharding
81
81
 
82
82
  '
@@ -100,6 +100,7 @@ files:
100
100
  - lib/herdis/handlers/sanity.rb
101
101
  - lib/herdis/handlers/shards.rb
102
102
  - lib/herdis/handlers/shutdown.rb
103
+ - lib/herdis/handlers/shutdown_cluster.rb
103
104
  - lib/herdis/plugins/shepherd_connection.rb
104
105
  - lib/herdis/rack/default_headers.rb
105
106
  - lib/herdis/rack/favicon.rb