beetle 0.3.0.rc.17 → 0.3.0.rc.18
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/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:
|