beetle 0.3.0.rc.17 → 0.3.0.rc.18
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +5 -5
- data/Rakefile +21 -6
- data/beetle.gemspec +3 -4
- data/features/support/test_daemons/redis.conf.erb +0 -5
- data/lib/beetle.rb +3 -1
- data/lib/beetle/commands/garbage_collect_deduplication_store.rb +50 -0
- data/lib/beetle/deduplication_store.rb +55 -2
- data/lib/beetle/message.rb +4 -0
- data/lib/beetle/redis_server_info.rb +1 -1
- data/lib/beetle/version.rb +1 -1
- data/script/start_rabbit +3 -0
- data/test/beetle/deduplication_store_test.rb +22 -0
- data/test/beetle/message_test.rb +12 -3
- data/test/beetle/redis_configuration_server_test.rb +7 -0
- data/test/colorized_test_output.rb +1 -1
- data/test/test_helper.rb +1 -5
- metadata +10 -9
data/README.rdoc
CHANGED
@@ -39,7 +39,7 @@ More information can be found on the {project website}[http://xing.github.com/be
|
|
39
39
|
b.publish :test, "I'm a test message"
|
40
40
|
|
41
41
|
=== Subscribing
|
42
|
-
b.
|
42
|
+
b.listen_queues
|
43
43
|
|
44
44
|
=== Examples
|
45
45
|
|
@@ -65,16 +65,16 @@ To set up a redundant messaging system you will need
|
|
65
65
|
At runtime, Beetle will use
|
66
66
|
* {uuid4r}[http://github.com/skaes/uuid4r]
|
67
67
|
(which needs ossp-uuid)
|
68
|
-
* {bunny}[http://github.com/
|
69
|
-
* {redis}[http://github.com/
|
70
|
-
* {amqp}[http://github.com/
|
68
|
+
* {bunny}[http://github.com/ruby-amqp/bunny]
|
69
|
+
* {redis}[http://github.com/redis/redis-rb]
|
70
|
+
* {amqp}[http://github.com/ruby-amqp/amqp]
|
71
71
|
(which is based on {eventmachine}[http://github.com/eventmachine/eventmachine])
|
72
72
|
* {daemons}[http://daemons.rubyforge.org/]
|
73
73
|
* activesupport
|
74
74
|
|
75
75
|
For development, you'll need
|
76
76
|
* {mocha}[http://github.com/floehopper/mocha]
|
77
|
-
* {rcov}[http://github.com/relevance/rcov]
|
77
|
+
* {rcov}[http://github.com/relevance/rcov] (for ruby 1.8.7)
|
78
78
|
* {cucumber}[http://github.com/aslakhellesoy/cucumber]
|
79
79
|
* {daemon_controller}[http://github.com/FooBarWidget/daemon_controller]
|
80
80
|
|
data/Rakefile
CHANGED
@@ -53,20 +53,35 @@ namespace :beetle do
|
|
53
53
|
end
|
54
54
|
|
55
55
|
namespace :rabbit do
|
56
|
-
def start(node_name, port)
|
56
|
+
def start(node_name, port, web_port)
|
57
57
|
script = File.expand_path(File.dirname(__FILE__)+"/script/start_rabbit")
|
58
|
-
|
58
|
+
# on my machine, the rabbitmq user is not be allowed to access my files.
|
59
|
+
# so we need to put the config file under /tmp
|
60
|
+
config_file = "/tmp/beetle-testing-rabbitmq-#{node_name}"
|
61
|
+
|
62
|
+
create_config_file config_file, web_port
|
63
|
+
|
64
|
+
puts "starting rabbit #{node_name} on port #{port}, web management port #{web_port}"
|
59
65
|
puts "type ^C a RETURN to abort"
|
60
66
|
sleep 1
|
61
|
-
exec "sudo #{script} #{node_name} #{port}"
|
67
|
+
exec "sudo #{script} #{node_name} #{port} #{config_file}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_config_file(config_file, web_port)
|
71
|
+
File.open("#{config_file}.config",'w') do |f|
|
72
|
+
f.puts "["
|
73
|
+
f.puts " {rabbitmq_management, [{listener, [{port, #{web_port}}]}]}"
|
74
|
+
f.puts "]."
|
75
|
+
end
|
62
76
|
end
|
77
|
+
|
63
78
|
desc "start rabbit instance 1"
|
64
79
|
task :start1 do
|
65
|
-
start "rabbit1", 5672
|
80
|
+
start "rabbit1", 5672, 15672
|
66
81
|
end
|
67
82
|
desc "start rabbit instance 2"
|
68
83
|
task :start2 do
|
69
|
-
start "rabbit2", 5673
|
84
|
+
start "rabbit2", 5673, 15673
|
70
85
|
end
|
71
86
|
desc "reset rabbit instances (deletes all data!)"
|
72
87
|
task :reset do
|
@@ -109,7 +124,7 @@ end
|
|
109
124
|
|
110
125
|
require 'rdoc/task'
|
111
126
|
|
112
|
-
|
127
|
+
RDoc::Task.new do |rdoc|
|
113
128
|
rdoc.rdoc_dir = 'site/rdoc'
|
114
129
|
rdoc.title = 'Beetle'
|
115
130
|
rdoc.main = 'README.rdoc'
|
data/beetle.gemspec
CHANGED
@@ -36,11 +36,10 @@ Gem::Specification.new do |s|
|
|
36
36
|
s.add_runtime_dependency("bunny", ["= 0.7.9"])
|
37
37
|
s.add_runtime_dependency("redis", ["= 3.0.1"])
|
38
38
|
s.add_runtime_dependency("hiredis", ["= 0.4.5"])
|
39
|
-
s.add_runtime_dependency("amq-client", ["= 0.9.
|
40
|
-
s.add_runtime_dependency("amq-protocol", ["= 0.
|
41
|
-
s.add_runtime_dependency("amqp", ["= 0.9.
|
39
|
+
s.add_runtime_dependency("amq-client", ["= 0.9.10"])
|
40
|
+
s.add_runtime_dependency("amq-protocol", ["= 1.0.1"])
|
41
|
+
s.add_runtime_dependency("amqp", ["= 0.9.8"])
|
42
42
|
s.add_runtime_dependency("activesupport", [">= 2.3.4"])
|
43
43
|
s.add_runtime_dependency("eventmachine_httpserver", [">= 0.2.1"])
|
44
44
|
s.add_runtime_dependency("daemons", [">= 1.0.10"])
|
45
45
|
end
|
46
|
-
|
@@ -166,11 +166,6 @@ appendfsync everysec
|
|
166
166
|
|
167
167
|
############################### ADVANCED CONFIG ###############################
|
168
168
|
|
169
|
-
# Glue small output buffers together in order to send small replies in a
|
170
|
-
# single TCP packet. Uses a bit more CPU but most of the times it is a win
|
171
|
-
# in terms of number of queries per second. Use 'yes' if unsure.
|
172
|
-
glueoutputbuf yes
|
173
|
-
|
174
169
|
# Use object sharing. Can save a lot of memory if you have many common
|
175
170
|
# string in your dataset, but performs lookups against the shared objects
|
176
171
|
# pool so it uses more CPU and can be a bit slower. Usually it's a good
|
data/lib/beetle.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
$:.unshift(File.expand_path('..', __FILE__))
|
2
|
-
require '
|
2
|
+
require 'amq/client' # defines AMQ::Client::Settings
|
3
|
+
require 'bunny' # which bunny picks up
|
4
|
+
require 'qrack/errors' # needed by the publisher
|
3
5
|
require 'uuid4r'
|
4
6
|
require 'redis/connection/hiredis' # require *before* redis as specified in the redis-rb gem docs
|
5
7
|
require 'redis'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'beetle'
|
3
|
+
|
4
|
+
module Beetle
|
5
|
+
module Commands
|
6
|
+
# Command to garbage collect the deduplication store
|
7
|
+
#
|
8
|
+
# Usage: beetle garbage_collect_deduplication_store [options]
|
9
|
+
#
|
10
|
+
# options:
|
11
|
+
# --redis-servers LIST Required (e.g. 192.168.0.1:6379,192.168.0.2:6379)
|
12
|
+
# --config-file PATH Path to an external yaml config file
|
13
|
+
# -v, --verbose
|
14
|
+
# -h, --help Show this message
|
15
|
+
#
|
16
|
+
class GarbageCollectDeduplicationStore
|
17
|
+
# parses command line options and starts Beetle::RedisConfigurationServer as a daemon
|
18
|
+
def self.execute
|
19
|
+
opts = OptionParser.new
|
20
|
+
opts.banner = "Usage: beetle garbage_collect_deduplication_store [options]"
|
21
|
+
opts.separator ""
|
22
|
+
|
23
|
+
opts.on("--redis-servers LIST", Array, "Required (e.g. 192.168.0.1:6379,192.168.0.2:6379)") do |val|
|
24
|
+
Beetle.config.redis_servers = val.join(",")
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--config-file PATH", String, "Path to an external yaml config file") do |val|
|
28
|
+
Beetle.config.config_file = val
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("--redis-db N", Integer, "Redis database used for GC") do |val|
|
32
|
+
Beetle.config.redis_db = val.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-v", "--verbose") do |val|
|
36
|
+
Beetle.config.logger.level = Logger::DEBUG
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
40
|
+
puts opts
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.parse!(ARGV)
|
45
|
+
|
46
|
+
DeduplicationStore.new.garbage_collect_keys_using_master_and_slave
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -48,16 +48,69 @@ module Beetle
|
|
48
48
|
key =~ /^(msgid:[^:]*:[-0-9a-f]*):.*$/ && $1
|
49
49
|
end
|
50
50
|
|
51
|
+
# garbage collect using master and slave (and redis-cli)
|
52
|
+
def garbage_collect_keys_using_master_and_slave(now = Time.now.to_i)
|
53
|
+
logger.info "garbage collecting obsolete redis keys from #{@config.redis_servers}"
|
54
|
+
info = RedisServerInfo.new(@config, {})
|
55
|
+
info.refresh
|
56
|
+
unless connection = info.slaves.first
|
57
|
+
logger.warn "no slave available, falling back to master."
|
58
|
+
connection = redis
|
59
|
+
end
|
60
|
+
file = "/tmp/beetle_redis_expire_keys_#{$$}.txt"
|
61
|
+
cmd = "redis-cli -h #{connection.host} -p #{connection.port} -n #{@config.redis_db} keys 'msgid:*:expires' > #{file}"
|
62
|
+
logger.info "retrieving expire keys: '#{cmd}'"
|
63
|
+
if system(cmd)
|
64
|
+
garbage_collect_keys_from_file(file, now)
|
65
|
+
else
|
66
|
+
logger.error "could not retrieve expire keys"
|
67
|
+
end
|
68
|
+
rescue => e
|
69
|
+
logger.error "#{e.class}(#{e})"
|
70
|
+
ensure
|
71
|
+
system("rm -f #{file}")
|
72
|
+
end
|
73
|
+
|
51
74
|
# garbage collect keys in Redis (always assume the worst!)
|
52
75
|
def garbage_collect_keys(now = Time.now.to_i)
|
53
76
|
keys = redis.keys("msgid:*:expires")
|
54
77
|
threshold = now + @config.gc_threshold
|
55
78
|
keys.each do |key|
|
56
|
-
|
57
|
-
|
79
|
+
gc_key(key, threshold)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# garbage collect keys from a given file
|
84
|
+
def garbage_collect_keys_from_file(file_name, now = Time.now.to_i)
|
85
|
+
threshold = now + @config.gc_threshold
|
86
|
+
expired = total = 0
|
87
|
+
File.foreach(file_name) do |line|
|
88
|
+
line.chomp!
|
89
|
+
if line =~ /^msgid:.*:expires$/
|
90
|
+
total += 1
|
91
|
+
expired += 1 if gc_key(line, threshold)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
logger.info "expired #{expired} keys out of #{total}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# garbage collect a single key if it's older than given threshold
|
98
|
+
def gc_key(key, threshold)
|
99
|
+
expires_at = redis.get key
|
100
|
+
if expires_at
|
101
|
+
if expires_at.to_i < threshold
|
58
102
|
msg_id = msg_id(key)
|
103
|
+
# puts "expiring: #{msg_id}"
|
59
104
|
redis.del(*keys(msg_id))
|
105
|
+
return true
|
106
|
+
else
|
107
|
+
# keys has future expiry date
|
108
|
+
# puts "#{msg_id} expires on #{Time.at(expires_at.to_i)}"
|
109
|
+
return false
|
60
110
|
end
|
111
|
+
else
|
112
|
+
# key might have been deleted in the meantime
|
113
|
+
return false
|
61
114
|
end
|
62
115
|
end
|
63
116
|
|
data/lib/beetle/message.rb
CHANGED
@@ -263,10 +263,12 @@ module Beetle
|
|
263
263
|
elsif !timed_out?
|
264
264
|
RC::HandlerNotYetTimedOut
|
265
265
|
elsif attempts_limit_reached?
|
266
|
+
completed!
|
266
267
|
ack!
|
267
268
|
logger.warn "Beetle: reached the handler execution attempts limit: #{attempts_limit} on #{msg_id}"
|
268
269
|
RC::AttemptsLimitReached
|
269
270
|
elsif exceptions_limit_reached?
|
271
|
+
completed!
|
270
272
|
ack!
|
271
273
|
logger.warn "Beetle: reached the handler exceptions limit: #{exceptions_limit} on #{msg_id}"
|
272
274
|
RC::ExceptionsLimitReached
|
@@ -306,10 +308,12 @@ module Beetle
|
|
306
308
|
def handler_failed!(result)
|
307
309
|
increment_exception_count!
|
308
310
|
if attempts_limit_reached?
|
311
|
+
completed!
|
309
312
|
ack!
|
310
313
|
logger.debug "Beetle: reached the handler execution attempts limit: #{attempts_limit} on #{msg_id}"
|
311
314
|
RC::AttemptsLimitReached
|
312
315
|
elsif exceptions_limit_reached?
|
316
|
+
completed!
|
313
317
|
ack!
|
314
318
|
logger.debug "Beetle: reached the handler exceptions limit: #{exceptions_limit} on #{msg_id}"
|
315
319
|
RC::ExceptionsLimitReached
|
@@ -14,7 +14,7 @@ module Beetle
|
|
14
14
|
@instances ||= @config.redis_servers.split(/ *, */).map{|s| Redis.from_server_string(s, @options)}
|
15
15
|
end
|
16
16
|
|
17
|
-
# fetches the server from the
|
17
|
+
# fetches the server from the instances whith the given <tt>server</tt> string
|
18
18
|
def find(server)
|
19
19
|
instances.find{|r| r.server == server}
|
20
20
|
end
|
data/lib/beetle/version.rb
CHANGED
data/script/start_rabbit
CHANGED
@@ -21,9 +21,12 @@ export RABBITMQ_NODENAME=$1
|
|
21
21
|
export RABBITMQ_NODE_PORT=$2
|
22
22
|
# Defaults to 5672.
|
23
23
|
|
24
|
+
export RABBITMQ_CONFIG_FILE=$3
|
24
25
|
# RABBITMQ_CLUSTER_CONFIG_FILE
|
25
26
|
# Defaults to /etc/rabbitmq/rabbitmq_cluster.config. If this file is present it
|
26
27
|
# is used by the server to auto-configure a RabbitMQ cluster. See the clustering
|
27
28
|
# guide at <http://www.rabbitmq.com/clustering.html> for details.
|
28
29
|
|
29
30
|
rabbitmq-server
|
31
|
+
|
32
|
+
rm -f $RABBITMQ_CONFIG_FILE
|
@@ -111,4 +111,26 @@ module Beetle
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
class GarbageCollectionTest < Test::Unit::TestCase
|
115
|
+
def setup
|
116
|
+
@store = DeduplicationStore.new
|
117
|
+
end
|
118
|
+
|
119
|
+
test "never tries to delete message keys when expire key doesn not exist" do
|
120
|
+
key = "foo"
|
121
|
+
@store.redis.del key
|
122
|
+
assert !@store.gc_key(key, 0)
|
123
|
+
end
|
124
|
+
|
125
|
+
test "rescues exeptions and logs an error" do
|
126
|
+
RedisServerInfo.expects(:new).raises("foo")
|
127
|
+
assert_nothing_raised { @store.garbage_collect_keys_using_master_and_slave }
|
128
|
+
end
|
129
|
+
|
130
|
+
test "logs an error when system command fails" do
|
131
|
+
@store.stubs(:system).returns(false)
|
132
|
+
@store.logger.expects(:error)
|
133
|
+
@store.garbage_collect_keys_using_master_and_slave
|
134
|
+
end
|
135
|
+
end
|
114
136
|
end
|
data/test/beetle/message_test.rb
CHANGED
@@ -93,6 +93,16 @@ module Beetle
|
|
93
93
|
@store.garbage_collect_keys(Time.now.to_i+1)
|
94
94
|
end
|
95
95
|
|
96
|
+
test "should be able to garbage collect expired keys using master and slave" do
|
97
|
+
Beetle.config.expects(:gc_threshold).returns(0)
|
98
|
+
header = header_with_params({:ttl => 0})
|
99
|
+
message = Message.new("somequeue", header, 'foo', :store => @store)
|
100
|
+
assert !message.key_exists?
|
101
|
+
assert message.key_exists?
|
102
|
+
@store.redis.expects(:del).with(*@store.keys(message.msg_id))
|
103
|
+
@store.garbage_collect_keys_using_master_and_slave(Time.now.to_i+1)
|
104
|
+
end
|
105
|
+
|
96
106
|
test "should not garbage collect not yet expired keys" do
|
97
107
|
Beetle.config.expects(:gc_threshold).returns(0)
|
98
108
|
header = header_with_params({:ttl => 0})
|
@@ -348,7 +358,7 @@ module Beetle
|
|
348
358
|
|
349
359
|
proc = lambda {|*args| raise "crash"}
|
350
360
|
s = sequence("s")
|
351
|
-
message.expects(:completed!).
|
361
|
+
message.expects(:completed!).once
|
352
362
|
header.expects(:ack)
|
353
363
|
assert_equal RC::ExceptionsLimitReached, message.__send__(:process_internal, proc)
|
354
364
|
end
|
@@ -363,7 +373,7 @@ module Beetle
|
|
363
373
|
|
364
374
|
proc = lambda {|*args| raise "crash"}
|
365
375
|
s = sequence("s")
|
366
|
-
message.expects(:completed!).
|
376
|
+
message.expects(:completed!).once
|
367
377
|
header.expects(:ack)
|
368
378
|
assert_equal RC::AttemptsLimitReached, message.__send__(:process_internal, proc)
|
369
379
|
end
|
@@ -765,4 +775,3 @@ module Beetle
|
|
765
775
|
end
|
766
776
|
|
767
777
|
end
|
768
|
-
|
@@ -324,5 +324,12 @@ module Beetle
|
|
324
324
|
@server.beetle.expects(:publish).with(:system_notification, ({:message => msg}).to_json)
|
325
325
|
@server.client_started(payload)
|
326
326
|
end
|
327
|
+
|
328
|
+
test "should not send a system notification when receiving a client started message from a known client" do
|
329
|
+
payload = {"id" => "rc-client-1"}
|
330
|
+
msg = "Received client_started message from unknown id 'unknown-client'"
|
331
|
+
@server.beetle.expects(:publish).never
|
332
|
+
@server.client_started(payload)
|
333
|
+
end
|
327
334
|
end
|
328
335
|
end
|
data/test/test_helper.rb
CHANGED
@@ -12,17 +12,13 @@ require 'active_support/testing/declarative'
|
|
12
12
|
require File.expand_path(File.dirname(__FILE__) + '/../lib/beetle')
|
13
13
|
require File.expand_path(File.dirname(__FILE__) + '/colorized_test_output')
|
14
14
|
|
15
|
-
# we can remove this hack which is needed only for testing
|
16
|
-
require 'qrack/errors'
|
17
|
-
|
18
|
-
|
19
15
|
class Test::Unit::TestCase
|
20
16
|
extend ActiveSupport::Testing::Declarative
|
21
17
|
end
|
22
18
|
|
23
|
-
|
24
19
|
Beetle.config.logger = Logger.new(File.dirname(__FILE__) + '/../test.log')
|
25
20
|
Beetle.config.redis_server = "localhost:6379"
|
21
|
+
Beetle.config.redis_servers = "localhost:6379,localhost:6380"
|
26
22
|
|
27
23
|
|
28
24
|
def header_with_params(opts = {})
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beetle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.0.rc.
|
4
|
+
version: 0.3.0.rc.18
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date:
|
16
|
+
date: 2013-01-12 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: uuid4r
|
@@ -86,7 +86,7 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - '='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.9.
|
89
|
+
version: 0.9.10
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -94,7 +94,7 @@ dependencies:
|
|
94
94
|
requirements:
|
95
95
|
- - '='
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: 0.9.
|
97
|
+
version: 0.9.10
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: amq-protocol
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,7 +102,7 @@ dependencies:
|
|
102
102
|
requirements:
|
103
103
|
- - '='
|
104
104
|
- !ruby/object:Gem::Version
|
105
|
-
version: 0.
|
105
|
+
version: 1.0.1
|
106
106
|
type: :runtime
|
107
107
|
prerelease: false
|
108
108
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -110,7 +110,7 @@ dependencies:
|
|
110
110
|
requirements:
|
111
111
|
- - '='
|
112
112
|
- !ruby/object:Gem::Version
|
113
|
-
version: 0.
|
113
|
+
version: 1.0.1
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: amqp
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,7 +118,7 @@ dependencies:
|
|
118
118
|
requirements:
|
119
119
|
- - '='
|
120
120
|
- !ruby/object:Gem::Version
|
121
|
-
version: 0.9.
|
121
|
+
version: 0.9.8
|
122
122
|
type: :runtime
|
123
123
|
prerelease: false
|
124
124
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -126,7 +126,7 @@ dependencies:
|
|
126
126
|
requirements:
|
127
127
|
- - '='
|
128
128
|
- !ruby/object:Gem::Version
|
129
|
-
version: 0.9.
|
129
|
+
version: 0.9.8
|
130
130
|
- !ruby/object:Gem::Dependency
|
131
131
|
name: activesupport
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,6 +203,7 @@ files:
|
|
203
203
|
- lib/beetle/client.rb
|
204
204
|
- lib/beetle/commands/configuration_client.rb
|
205
205
|
- lib/beetle/commands/configuration_server.rb
|
206
|
+
- lib/beetle/commands/garbage_collect_deduplication_store.rb
|
206
207
|
- lib/beetle/commands.rb
|
207
208
|
- lib/beetle/configuration.rb
|
208
209
|
- lib/beetle/deduplication_store.rb
|
@@ -276,7 +277,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
276
277
|
version: '0'
|
277
278
|
segments:
|
278
279
|
- 0
|
279
|
-
hash: -
|
280
|
+
hash: -3293468122773492105
|
280
281
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
281
282
|
none: false
|
282
283
|
requirements:
|