redis-spawn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ # redis-spawn
2
+
3
+ An extension to redis-rb to facilitate spawning a redis-server specifically
4
+ for your app.
5
+
6
+ Phil Stewart, 201110
7
+
8
+ License: MIT
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
@@ -0,0 +1,5 @@
1
+ class Redis
2
+ class SpawnServer
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require 'redis/spawn'
4
+
@@ -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
+