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