herdis 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/herdis/client.rb +16 -12
- data/lib/herdis/handlers/ping.rb +5 -1
- data/lib/herdis/handlers/shutdown_cluster.rb +18 -0
- data/lib/herdis/plugins/shepherd_connection.rb +6 -0
- data/lib/herdis/server.rb +3 -0
- data/lib/herdis/shepherd.rb +157 -45
- metadata +15 -14
data/lib/herdis/client.rb
CHANGED
@@ -116,21 +116,25 @@ module Herdis
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def method_missing(meth, *args, &block)
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
132
|
-
|
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
|
|
data/lib/herdis/handlers/ping.rb
CHANGED
@@ -7,7 +7,11 @@ module Herdis
|
|
7
7
|
include Common
|
8
8
|
|
9
9
|
def response(env)
|
10
|
-
|
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
|
@@ -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)
|
data/lib/herdis/server.rb
CHANGED
@@ -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
|
data/lib/herdis/shepherd.rb
CHANGED
@@ -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
|
-
|
87
|
-
|
94
|
+
initialization = Proc.new do |p|
|
95
|
+
unless master
|
88
96
|
begin
|
89
|
-
connection.
|
90
|
-
|
91
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
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
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70336754636440
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: em-synchrony
|
27
|
-
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: *
|
35
|
+
version_requirements: *70336754635880
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: em-http-request
|
38
|
-
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: *
|
46
|
+
version_requirements: *70336754635440
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: redis
|
49
|
-
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: *
|
57
|
+
version_requirements: *70336754635020
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: yajl-ruby
|
60
|
-
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: *
|
68
|
+
version_requirements: *70336754634580
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: goliath
|
71
|
-
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: *
|
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
|