redis-spawn 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README +8 -0
- data/Rakefile +17 -0
- data/lib/redis/spawn/version.rb +5 -0
- data/lib/redis/spawn.rb +191 -0
- data/test/build_config_helper.rb +14 -0
- data/test/build_config_test.rb +80 -0
- data/test/helper.rb +4 -0
- data/test/spawn_test.rb +8 -0
- data/test/write_config_test.rb +27 -0
- metadata +101 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (C) 2011 by Phil Stewart
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19
|
+
FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
6
|
+
require 'redis/spawn'
|
7
|
+
|
8
|
+
task :test do
|
9
|
+
require 'cutest'
|
10
|
+
|
11
|
+
Cutest.run(Dir["./test/**/*_test.rb"])
|
12
|
+
end
|
13
|
+
|
14
|
+
task :pry do
|
15
|
+
require 'pry'
|
16
|
+
binding.pry
|
17
|
+
end
|
data/lib/redis/spawn.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class SpawnServer
|
5
|
+
@_default_server_opts = {
|
6
|
+
port: "0",
|
7
|
+
bind: "127.0.0.1",
|
8
|
+
unixsocket: "/tmp/redis-spawned.#{Process.pid}.sock",
|
9
|
+
loglevel: "notice",
|
10
|
+
logfile: "/tmp/redis-spawned.#{Process.pid}.log",
|
11
|
+
databases: "16",
|
12
|
+
save: ["900 1", "300 10", "60 10000"],
|
13
|
+
rdbcompression: "yes",
|
14
|
+
dbfilename: "dump.rdb",
|
15
|
+
dir: "/tmp/redis-spawned.#{Process.pid}.data",
|
16
|
+
appendonly: "no",
|
17
|
+
appendfsync: "everysec",
|
18
|
+
vm_enabled: "no",
|
19
|
+
hash_max_zipmap_entries: "512",
|
20
|
+
hash_max_zipmap_value: "64",
|
21
|
+
list_max_ziplist_entries: "512",
|
22
|
+
list_max_ziplist_value: "64",
|
23
|
+
set_max_intset_entries: "512",
|
24
|
+
activerehashing: "yes"
|
25
|
+
}
|
26
|
+
|
27
|
+
@_running_servers = {}
|
28
|
+
|
29
|
+
def self.default_server_opts
|
30
|
+
@_default_server_opts
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.default_server_opts=(new_defaults_hash)
|
34
|
+
@_default_server_opts = new_defaults_hash
|
35
|
+
end
|
36
|
+
|
37
|
+
# Build a configuration file line
|
38
|
+
#
|
39
|
+
# @param [Symbol, String] key: The configuration parameter. Underscores in
|
40
|
+
# the key are transposed to dashes
|
41
|
+
# @param [String, Object] value: The value to set for this configuration
|
42
|
+
# parameter
|
43
|
+
#
|
44
|
+
# @return A line of Redis server configuration data
|
45
|
+
def self.build_config_line(key, value)
|
46
|
+
key.to_s.gsub(/_/, "-") + " " + value.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
# Spawn a Redis server configured with the supplied options. By default,
|
50
|
+
# the spawned server will be a child of the current process and won't
|
51
|
+
# daemonize (@todo allow daemonization)
|
52
|
+
#
|
53
|
+
# @param supplied_opts: options for this server including any configuration overrides
|
54
|
+
#
|
55
|
+
# @return [SpawnServer] instance corresponding to the spawned server
|
56
|
+
def self.spawn(supplied_opts = {})
|
57
|
+
self.new(supplied_opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :opts, :supplied_opts, :server_opts, :pid
|
61
|
+
|
62
|
+
def initialize(supplied_opts = {})
|
63
|
+
default_opts = {
|
64
|
+
generated_config_file: "/tmp/redis-spawned.#{Process.pid}.config",
|
65
|
+
cleanup_files: [:socket, :log, :config],
|
66
|
+
server_opts: {},
|
67
|
+
start: true
|
68
|
+
}
|
69
|
+
@supplied_opts = supplied_opts
|
70
|
+
@opts = default_opts.merge(supplied_opts)
|
71
|
+
self.server_opts = opts[:server_opts]
|
72
|
+
|
73
|
+
@pid = opts[:start] ? self.start : 0
|
74
|
+
|
75
|
+
# Return the instance
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Prepare a redis configuration file then start the server
|
80
|
+
#
|
81
|
+
# @return pid of the server
|
82
|
+
def start
|
83
|
+
# If config_file is passed in opts use it as the name of the config file.
|
84
|
+
# Otherwise, generate our own
|
85
|
+
@config_file = if opts.has_key?(:config_file)
|
86
|
+
# Don't attempt to cleanup files when supplied with pre-existing
|
87
|
+
# config file unless specifically asked
|
88
|
+
opts[:cleanup_files] = [] unless supplied_opts.has_key?(:cleanup_files)
|
89
|
+
opts[:config_file]
|
90
|
+
else
|
91
|
+
self.write_config
|
92
|
+
end
|
93
|
+
|
94
|
+
# Ensure the data directory exists
|
95
|
+
Dir.exists?(self.server_opts[:dir]) || Dir.mkdir(self.server_opts[:dir])
|
96
|
+
|
97
|
+
# Spawn the redis server for this instance
|
98
|
+
self.spawn
|
99
|
+
end
|
100
|
+
|
101
|
+
# Spawn a redis server. Only call this function once a config file exists
|
102
|
+
# and is specified
|
103
|
+
#
|
104
|
+
# @return the pid of the spawned server
|
105
|
+
def spawn
|
106
|
+
# Abort if there's no config file
|
107
|
+
unless @config_file && File.exist?(@config_file)
|
108
|
+
raise "Config file #{@config_file.inspect} not found"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Make sure we clean up after our children and avoid a zombie invasion
|
112
|
+
trap("CLD") do
|
113
|
+
pid = Process.wait
|
114
|
+
end
|
115
|
+
|
116
|
+
# Start the server
|
117
|
+
pid = fork { exec("redis-server #{@config_file}") }
|
118
|
+
#logger.info("Spawned redis server with PID #{pid}")
|
119
|
+
|
120
|
+
at_exit do
|
121
|
+
begin
|
122
|
+
Process.kill("TERM", pid) # Maybe make this configurable to allow the server to continue after exit
|
123
|
+
rescue Errno::ESRCH
|
124
|
+
# Already dead - do nothing
|
125
|
+
end
|
126
|
+
self.cleanup_files
|
127
|
+
end
|
128
|
+
|
129
|
+
pid
|
130
|
+
end
|
131
|
+
|
132
|
+
# Attribute write for server opts: merges supplied opts with defaults
|
133
|
+
# to create fully populated server opts
|
134
|
+
#
|
135
|
+
# @param [Hash] opts: partially populated server options hash
|
136
|
+
def server_opts=(opts)
|
137
|
+
@server_opts = self.class.default_server_opts.merge(opts)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Write the Redis configuration file to disk.
|
141
|
+
#
|
142
|
+
# @return the name of the written file
|
143
|
+
def write_config
|
144
|
+
File.open(self.opts[:generated_config_file], "w") do |file|
|
145
|
+
## @todo Migrate class based build_config to instance based build_config
|
146
|
+
file.write(self.build_config)
|
147
|
+
end
|
148
|
+
self.opts[:generated_config_file]
|
149
|
+
end
|
150
|
+
|
151
|
+
# Build configuration file data
|
152
|
+
#
|
153
|
+
# @return Redis server compatible configuration file data
|
154
|
+
def build_config
|
155
|
+
config_data = ""
|
156
|
+
self.server_opts.each do |key, value|
|
157
|
+
if value.kind_of?(Array)
|
158
|
+
value.each { |subvalue| config_data << self.class.build_config_line(key, subvalue) << "\n" }
|
159
|
+
else
|
160
|
+
config_data << self.class.build_config_line(key, value) << "\n"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
config_data
|
164
|
+
end
|
165
|
+
|
166
|
+
# Clean up server files associated with this instance. Expects
|
167
|
+
# #opts[:cleanup_files] to already be set up
|
168
|
+
def cleanup_files
|
169
|
+
files_from_symbols(opts[:cleanup_files]) do |file|
|
170
|
+
File.exist?(file) && File.delete(file)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Iterates over the supplied symbols and yields corresponding filenames
|
175
|
+
#
|
176
|
+
# @param [Array] file_syms: array of symbols to iterate over
|
177
|
+
def files_from_symbols(file_syms)
|
178
|
+
file_syms.each do |file_sym|
|
179
|
+
yield case file_sym
|
180
|
+
when :socket
|
181
|
+
server_opts[:unixsocket]
|
182
|
+
when :log
|
183
|
+
server_opts[:logfile]
|
184
|
+
when :config
|
185
|
+
@config_file || opts[:generated_config_file]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Monkeypatch Process to allow for consistent value of Process.pid in tests
|
2
|
+
module Process
|
3
|
+
class << self
|
4
|
+
alias :redis_spawn_original_pid :pid
|
5
|
+
|
6
|
+
def pid
|
7
|
+
if caller[0] =~ /redis\/spawn/
|
8
|
+
return 0
|
9
|
+
else
|
10
|
+
return redis_spawn_original_pid
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.expand_path("./build_config_helper", File.dirname(__FILE__))
|
2
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
3
|
+
|
4
|
+
test "build_config_line" do
|
5
|
+
assert "key value" == Redis::SpawnServer.build_config_line("key", "value")
|
6
|
+
assert "symbolkey value" == Redis::SpawnServer.build_config_line(:symbolkey, "value")
|
7
|
+
assert "symbol-with-underscores value" == Redis::SpawnServer.build_config_line(:symbol_with_underscores, "value")
|
8
|
+
assert "key 0" == Redis::SpawnServer.build_config_line("key", 0)
|
9
|
+
end
|
10
|
+
|
11
|
+
setup do
|
12
|
+
@test_config_defaults = <<TEST_CONF_END
|
13
|
+
port 0
|
14
|
+
bind 127.0.0.1
|
15
|
+
unixsocket /tmp/redis-spawned.0.sock
|
16
|
+
loglevel notice
|
17
|
+
logfile /tmp/redis-spawned.0.log
|
18
|
+
databases 16
|
19
|
+
save 900 1
|
20
|
+
save 300 10
|
21
|
+
save 60 10000
|
22
|
+
rdbcompression yes
|
23
|
+
dbfilename dump.rdb
|
24
|
+
dir /tmp/redis-spawned.0.data
|
25
|
+
appendonly no
|
26
|
+
appendfsync everysec
|
27
|
+
vm-enabled no
|
28
|
+
hash-max-zipmap-entries 512
|
29
|
+
hash-max-zipmap-value 64
|
30
|
+
list-max-ziplist-entries 512
|
31
|
+
list-max-ziplist-value 64
|
32
|
+
set-max-intset-entries 512
|
33
|
+
activerehashing yes
|
34
|
+
TEST_CONF_END
|
35
|
+
end
|
36
|
+
|
37
|
+
test "build_config with defaults" do
|
38
|
+
assert @test_config_defaults == Redis::SpawnServer.new(start: false).build_config
|
39
|
+
end
|
40
|
+
|
41
|
+
setup do
|
42
|
+
@test_config_defaults = <<TEST_CONF_END
|
43
|
+
port 0
|
44
|
+
bind 127.0.0.1
|
45
|
+
unixsocket /tmp/redis-spawned.override.sock
|
46
|
+
loglevel notice
|
47
|
+
logfile /tmp/redis-spawned.override.log
|
48
|
+
databases 8
|
49
|
+
save 900 1
|
50
|
+
save 300 10
|
51
|
+
save 100 1000
|
52
|
+
save 60 10000
|
53
|
+
rdbcompression no
|
54
|
+
dbfilename dump.rdb
|
55
|
+
dir /tmp/redis-spawned.override.data
|
56
|
+
appendonly no
|
57
|
+
appendfsync everysec
|
58
|
+
vm-enabled no
|
59
|
+
hash-max-zipmap-entries 512
|
60
|
+
hash-max-zipmap-value 64
|
61
|
+
list-max-ziplist-entries 512
|
62
|
+
list-max-ziplist-value 64
|
63
|
+
set-max-intset-entries 512
|
64
|
+
activerehashing yes
|
65
|
+
TEST_CONF_END
|
66
|
+
end
|
67
|
+
|
68
|
+
test "build_config with overrides" do
|
69
|
+
overrides = {
|
70
|
+
unixsocket: "/tmp/redis-spawned.override.sock",
|
71
|
+
logfile: "/tmp/redis-spawned.override.log",
|
72
|
+
databases: 8,
|
73
|
+
save: ["900 1", "300 10", "100 1000", "60 10000"],
|
74
|
+
rdbcompression: "no",
|
75
|
+
dir: "/tmp/redis-spawned.override.data"
|
76
|
+
}
|
77
|
+
assert @test_config_defaults == Redis::SpawnServer.new(start: false, server_opts: overrides).build_config
|
78
|
+
end
|
79
|
+
|
80
|
+
|
data/test/helper.rb
ADDED
data/test/spawn_test.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "spawn with defaults" do
|
4
|
+
spawn_instance = Redis::SpawnServer.new
|
5
|
+
assert_equal `ps -o cmd= -p #{spawn_instance.pid}`, "redis-server /tmp/redis-spawned.#{Process.pid}.config\n"
|
6
|
+
Process.kill("TERM", spawn_instance.pid)
|
7
|
+
end
|
8
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
setup do
|
5
|
+
testdir = "/tmp/redis-spawn-write-config-test.dir"
|
6
|
+
filename = "/tmp/redis-spawn-write-config-test.config"
|
7
|
+
spawn_instance = Redis::SpawnServer.new(start: false,
|
8
|
+
generated_config_file: filename,
|
9
|
+
server_opts: {dir: testdir})
|
10
|
+
Dir.exists?(testdir) && Dir.rmdir(testdir)
|
11
|
+
{
|
12
|
+
filename: filename,
|
13
|
+
spawn_instance: spawn_instance,
|
14
|
+
config_sha: Digest::SHA1.hexdigest(spawn_instance.build_config)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
test "write_config" do |params|
|
19
|
+
begin
|
20
|
+
filename = params[:spawn_instance].write_config
|
21
|
+
assert_equal filename, params[:filename]
|
22
|
+
assert_equal Digest::SHA1.hexdigest(File.read(filename)), params[:config_sha]
|
23
|
+
# assert Dir.exist?(params[:spawn_instance].server_opts[:dir])
|
24
|
+
ensure
|
25
|
+
File.delete(params[:filename])
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-spawn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Phil Stewart
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-10-26 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: redis-rb
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 0
|
31
|
+
version: "2.0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: cutest
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
- 1
|
45
|
+
version: "0.1"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: Insert something more verbose than the summary here.
|
49
|
+
email:
|
50
|
+
- phil.stewart@lichp.co.uk
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- lib/redis/spawn/version.rb
|
59
|
+
- lib/redis/spawn.rb
|
60
|
+
- README
|
61
|
+
- LICENSE
|
62
|
+
- Rakefile
|
63
|
+
- test/write_config_test.rb
|
64
|
+
- test/helper.rb
|
65
|
+
- test/spawn_test.rb
|
66
|
+
- test/build_config_helper.rb
|
67
|
+
- test/build_config_test.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/lichp/redis-spawn
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.3.7
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: An extension to redis-rb to facilitate spawning a redis-server specifically for your app.
|
100
|
+
test_files: []
|
101
|
+
|