herdis 0.0.6 → 0.0.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.
@@ -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