beetle 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/REDIS_AUTO_FAILOVER.rdoc +124 -0
- data/RELEASE_NOTES.rdoc +50 -0
- data/Rakefile +113 -0
- data/beetle.gemspec +3 -3
- data/features/README.rdoc +23 -0
- data/features/redis_auto_failover.feature +105 -0
- data/features/step_definitions/redis_auto_failover_steps.rb +133 -0
- data/features/support/beetle_handler +32 -0
- data/features/support/env.rb +48 -0
- data/features/support/system_notification_logger +31 -0
- data/features/support/test_daemons/redis.conf.erb +189 -0
- data/features/support/test_daemons/redis.rb +186 -0
- data/features/support/test_daemons/redis_configuration_client.rb +64 -0
- data/features/support/test_daemons/redis_configuration_server.rb +52 -0
- data/lib/beetle/redis_configuration_server.rb +10 -0
- data/script/console +28 -0
- data/script/start_rabbit +29 -0
- data/test/beetle/deduplication_store_test.rb +3 -1
- data/test/beetle/redis_configuration_server_test.rb +18 -0
- metadata +25 -4
@@ -0,0 +1,133 @@
|
|
1
|
+
Given /^a redis server "([^\"]*)" exists as master$/ do |redis_name|
|
2
|
+
TestDaemons::Redis[redis_name].start
|
3
|
+
TestDaemons::Redis[redis_name].master
|
4
|
+
end
|
5
|
+
|
6
|
+
Given /^a redis server "([^\"]*)" exists as slave of "([^\"]*)"$/ do |redis_name, redis_master_name|
|
7
|
+
TestDaemons::Redis[redis_name].start
|
8
|
+
Given "redis server \"#{redis_name}\" is slave of \"#{redis_master_name}\""
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^redis server "([^\"]*)" is master$/ do |redis_name|
|
12
|
+
TestDaemons::Redis[redis_name].master
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^redis server "([^\"]*)" is slave of "([^\"]*)"$/ do |redis_name, redis_master_name|
|
16
|
+
TestDaemons::Redis[redis_name].slave_of(TestDaemons::Redis[redis_master_name].port)
|
17
|
+
master = TestDaemons::Redis[redis_master_name].redis
|
18
|
+
slave = TestDaemons::Redis[redis_name].redis
|
19
|
+
begin
|
20
|
+
sleep 1
|
21
|
+
end while !slave.slave_of?(master.host, master.port)
|
22
|
+
end
|
23
|
+
|
24
|
+
Given /^a redis configuration server using redis servers "([^\"]*)" with clients "([^\"]*)" exists$/ do |redis_names, redis_configuration_client_names|
|
25
|
+
redis_servers = redis_names.split(",").map { |redis_name| TestDaemons::Redis[redis_name].ip_with_port }.join(",")
|
26
|
+
TestDaemons::RedisConfigurationServer.start(redis_servers, redis_configuration_client_names)
|
27
|
+
end
|
28
|
+
|
29
|
+
Given /^a redis configuration client "([^\"]*)" using redis servers "([^\"]*)" exists$/ do |redis_configuration_client_name, redis_names|
|
30
|
+
redis_servers = redis_names.split(",").map do |redis_name|
|
31
|
+
TestDaemons::Redis[redis_name].ip_with_port
|
32
|
+
end
|
33
|
+
TestDaemons::RedisConfigurationClient[redis_configuration_client_name].start
|
34
|
+
end
|
35
|
+
|
36
|
+
Given /^redis server "([^\"]*)" is down$/ do |redis_name|
|
37
|
+
TestDaemons::Redis[redis_name].stop
|
38
|
+
end
|
39
|
+
|
40
|
+
Given /^the retry timeout for the redis master check is reached$/ do
|
41
|
+
basetime = Time.now
|
42
|
+
i = 0
|
43
|
+
while (i <= 10.0) do
|
44
|
+
break if TestDaemons::RedisConfigurationClient.instances.values.all? {|instance| File.mtime(instance.redis_master_file) > basetime rescue false}
|
45
|
+
i += 0.1
|
46
|
+
sleep(0.1)
|
47
|
+
end
|
48
|
+
sleep 1 # give it time to switch because the modified mtime might be because of the initial invalidation and not the switch
|
49
|
+
end
|
50
|
+
|
51
|
+
Given /^a beetle handler using the redis-master file from "([^\"]*)" exists$/ do |redis_configuration_client_name|
|
52
|
+
master_file = redis_master_file(redis_configuration_client_name)
|
53
|
+
`ruby features/support/beetle_handler start -- --redis-master-file=#{master_file}`
|
54
|
+
assert File.exist?(master_file), "file #{master_file} does not exist"
|
55
|
+
end
|
56
|
+
|
57
|
+
Given /^redis server "([^\"]*)" is down for less seconds than the retry timeout for the redis master check$/ do |redis_name|
|
58
|
+
TestDaemons::Redis[redis_name].restart(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
Given /^the retry timeout for the redis master determination is reached$/ do
|
62
|
+
sleep 1
|
63
|
+
end
|
64
|
+
|
65
|
+
Given /^redis server "([^\"]*)" is coming back$/ do |redis_name|
|
66
|
+
TestDaemons::Redis[redis_name].start
|
67
|
+
end
|
68
|
+
|
69
|
+
Given /^an old redis master file for "([^\"]*)" with master "([^\"]*)" exists$/ do |redis_configuration_client_name, redis_name|
|
70
|
+
master_file = redis_master_file(redis_configuration_client_name)
|
71
|
+
File.open(master_file, 'w') do |f|
|
72
|
+
f.puts TestDaemons::Redis[redis_name].ip_with_port
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
Then /^the role of redis server "([^\"]*)" should be "(master|slave)"$/ do |redis_name, role|
|
78
|
+
expected_role = false
|
79
|
+
10.times do
|
80
|
+
expected_role = true and break if TestDaemons::Redis[redis_name].__send__ "#{role}?"
|
81
|
+
sleep 1
|
82
|
+
end
|
83
|
+
assert expected_role, "#{redis_name} is not a #{role}"
|
84
|
+
end
|
85
|
+
|
86
|
+
Then /^the redis master of "([^\"]*)" should be "([^\"]*)"$/ do |redis_configuration_client_name, redis_name|
|
87
|
+
master_file = redis_master_file(redis_configuration_client_name)
|
88
|
+
master = false
|
89
|
+
server_info = nil
|
90
|
+
10.times do
|
91
|
+
server_info = File.read(master_file).chomp if File.exist?(master_file)
|
92
|
+
master = true and break if TestDaemons::Redis[redis_name].ip_with_port == server_info
|
93
|
+
sleep 1
|
94
|
+
end
|
95
|
+
assert master, "#{redis_name} is not master of #{redis_configuration_client_name}, master file content: #{server_info.inspect}"
|
96
|
+
end
|
97
|
+
|
98
|
+
Then /^the redis master of "([^\"]*)" should be undefined$/ do |redis_configuration_client_name|
|
99
|
+
master_file = redis_master_file(redis_configuration_client_name)
|
100
|
+
empty = false
|
101
|
+
server_info = nil
|
102
|
+
10.times do
|
103
|
+
server_info = File.read(master_file).chomp if File.exist?(master_file)
|
104
|
+
empty = server_info == ""
|
105
|
+
break if empty
|
106
|
+
sleep 1
|
107
|
+
end
|
108
|
+
assert empty, "master file is not empty: #{server_info}"
|
109
|
+
end
|
110
|
+
|
111
|
+
Then /^the redis master of the beetle handler should be "([^\"]*)"$/ do |redis_name|
|
112
|
+
Beetle.config.servers = "localhost:5672" # rabbitmq
|
113
|
+
Beetle.config.logger.level = Logger::INFO
|
114
|
+
client = Beetle::Client.new
|
115
|
+
client.register_queue(:echo)
|
116
|
+
client.register_message(:echo)
|
117
|
+
assert_equal TestDaemons::Redis[redis_name].ip_with_port, client.rpc(:echo, 'nil').second
|
118
|
+
end
|
119
|
+
|
120
|
+
Then /^a system notification for "([^\"]*)" not being available should be sent$/ do |redis_name|
|
121
|
+
text = "Redis master '#{TestDaemons::Redis[redis_name].ip_with_port}' not available"
|
122
|
+
assert_match /#{text}/, File.readlines(system_notification_log_path).last
|
123
|
+
end
|
124
|
+
|
125
|
+
Then /^a system notification for switching from "([^\"]*)" to "([^\"]*)" should be sent$/ do |old_redis_master_name, new_redis_master_name|
|
126
|
+
text = "Setting redis master to '#{TestDaemons::Redis[new_redis_master_name].ip_with_port}' (was '#{TestDaemons::Redis[old_redis_master_name].ip_with_port}')"
|
127
|
+
assert_match /#{Regexp.escape(text)}/, File.read(system_notification_log_path)
|
128
|
+
end
|
129
|
+
|
130
|
+
Then /^a system notification for no slave available to become new master should be sent$/ do
|
131
|
+
text = "Redis master could not be switched, no slave available to become new master"
|
132
|
+
assert_match /#{text}/, File.readlines(system_notification_log_path).last
|
133
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "daemons"
|
5
|
+
require "optparse"
|
6
|
+
require File.expand_path("../../lib/beetle", File.dirname(__FILE__))
|
7
|
+
|
8
|
+
tmp_path = File.expand_path("../../tmp", File.dirname(__FILE__))
|
9
|
+
|
10
|
+
Daemons.run_proc("beetle_handler", :log_output => true, :dir_mode => :normal, :dir => tmp_path) do
|
11
|
+
opts = OptionParser.new
|
12
|
+
|
13
|
+
opts.on("-f", "--redis-master-file path", String) do |val|
|
14
|
+
Beetle.config.redis_server = val
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.parse!(ARGV - ["start", "--"])
|
18
|
+
|
19
|
+
Beetle.config.servers = "localhost:5672" # rabbitmq
|
20
|
+
|
21
|
+
# set Beetle log level to info, less noisy than debug
|
22
|
+
Beetle.config.logger.level = Logger::INFO
|
23
|
+
|
24
|
+
client = Beetle::Client.new.configure :auto_delete => true do |config|
|
25
|
+
config.queue(:echo)
|
26
|
+
config.message(:echo)
|
27
|
+
config.handler(:echo) {|message| client.deduplication_store.redis.server rescue "no redis master"}
|
28
|
+
end
|
29
|
+
client.listen do
|
30
|
+
puts "Started beetle handler"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../../../lib/beetle', __FILE__)
|
2
|
+
|
3
|
+
# Allow using Test::Unit for step assertions
|
4
|
+
# See http://wiki.github.com/aslakhellesoy/cucumber/using-testunit
|
5
|
+
require 'test/unit/assertions'
|
6
|
+
World(Test::Unit::Assertions)
|
7
|
+
|
8
|
+
Before do
|
9
|
+
`ruby features/support/system_notification_logger start`
|
10
|
+
end
|
11
|
+
|
12
|
+
After do
|
13
|
+
cleanup_test_env
|
14
|
+
end
|
15
|
+
|
16
|
+
at_exit do
|
17
|
+
cleanup_test_env
|
18
|
+
end
|
19
|
+
|
20
|
+
def cleanup_test_env
|
21
|
+
TestDaemons::RedisConfigurationClient.stop_all
|
22
|
+
TestDaemons::RedisConfigurationServer.stop
|
23
|
+
|
24
|
+
`ruby features/support/beetle_handler stop`
|
25
|
+
redis_master_files = tmp_path + "/redis-master-*"
|
26
|
+
`rm -f #{redis_master_files}`
|
27
|
+
|
28
|
+
`ruby features/support/system_notification_logger stop`
|
29
|
+
`rm -f #{system_notification_log_path}`
|
30
|
+
|
31
|
+
TestDaemons::Redis.stop_all
|
32
|
+
end
|
33
|
+
|
34
|
+
def redis_master_file(client_name)
|
35
|
+
tmp_path + "/redis-master-#{client_name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def first_redis_configuration_client_pid
|
39
|
+
File.read("redis_configuration_client0.pid").chomp.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def system_notification_log_path
|
43
|
+
tmp_path + "/system_notifications.log"
|
44
|
+
end
|
45
|
+
|
46
|
+
def tmp_path
|
47
|
+
File.expand_path("../../../tmp", __FILE__)
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "daemons"
|
5
|
+
require File.expand_path("../../lib/beetle", File.dirname(__FILE__))
|
6
|
+
|
7
|
+
tmp_path = File.expand_path("../../tmp", File.dirname(__FILE__))
|
8
|
+
system_notification_log_file_path = "#{tmp_path}/system_notifications.log"
|
9
|
+
|
10
|
+
Daemons.run_proc("system_notification_logger", :log_output => true, :dir_mode => :normal, :dir => tmp_path) do
|
11
|
+
Beetle.config.servers = "localhost:5672, localhost:5673" # rabbitmq
|
12
|
+
|
13
|
+
# set Beetle log level to info, less noisy than debug
|
14
|
+
Beetle.config.logger.level = Logger::DEBUG
|
15
|
+
|
16
|
+
client = Beetle::Client.new
|
17
|
+
client.configure :exchange => :system, :auto_delete => true do |config|
|
18
|
+
config.message :system_notification
|
19
|
+
config.queue :system_notification
|
20
|
+
config.handler :system_notification do |message|
|
21
|
+
payload = ActiveSupport::JSON.decode(message.data)
|
22
|
+
text = payload["message"]
|
23
|
+
puts "Writing message to #{system_notification_log_file_path}: #{text}"
|
24
|
+
File.open(system_notification_log_file_path, "a+") do |f|
|
25
|
+
f << text
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
puts "Started system notification logger"
|
30
|
+
client.listen
|
31
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# Redis configuration file example
|
2
|
+
|
3
|
+
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
4
|
+
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
5
|
+
daemonize yes
|
6
|
+
|
7
|
+
# When run as a daemon, Redis write a pid file in /var/run/redis.pid by default.
|
8
|
+
# You can specify a custom pid file location here.
|
9
|
+
pidfile <%= pid_file %>
|
10
|
+
|
11
|
+
# Accept connections on the specified port, default is 6379
|
12
|
+
port <%= port %>
|
13
|
+
|
14
|
+
# If you want you can bind a single interface, if the bind option is not
|
15
|
+
# specified all the interfaces will listen for connections.
|
16
|
+
#
|
17
|
+
# bind 127.0.0.1
|
18
|
+
|
19
|
+
# Close the connection after a client is idle for N seconds (0 to disable)
|
20
|
+
timeout 300
|
21
|
+
|
22
|
+
# Set server verbosity to 'debug'
|
23
|
+
# it can be one of:
|
24
|
+
# debug (a lot of information, useful for development/testing)
|
25
|
+
# notice (moderately verbose, what you want in production probably)
|
26
|
+
# warning (only very important / critical messages are logged)
|
27
|
+
loglevel debug
|
28
|
+
|
29
|
+
# Specify the log file name. Also 'stdout' can be used to force
|
30
|
+
# the demon to log on the standard output. Note that if you use standard
|
31
|
+
# output for logging but daemonize, logs will be sent to /dev/null
|
32
|
+
logfile <%= log_file %>
|
33
|
+
|
34
|
+
# Set the number of databases. The default database is DB 0, you can select
|
35
|
+
# a different one on a per-connection basis using SELECT <dbid> where
|
36
|
+
# dbid is a number between 0 and 'databases'-1
|
37
|
+
databases 16
|
38
|
+
|
39
|
+
################################ SNAPSHOTTING #################################
|
40
|
+
#
|
41
|
+
# Save the DB on disk:
|
42
|
+
#
|
43
|
+
# save <seconds> <changes>
|
44
|
+
#
|
45
|
+
# Will save the DB if both the given number of seconds and the given
|
46
|
+
# number of write operations against the DB occurred.
|
47
|
+
#
|
48
|
+
# In the example below the behaviour will be to save:
|
49
|
+
# after 900 sec (15 min) if at least 1 key changed
|
50
|
+
# after 300 sec (5 min) if at least 10 keys changed
|
51
|
+
# after 60 sec if at least 10000 keys changed
|
52
|
+
save 900 1
|
53
|
+
save 300 10
|
54
|
+
save 60 10000
|
55
|
+
|
56
|
+
# Compress string objects using LZF when dump .rdb databases?
|
57
|
+
# For default that's set to 'yes' as it's almost always a win.
|
58
|
+
# If you want to save some CPU in the saving child set it to 'no' but
|
59
|
+
# the dataset will likely be bigger if you have compressible values or keys.
|
60
|
+
rdbcompression yes
|
61
|
+
|
62
|
+
# The filename where to dump the DB
|
63
|
+
dbfilename dump.rdb
|
64
|
+
|
65
|
+
# For default save/load DB in/from the working directory
|
66
|
+
# Note that you must specify a directory not a file name.
|
67
|
+
dir <%= dir %>
|
68
|
+
|
69
|
+
################################# REPLICATION #################################
|
70
|
+
|
71
|
+
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
72
|
+
# another Redis server. Note that the configuration is local to the slave
|
73
|
+
# so for example it is possible to configure the slave to save the DB with a
|
74
|
+
# different interval, or to listen to another port, and so on.
|
75
|
+
#
|
76
|
+
# slaveof <masterip> <masterport>
|
77
|
+
|
78
|
+
# If the master is password protected (using the "requirepass" configuration
|
79
|
+
# directive below) it is possible to tell the slave to authenticate before
|
80
|
+
# starting the replication synchronization process, otherwise the master will
|
81
|
+
# refuse the slave request.
|
82
|
+
#
|
83
|
+
# masterauth <master-password>
|
84
|
+
|
85
|
+
################################## SECURITY ###################################
|
86
|
+
|
87
|
+
# Require clients to issue AUTH <PASSWORD> before processing any other
|
88
|
+
# commands. This might be useful in environments in which you do not trust
|
89
|
+
# others with access to the host running redis-server.
|
90
|
+
#
|
91
|
+
# This should stay commented out for backward compatibility and because most
|
92
|
+
# people do not need auth (e.g. they run their own servers).
|
93
|
+
#
|
94
|
+
# requirepass foobared
|
95
|
+
|
96
|
+
################################### LIMITS ####################################
|
97
|
+
|
98
|
+
# Set the max number of connected clients at the same time. By default there
|
99
|
+
# is no limit, and it's up to the number of file descriptors the Redis process
|
100
|
+
# is able to open. The special value '0' means no limts.
|
101
|
+
# Once the limit is reached Redis will close all the new connections sending
|
102
|
+
# an error 'max number of clients reached'.
|
103
|
+
#
|
104
|
+
# maxclients 128
|
105
|
+
|
106
|
+
# Don't use more memory than the specified amount of bytes.
|
107
|
+
# When the memory limit is reached Redis will try to remove keys with an
|
108
|
+
# EXPIRE set. It will try to start freeing keys that are going to expire
|
109
|
+
# in little time and preserve keys with a longer time to live.
|
110
|
+
# Redis will also try to remove objects from free lists if possible.
|
111
|
+
#
|
112
|
+
# If all this fails, Redis will start to reply with errors to commands
|
113
|
+
# that will use more memory, like SET, LPUSH, and so on, and will continue
|
114
|
+
# to reply to most read-only commands like GET.
|
115
|
+
#
|
116
|
+
# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
|
117
|
+
# 'state' server or cache, not as a real DB. When Redis is used as a real
|
118
|
+
# database the memory usage will grow over the weeks, it will be obvious if
|
119
|
+
# it is going to use too much memory in the long run, and you'll have the time
|
120
|
+
# to upgrade. With maxmemory after the limit is reached you'll start to get
|
121
|
+
# errors for write operations, and this may even lead to DB inconsistency.
|
122
|
+
#
|
123
|
+
# maxmemory <bytes>
|
124
|
+
|
125
|
+
############################## APPEND ONLY MODE ###############################
|
126
|
+
|
127
|
+
# By default Redis asynchronously dumps the dataset on disk. If you can live
|
128
|
+
# with the idea that the latest records will be lost if something like a crash
|
129
|
+
# happens this is the preferred way to run Redis. If instead you care a lot
|
130
|
+
# about your data and don't want to that a single record can get lost you should
|
131
|
+
# enable the append only mode: when this mode is enabled Redis will append
|
132
|
+
# every write operation received in the file appendonly.log. This file will
|
133
|
+
# be read on startup in order to rebuild the full dataset in memory.
|
134
|
+
#
|
135
|
+
# Note that you can have both the async dumps and the append only file if you
|
136
|
+
# like (you have to comment the "save" statements above to disable the dumps).
|
137
|
+
# Still if append only mode is enabled Redis will load the data from the
|
138
|
+
# log file at startup ignoring the dump.rdb file.
|
139
|
+
#
|
140
|
+
# The name of the append only file is "appendonly.log"
|
141
|
+
#
|
142
|
+
# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
|
143
|
+
# log file in background when it gets too big.
|
144
|
+
|
145
|
+
appendonly yes
|
146
|
+
|
147
|
+
# The fsync() call tells the Operating System to actually write data on disk
|
148
|
+
# instead to wait for more data in the output buffer. Some OS will really flush
|
149
|
+
# data on disk, some other OS will just try to do it ASAP.
|
150
|
+
#
|
151
|
+
# Redis supports three different modes:
|
152
|
+
#
|
153
|
+
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
154
|
+
# always: fsync after every write to the append only log . Slow, Safest.
|
155
|
+
# everysec: fsync only if one second passed since the last fsync. Compromise.
|
156
|
+
#
|
157
|
+
# The default is "always" that's the safer of the options. It's up to you to
|
158
|
+
# understand if you can relax this to "everysec" that will fsync every second
|
159
|
+
# or to "no" that will let the operating system flush the output buffer when
|
160
|
+
# it want, for better performances (but if you can live with the idea of
|
161
|
+
# some data loss consider the default persistence mode that's snapshotting).
|
162
|
+
|
163
|
+
# appendfsync always
|
164
|
+
appendfsync everysec
|
165
|
+
# appendfsync no
|
166
|
+
|
167
|
+
############################### ADVANCED CONFIG ###############################
|
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
|
+
# Use object sharing. Can save a lot of memory if you have many common
|
175
|
+
# string in your dataset, but performs lookups against the shared objects
|
176
|
+
# pool so it uses more CPU and can be a bit slower. Usually it's a good
|
177
|
+
# idea.
|
178
|
+
#
|
179
|
+
# When object sharing is enabled (shareobjects yes) you can use
|
180
|
+
# shareobjectspoolsize to control the size of the pool used in order to try
|
181
|
+
# object sharing. A bigger pool size will lead to better sharing capabilities.
|
182
|
+
# In general you want this value to be at least the double of the number of
|
183
|
+
# very common strings you have in your dataset.
|
184
|
+
#
|
185
|
+
# WARNING: object sharing is experimental, don't enable this feature
|
186
|
+
# in production before of Redis 1.0-stable. Still please try this feature in
|
187
|
+
# your development environment so that we can test it better.
|
188
|
+
shareobjects no
|
189
|
+
shareobjectspoolsize 1024
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'erb'
|
4
|
+
require 'redis'
|
5
|
+
require 'lib/beetle/redis_ext'
|
6
|
+
require 'daemon_controller'
|
7
|
+
|
8
|
+
module TestDaemons
|
9
|
+
class Redis
|
10
|
+
|
11
|
+
@@instances = {}
|
12
|
+
@@next_available_port = 6381
|
13
|
+
|
14
|
+
attr_reader :name, :port
|
15
|
+
|
16
|
+
def initialize(name)
|
17
|
+
@name = name
|
18
|
+
@port = @@next_available_port
|
19
|
+
|
20
|
+
@@next_available_port += 1
|
21
|
+
@@instances[name] = self
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def find_or_initialize_by_name(name)
|
26
|
+
@@instances[name] ||= new(name)
|
27
|
+
end
|
28
|
+
alias_method :[], :find_or_initialize_by_name
|
29
|
+
|
30
|
+
def stop_all
|
31
|
+
@@instances.values.each{|i| i.stop}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
create_dir
|
37
|
+
create_config
|
38
|
+
daemon_controller.start
|
39
|
+
end
|
40
|
+
|
41
|
+
def restart(delay=1)
|
42
|
+
redis.shutdown rescue Errno::ECONNREFUSED
|
43
|
+
sleep delay
|
44
|
+
`redis-server #{config_filename}`
|
45
|
+
end
|
46
|
+
|
47
|
+
def stop
|
48
|
+
# TODO: Might need to be moved into RedisConfigurationServer
|
49
|
+
# 10.times do
|
50
|
+
# break if (redis.info["bgsave_in_progress"]) == 0 rescue false
|
51
|
+
# sleep 1
|
52
|
+
# end
|
53
|
+
daemon_controller.stop
|
54
|
+
ensure
|
55
|
+
cleanup
|
56
|
+
end
|
57
|
+
|
58
|
+
def cleanup
|
59
|
+
remove_dir
|
60
|
+
remove_config
|
61
|
+
remove_pid_file
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: The retry logic must be moved into RedisConfigurationServer
|
65
|
+
def master
|
66
|
+
tries = 0
|
67
|
+
begin
|
68
|
+
redis.master!
|
69
|
+
rescue Errno::ECONNREFUSED, Errno::EAGAIN
|
70
|
+
puts "master role setting for #{name} failed: #{$!}"
|
71
|
+
sleep 1
|
72
|
+
retry if (tries+=1) > 5
|
73
|
+
raise "could not setup master #{name} #{$!}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def running?
|
78
|
+
cmd = "ps aux | grep 'redis-server #{config_filename}' | grep -v grep"
|
79
|
+
res = `#{cmd}`
|
80
|
+
x = res.chomp.split("\n")
|
81
|
+
x.size == 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def available?
|
85
|
+
redis.available?
|
86
|
+
end
|
87
|
+
|
88
|
+
def master?
|
89
|
+
redis.master?
|
90
|
+
end
|
91
|
+
|
92
|
+
def slave?
|
93
|
+
redis.slave?
|
94
|
+
end
|
95
|
+
|
96
|
+
# TODO: Move to redis_ext
|
97
|
+
def slave_of(master_port)
|
98
|
+
tries = 0
|
99
|
+
begin
|
100
|
+
redis.slave_of!("127.0.0.1", master_port)
|
101
|
+
rescue Errno::ECONNREFUSED, Errno::EAGAIN
|
102
|
+
puts "slave role setting for #{name} failed: #{$!}"
|
103
|
+
sleep 1
|
104
|
+
retry if (tries+=1) > 5
|
105
|
+
raise "could not setup slave #{name}: #{$!}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ip_with_port
|
110
|
+
"127.0.0.1:#{port}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def redis
|
114
|
+
@redis ||= ::Redis.new(:host => "127.0.0.1", :port => port)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def create_dir
|
120
|
+
FileUtils.mkdir(dir) unless File.exists?(dir)
|
121
|
+
end
|
122
|
+
|
123
|
+
def remove_dir
|
124
|
+
FileUtils.rm_r(dir) if File.exists?(dir)
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_config
|
128
|
+
File.open(config_filename, "w") do |file|
|
129
|
+
file.puts config_content
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def remove_config
|
134
|
+
FileUtils.rm(config_filename) if File.exists?(config_filename)
|
135
|
+
end
|
136
|
+
|
137
|
+
def remove_pid_file
|
138
|
+
FileUtils.rm(pid_file) if File.exists?(pid_file)
|
139
|
+
end
|
140
|
+
|
141
|
+
def tmp_path
|
142
|
+
File.expand_path(File.dirname(__FILE__) + "/../../../tmp")
|
143
|
+
end
|
144
|
+
|
145
|
+
def config_filename
|
146
|
+
tmp_path + "/redis-test-server-#{name}.conf"
|
147
|
+
end
|
148
|
+
|
149
|
+
def config_content
|
150
|
+
template = ERB.new(File.read(config_template_filename))
|
151
|
+
template.result(binding)
|
152
|
+
end
|
153
|
+
|
154
|
+
def config_template_filename
|
155
|
+
File.dirname(__FILE__) + "/redis.conf.erb"
|
156
|
+
end
|
157
|
+
|
158
|
+
def pid_file
|
159
|
+
tmp_path + "/redis-test-server-#{name}.pid"
|
160
|
+
end
|
161
|
+
|
162
|
+
def pid
|
163
|
+
File.read(pid_file).chomp.to_i
|
164
|
+
end
|
165
|
+
|
166
|
+
def log_file
|
167
|
+
tmp_path + "/redis-test-server-#{name}.log"
|
168
|
+
end
|
169
|
+
|
170
|
+
def dir
|
171
|
+
tmp_path + "/redis-test-server-#{name}/"
|
172
|
+
end
|
173
|
+
|
174
|
+
def daemon_controller
|
175
|
+
@daemon_controller ||= DaemonController.new(
|
176
|
+
:identifier => "Redis test server",
|
177
|
+
:start_command => "redis-server #{config_filename}",
|
178
|
+
:ping_command => lambda { running? && available? },
|
179
|
+
:pid_file => pid_file,
|
180
|
+
:log_file => log_file,
|
181
|
+
:start_timeout => 5
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|