rprov 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,19 @@
1
+ Copyright (c) 2010 Cyril David, Michel Martens, Damian Janowski
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 deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,40 @@
1
+ NAME
2
+ rprov -- Redis Provisioning
3
+
4
+ SYNOPSIS
5
+ rprov setup /some/path -m 1gb -h hostname
6
+ rprov setup /some/path -m 1gb --paranoid
7
+
8
+ rprov start /some/path
9
+ rprov stop /some/path
10
+ rprov info /some/path
11
+
12
+ DESCRIPTION
13
+ Rprov is a simple ruby command line utility which helps you
14
+ provision and manage redis instances easily.
15
+
16
+ COMMANDS
17
+ help Show this usage guide
18
+
19
+ setup PATH Provision a new Redis instance on the specified path.
20
+
21
+ start PATH Start the previously provisioned instance located
22
+ on the specified path.
23
+
24
+ stop PATH Stop a running instance identified by a specific path.
25
+
26
+ info PATH Get the necessary information for an instance, most notably
27
+ a `REDIS_URL` which encapsulates all of the connection
28
+ information necessary to access the instance.
29
+
30
+ OPTIONS
31
+ -m Customize the amount of max memory usable for the instance.
32
+ (Defaults to no limit.)
33
+
34
+ -h Specify the bind address for this redis instance.
35
+ (Defaults to 127.0.0.1)
36
+
37
+ --paranoid Removes some of the commands meant to be used by sysadmins
38
+ and renames some into a cryptic, non-guessable one.
39
+
40
+
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ help = <<-EOT
4
+ RPROV(1)
5
+
6
+ NAME
7
+ rprov -- Redis Provisioning
8
+
9
+ SYNOPSIS
10
+ rprov setup /some/path -m 1gb -h hostname
11
+ rprov setup /some/path -m 1gb --paranoid
12
+
13
+ rprov start /some/path
14
+ rprov stop /some/path
15
+ rprov info /some/path
16
+
17
+ DESCRIPTION
18
+ Rprov is a simple ruby command line utility which helps you
19
+ provision and manage redis instances easily.
20
+
21
+ COMMANDS
22
+ help Show this usage guide
23
+
24
+ setup PATH Provision a new Redis instance on the specified path.
25
+
26
+ start PATH Start the previously provisioned instance located
27
+ on the specified path.
28
+
29
+ stop PATH Stop a running instance identified by a specific path.
30
+
31
+ info PATH Get the necessary information for an instance, most notably
32
+ a `REDIS_URL` which encapsulates all of the connection
33
+ information necessary to access the instance.
34
+
35
+ OPTIONS
36
+ -m Customize the amount of max memory usable for the instance.
37
+ (Defaults to no limit.)
38
+
39
+ -h Specify the bind address for this redis instance.
40
+ (Defaults to 127.0.0.1)
41
+
42
+ --paranoid Removes some of the commands meant to be used by sysadmins
43
+ and renames some into a cryptic, non-guessable one.
44
+
45
+
46
+ EOT
47
+
48
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
49
+
50
+ require "clap"
51
+ require "rprov"
52
+
53
+ if ARGV.empty?
54
+ puts help
55
+ exit
56
+ end
57
+
58
+ rprov = Rprov.new
59
+ decor = Rprov::Decorator.new(rprov)
60
+
61
+ begin
62
+ command_and_path = Clap.run ARGV,
63
+ "help" => lambda { puts help; exit },
64
+ "-m" => rprov.method(:memory=),
65
+ "-h" => rprov.method(:host=),
66
+ "--paranoid" => lambda { rprov.paranoid = true }
67
+
68
+ Clap.run command_and_path,
69
+ "setup" => decor.method(:setup),
70
+ "start" => decor.method(:start),
71
+ "stop" => decor.method(:stop),
72
+ "info" => decor.method(:info),
73
+
74
+ rescue ArgumentError
75
+ puts help
76
+ end
@@ -0,0 +1,65 @@
1
+ require "redis"
2
+ require "nest"
3
+ require "digest/sha1"
4
+ require "erb"
5
+ require "fileutils"
6
+
7
+ class Rprov
8
+ Conflict = Class.new(StandardError)
9
+ Missing = Class.new(StandardError)
10
+
11
+ VERSION = "0.0.1"
12
+
13
+ autoload :Config, "rprov/config"
14
+ autoload :Decorator, "rprov/decorator"
15
+
16
+ attr_accessor :memory, :host, :paranoid
17
+
18
+ def start(path)
19
+ exec("redis-server #{redis_conf!(path)}")
20
+ end
21
+
22
+ def stop(path)
23
+ conf = Config.new(key(path))
24
+ redis = Redis.connect(:url => conf.url)
25
+ redis.client.process(conf.shutdown_cmd)
26
+ end
27
+
28
+ def setup(path)
29
+ raise Conflict if redis_conf(path)
30
+
31
+ FileUtils.mkdir_p(path) if not File.exist?(path)
32
+
33
+ conf = Config.generate
34
+ conf.path = path
35
+ conf.memory = memory if memory
36
+ conf.host = host if host
37
+ conf.paranoid = paranoid if paranoid
38
+
39
+ where = File.expand_path(File.join(path, "redis.%s.conf" % conf.key))
40
+
41
+ File.open(where, "w") do |file|
42
+ file.write conf.redis_conf
43
+ end
44
+ end
45
+
46
+ def info(path)
47
+ conf = Config.new(key(path))
48
+
49
+ puts "\nREDIS_URL:\n #{conf.url}"
50
+ puts "\nRun `rprov start #{path}` to start this instance"
51
+ end
52
+
53
+ private
54
+ def key(path)
55
+ redis_conf!(path)[/redis\.(.*?)\.conf/, 1]
56
+ end
57
+
58
+ def redis_conf(path)
59
+ Dir[File.expand_path(File.join(path, "redis.*.conf"))].first
60
+ end
61
+
62
+ def redis_conf!(path)
63
+ redis_conf(path) or raise(Missing)
64
+ end
65
+ end
@@ -0,0 +1,109 @@
1
+ class Rprov
2
+ class Config
3
+ STARTING_PORT = 10_000
4
+
5
+ DEFAULTS = {
6
+ :appendfsync => "everysec",
7
+ :host => "127.0.0.1",
8
+ :vm_enabled => "no",
9
+ :vm_max_memory => 0,
10
+ :paranoid => false
11
+ }
12
+
13
+ attr :key
14
+ attr :password
15
+ attr :port
16
+ attr :paranoid
17
+ attr :path
18
+
19
+ attr_accessor :host
20
+ attr_accessor :appendfsync
21
+ attr_accessor :vm_enabled
22
+ attr_accessor :vm_max_memory
23
+
24
+ def initialize(key)
25
+ @key = key
26
+
27
+ @password, @port = redis.hmget(:password, :port)
28
+
29
+ DEFAULTS.each do |key, value|
30
+ send("%s=" % key, redis.hget(key) || value)
31
+ end
32
+ rescue Errno::ECONNREFUSED
33
+ raise Errno::ECONNREFUSED, "You need to setup a master redis server."
34
+ end
35
+
36
+ def memory=(memory)
37
+ self.vm_max_memory = memory
38
+ self.vm_enabled = "yes"
39
+ end
40
+
41
+ def path=(path)
42
+ @path = path.to_s.gsub(/\/*$/, "")
43
+ end
44
+
45
+ def url
46
+ "redis://:#{password}@#{host}:#{port}"
47
+ end
48
+
49
+ def paranoid=(val)
50
+ @paranoid = val
51
+
52
+ if val
53
+ redis.hmset(:config_cmd, self.class.random_string,
54
+ :shutdown_cmd, self.class.random_string)
55
+ end
56
+ end
57
+
58
+ def config_cmd() redis.hget(:config_cmd) || :config end
59
+ def shutdown_cmd() redis.hget(:shutdown_cmd) || :shutdown end
60
+
61
+ def redis_conf
62
+ redis_conf_erb.result(binding)
63
+ end
64
+
65
+ def self.generate(key = gen_key)
66
+ redis[key].hmset(:port, gen_port, :password, random_string)
67
+
68
+ return new(key)
69
+ end
70
+
71
+ def self.random_string
72
+ Digest::SHA1.hexdigest(uuid + Time.now.to_f.to_s)
73
+ end
74
+
75
+ def self.redis
76
+ @redis ||= Nest.new(:rprov)[:config]
77
+ end
78
+
79
+ private
80
+ def redis_conf_erb
81
+ ERB.new(File.read(template_path), nil, "-")
82
+ end
83
+
84
+ def template_path
85
+ File.expand_path("../../templates/redis.conf.erb",
86
+ File.dirname(__FILE__))
87
+ end
88
+
89
+ def redis
90
+ @redis ||= self.class.redis[key]
91
+ end
92
+
93
+ def self.gen_port
94
+ unless redis[:port].setnx(STARTING_PORT)
95
+ redis[:port].incr
96
+ end
97
+
98
+ redis[:port].get
99
+ end
100
+
101
+ def self.gen_key
102
+ Digest::SHA1.hexdigest(uuid)
103
+ end
104
+
105
+ def self.uuid
106
+ `uuidgen`.strip
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,71 @@
1
+ class Rprov
2
+ class Decorator
3
+ attr :component
4
+
5
+ def initialize(component)
6
+ @component = component
7
+ end
8
+
9
+ def start(path)
10
+ init("Starting Redis Instance: `#{path}`")
11
+
12
+ begin
13
+ component.start(path)
14
+ rescue Rprov::Missing
15
+ fail_missing(path)
16
+ end
17
+ end
18
+
19
+ def stop(path)
20
+ init("Stopping Redis Instance: `#{path}`")
21
+
22
+ begin
23
+ component.stop(path)
24
+ rescue Errno::ECONNREFUSED
25
+ fail("The instance appears to be down.")
26
+ rescue Rprov::Missing
27
+ fail_missing(path)
28
+ else
29
+ say("Done!")
30
+ end
31
+ end
32
+
33
+ def setup(path)
34
+ init("Setting up Redis Instance: #{path}")
35
+
36
+ begin
37
+ component.setup(path)
38
+ rescue Rprov::Conflict
39
+ fail("The path #{path} already exists.")
40
+ else
41
+ say("Done!")
42
+ end
43
+ end
44
+
45
+ def info(path)
46
+ begin
47
+ component.info(path)
48
+ rescue Rprov::Missing
49
+ fail_missing(path)
50
+ end
51
+ end
52
+
53
+ private
54
+ def init(str)
55
+ puts "-----> #{str}"
56
+ end
57
+
58
+ def fail(str)
59
+ puts "!! #{str}"
60
+ end
61
+
62
+ def fail_missing(path)
63
+ fail("Tried to run rprov on non-existent path `#{path}`.")
64
+ fail("Maybe try `rprov setup #{path}` first?")
65
+ end
66
+
67
+ def say(str)
68
+ puts " #{str}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,408 @@
1
+ # Redis configuration file example
2
+
3
+ # Note on units: when memory size is needed, it is possible to specifiy
4
+ # it in the usual form of 1k 5GB 4M and so forth:
5
+ #
6
+ # 1k => 1000 bytes
7
+ # 1kb => 1024 bytes
8
+ # 1m => 1000000 bytes
9
+ # 1mb => 1024*1024 bytes
10
+ # 1g => 1000000000 bytes
11
+ # 1gb => 1024*1024*1024 bytes
12
+ #
13
+ # units are case insensitive so 1GB 1Gb 1gB are all the same.
14
+
15
+ # By default Redis does not run as a daemon. Use 'yes' if you need it.
16
+ # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
17
+ daemonize yes
18
+
19
+ # When running daemonized, Redis writes a pid file in /var/run/redis.pid by
20
+ # default. You can specify a custom pid file location here.
21
+ pidfile <%= path -%>/redis.pid
22
+
23
+ # Accept connections on the specified port, default is 6379.
24
+ port <%= port -%>
25
+
26
+ # If you want you can bind a single interface, if the bind option is not
27
+ # specified all the interfaces will listen for incoming connections.
28
+ #
29
+ bind <%= host -%>
30
+
31
+ # Specify the path for the unix socket that will be used to listen for
32
+ # incoming connections. There is no default, so Redis will not listen
33
+ # on a unix socket when not specified.
34
+ #
35
+ # unixsocket /tmp/redis.sock
36
+
37
+ # Close the connection after a client is idle for N seconds (0 to disable)
38
+ timeout 300
39
+
40
+ # Set server verbosity to 'debug'
41
+ # it can be one of:
42
+ # debug (a lot of information, useful for development/testing)
43
+ # verbose (many rarely useful info, but not a mess like the debug level)
44
+ # notice (moderately verbose, what you want in production probably)
45
+ # warning (only very important / critical messages are logged)
46
+ loglevel verbose
47
+
48
+ # Specify the log file name. Also 'stdout' can be used to force
49
+ # Redis to log on the standard output. Note that if you use standard
50
+ # output for logging but daemonize, logs will be sent to /dev/null
51
+ logfile <%= path -%>/redis.log
52
+
53
+ # Set the number of databases. The default database is DB 0, you can select
54
+ # a different one on a per-connection basis using SELECT <dbid> where
55
+ # dbid is a number between 0 and 'databases'-1
56
+ databases 16
57
+
58
+ ################################ SNAPSHOTTING #################################
59
+ #
60
+ # Save the DB on disk:
61
+ #
62
+ # save <seconds> <changes>
63
+ #
64
+ # Will save the DB if both the given number of seconds and the given
65
+ # number of write operations against the DB occurred.
66
+ #
67
+ # In the example below the behaviour will be to save:
68
+ # after 900 sec (15 min) if at least 1 key changed
69
+ # after 300 sec (5 min) if at least 10 keys changed
70
+ # after 60 sec if at least 10000 keys changed
71
+ #
72
+ # Note: you can disable saving at all commenting all the "save" lines.
73
+
74
+ # save 900 1
75
+ # save 300 10
76
+ # save 60 10000
77
+
78
+ # Compress string objects using LZF when dump .rdb databases?
79
+ # For default that's set to 'yes' as it's almost always a win.
80
+ # If you want to save some CPU in the saving child set it to 'no' but
81
+ # the dataset will likely be bigger if you have compressible values or keys.
82
+ rdbcompression yes
83
+
84
+ # The filename where to dump the DB
85
+ dbfilename dump.rdb
86
+
87
+ # The working directory.
88
+ #
89
+ # The DB will be written inside this directory, with the filename specified
90
+ # above using the 'dbfilename' configuration directive.
91
+ #
92
+ # Also the Append Only File will be created inside this directory.
93
+ #
94
+ # Note that you must specify a directory here, not a file name.
95
+ dir <%= path -%>
96
+
97
+ ################################# REPLICATION #################################
98
+
99
+ # Master-Slave replication. Use slaveof to make a Redis instance a copy of
100
+ # another Redis server. Note that the configuration is local to the slave
101
+ # so for example it is possible to configure the slave to save the DB with a
102
+ # different interval, or to listen to another port, and so on.
103
+ #
104
+ # slaveof <masterip> <masterport>
105
+
106
+ # If the master is password protected (using the "requirepass" configuration
107
+ # directive below) it is possible to tell the slave to authenticate before
108
+ # starting the replication synchronization process, otherwise the master will
109
+ # refuse the slave request.
110
+ #
111
+ # masterauth <master-password>
112
+
113
+ # When a slave lost the connection with the master, or when the replication
114
+ # is still in progress, the slave can act in two different ways:
115
+ #
116
+ # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
117
+ # still reply to client requests, possibly with out of data data, or the
118
+ # data set may just be empty if this is the first synchronization.
119
+ #
120
+ # 2) if slave-serve-stale data is set to 'no' the slave will reply with
121
+ # an error "SYNC with master in progress" to all the kind of commands
122
+ # but to INFO and SLAVEOF.
123
+ #
124
+ slave-serve-stale-data yes
125
+
126
+ ################################## SECURITY ###################################
127
+
128
+ # Require clients to issue AUTH <PASSWORD> before processing any other
129
+ # commands. This might be useful in environments in which you do not trust
130
+ # others with access to the host running redis-server.
131
+ #
132
+ # This should stay commented out for backward compatibility and because most
133
+ # people do not need auth (e.g. they run their own servers).
134
+ #
135
+ # Warning: since Redis is pretty fast an outside user can try up to
136
+ # 150k passwords per second against a good box. This means that you should
137
+ # use a very strong password otherwise it will be very easy to break.
138
+ #
139
+ requirepass <%= password -%>
140
+
141
+ # Command renaming.
142
+ #
143
+ # It is possilbe to change the name of dangerous commands in a shared
144
+ # environment. For instance the CONFIG command may be renamed into something
145
+ # of hard to guess so that it will be still available for internal-use
146
+ # tools but not available for general clients.
147
+ #
148
+ # Example:
149
+ #
150
+ # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
151
+ #
152
+ # It is also possilbe to completely kill a command renaming it into
153
+ # an empty string:
154
+
155
+ <%- if paranoid -%>
156
+ rename-command CONFIG <%= config_cmd %>
157
+ rename-command SHUTDOWN <%= shutdown_cmd %>
158
+ rename-command SLAVEOF ""
159
+ rename-command BGREWRITEAOF ""
160
+ rename-command SAVE ""
161
+ rename-command BGSAVE ""
162
+ <%- end -%>
163
+
164
+ ################################### LIMITS ####################################
165
+
166
+ # Set the max number of connected clients at the same time. By default there
167
+ # is no limit, and it's up to the number of file descriptors the Redis process
168
+ # is able to open. The special value '0' means no limits.
169
+ # Once the limit is reached Redis will close all the new connections sending
170
+ # an error 'max number of clients reached'.
171
+ #
172
+ # maxclients 128
173
+
174
+ # Don't use more memory than the specified amount of bytes.
175
+ # When the memory limit is reached Redis will try to remove keys with an
176
+ # EXPIRE set. It will try to start freeing keys that are going to expire
177
+ # in little time and preserve keys with a longer time to live.
178
+ # Redis will also try to remove objects from free lists if possible.
179
+ #
180
+ # If all this fails, Redis will start to reply with errors to commands
181
+ # that will use more memory, like SET, LPUSH, and so on, and will continue
182
+ # to reply to most read-only commands like GET.
183
+ #
184
+ # WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
185
+ # 'state' server or cache, not as a real DB. When Redis is used as a real
186
+ # database the memory usage will grow over the weeks, it will be obvious if
187
+ # it is going to use too much memory in the long run, and you'll have the time
188
+ # to upgrade. With maxmemory after the limit is reached you'll start to get
189
+ # errors for write operations, and this may even lead to DB inconsistency.
190
+ #
191
+ # maxmemory <bytes>
192
+
193
+ # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
194
+ # is reached? You can select among five behavior:
195
+ #
196
+ # volatile-lru -> remove the key with an expire set using an LRU algorithm
197
+ # allkeys-lru -> remove any key accordingly to the LRU algorithm
198
+ # volatile-random -> remove a random key with an expire set
199
+ # allkeys->random -> remove a random key, any key
200
+ # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
201
+ # noeviction -> don't expire at all, just return an error on write operations
202
+ #
203
+ # Note: with all the kind of policies, Redis will return an error on write
204
+ # operations, when there are not suitable keys for eviction.
205
+ #
206
+ # At the date of writing this commands are: set setnx setex append
207
+ # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
208
+ # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
209
+ # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
210
+ # getset mset msetnx exec sort
211
+ #
212
+ # The default is:
213
+ #
214
+ # maxmemory-policy volatile-lru
215
+
216
+ # LRU and minimal TTL algorithms are not precise algorithms but approximated
217
+ # algorithms (in order to save memory), so you can select as well the sample
218
+ # size to check. For instance for default Redis will check three keys and
219
+ # pick the one that was used less recently, you can change the sample size
220
+ # using the following configuration directive.
221
+ #
222
+ # maxmemory-samples 3
223
+
224
+ ############################## APPEND ONLY MODE ###############################
225
+
226
+ # By default Redis asynchronously dumps the dataset on disk. If you can live
227
+ # with the idea that the latest records will be lost if something like a crash
228
+ # happens this is the preferred way to run Redis. If instead you care a lot
229
+ # about your data and don't want to that a single record can get lost you should
230
+ # enable the append only mode: when this mode is enabled Redis will append
231
+ # every write operation received in the file appendonly.aof. This file will
232
+ # be read on startup in order to rebuild the full dataset in memory.
233
+ #
234
+ # Note that you can have both the async dumps and the append only file if you
235
+ # like (you have to comment the "save" statements above to disable the dumps).
236
+ # Still if append only mode is enabled Redis will load the data from the
237
+ # log file at startup ignoring the dump.rdb file.
238
+ #
239
+ # IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
240
+ # log file in background when it gets too big.
241
+
242
+ appendonly yes
243
+
244
+ # The name of the append only file (default: "appendonly.aof")
245
+ appendfilename appendonly.aof
246
+
247
+ # The fsync() call tells the Operating System to actually write data on disk
248
+ # instead to wait for more data in the output buffer. Some OS will really flush
249
+ # data on disk, some other OS will just try to do it ASAP.
250
+ #
251
+ # Redis supports three different modes:
252
+ #
253
+ # no: don't fsync, just let the OS flush the data when it wants. Faster.
254
+ # always: fsync after every write to the append only log . Slow, Safest.
255
+ # everysec: fsync only if one second passed since the last fsync. Compromise.
256
+ #
257
+ # The default is "everysec" that's usually the right compromise between
258
+ # speed and data safety. It's up to you to understand if you can relax this to
259
+ # "no" that will will let the operating system flush the output buffer when
260
+ # it wants, for better performances (but if you can live with the idea of
261
+ # some data loss consider the default persistence mode that's snapshotting),
262
+ # or on the contrary, use "always" that's very slow but a bit safer than
263
+ # everysec.
264
+ #
265
+ # If unsure, use "everysec".
266
+
267
+ # appendfsync always
268
+ # appendfsync everysec
269
+ # appendfsync no
270
+ appendfsync <%= appendfsync -%>
271
+
272
+ # When the AOF fsync policy is set to always or everysec, and a background
273
+ # saving process (a background save or AOF log background rewriting) is
274
+ # performing a lot of I/O against the disk, in some Linux configurations
275
+ # Redis may block too long on the fsync() call. Note that there is no fix for
276
+ # this currently, as even performing fsync in a different thread will block
277
+ # our synchronous write(2) call.
278
+ #
279
+ # In order to mitigate this problem it's possible to use the following option
280
+ # that will prevent fsync() from being called in the main process while a
281
+ # BGSAVE or BGREWRITEAOF is in progress.
282
+ #
283
+ # This means that while another child is saving the durability of Redis is
284
+ # the same as "appendfsync none", that in pratical terms means that it is
285
+ # possible to lost up to 30 seconds of log in the worst scenario (with the
286
+ # default Linux settings).
287
+ #
288
+ # If you have latency problems turn this to "yes". Otherwise leave it as
289
+ # "no" that is the safest pick from the point of view of durability.
290
+ no-appendfsync-on-rewrite no
291
+
292
+ ################################ VIRTUAL MEMORY ###############################
293
+
294
+ # Virtual Memory allows Redis to work with datasets bigger than the actual
295
+ # amount of RAM needed to hold the whole dataset in memory.
296
+ # In order to do so very used keys are taken in memory while the other keys
297
+ # are swapped into a swap file, similarly to what operating systems do
298
+ # with memory pages.
299
+ #
300
+ # To enable VM just set 'vm-enabled' to yes, and set the following three
301
+ # VM parameters accordingly to your needs.
302
+
303
+ # vm-enabled no
304
+ # vm-enabled yes
305
+ vm-enabled <%= vm_enabled %>
306
+
307
+ # This is the path of the Redis swap file. As you can guess, swap files
308
+ # can't be shared by different Redis instances, so make sure to use a swap
309
+ # file for every redis process you are running. Redis will complain if the
310
+ # swap file is already in use.
311
+ #
312
+ # The best kind of storage for the Redis swap file (that's accessed at random)
313
+ # is a Solid State Disk (SSD).
314
+ #
315
+ # *** WARNING *** if you are using a shared hosting the default of putting
316
+ # the swap file under /tmp is not secure. Create a dir with access granted
317
+ # only to Redis user and configure Redis to create the swap file there.
318
+ vm-swap-file <%= path -%>/redis.swap
319
+
320
+ # vm-max-memory configures the VM to use at max the specified amount of
321
+ # RAM. Everything that deos not fit will be swapped on disk *if* possible, that
322
+ # is, if there is still enough contiguous space in the swap file.
323
+ #
324
+ # With vm-max-memory 0 the system will swap everything it can. Not a good
325
+ # default, just specify the max amount of RAM you can in bytes, but it's
326
+ # better to leave some margin. For instance specify an amount of RAM
327
+ # that's more or less between 60 and 80% of your free RAM.
328
+ # vm-max-memory 0
329
+ vm-max-memory <%= vm_max_memory -%>
330
+
331
+ # Redis swap files is split into pages. An object can be saved using multiple
332
+ # contiguous pages, but pages can't be shared between different objects.
333
+ # So if your page is too big, small objects swapped out on disk will waste
334
+ # a lot of space. If you page is too small, there is less space in the swap
335
+ # file (assuming you configured the same number of total swap file pages).
336
+ #
337
+ # If you use a lot of small objects, use a page size of 64 or 32 bytes.
338
+ # If you use a lot of big objects, use a bigger page size.
339
+ # If unsure, use the default :)
340
+ vm-page-size 32
341
+
342
+ # Number of total memory pages in the swap file.
343
+ # Given that the page table (a bitmap of free/used pages) is taken in memory,
344
+ # every 8 pages on disk will consume 1 byte of RAM.
345
+ #
346
+ # The total swap size is vm-page-size * vm-pages
347
+ #
348
+ # With the default of 32-bytes memory pages and 134217728 pages Redis will
349
+ # use a 4 GB swap file, that will use 16 MB of RAM for the page table.
350
+ #
351
+ # It's better to use the smallest acceptable value for your application,
352
+ # but the default is large in order to work in most conditions.
353
+ vm-pages 134217728
354
+
355
+ # Max number of VM I/O threads running at the same time.
356
+ # This threads are used to read/write data from/to swap file, since they
357
+ # also encode and decode objects from disk to memory or the reverse, a bigger
358
+ # number of threads can help with big objects even if they can't help with
359
+ # I/O itself as the physical device may not be able to couple with many
360
+ # reads/writes operations at the same time.
361
+ #
362
+ # The special value of 0 turn off threaded I/O and enables the blocking
363
+ # Virtual Memory implementation.
364
+ vm-max-threads 4
365
+
366
+ ############################### ADVANCED CONFIG ###############################
367
+
368
+ # Glue small output buffers together in order to send small replies in a
369
+ # single TCP packet. Uses a bit more CPU but most of the times it is a win
370
+ # in terms of number of queries per second. Use 'yes' if unsure.
371
+ glueoutputbuf yes
372
+
373
+ # Hashes are encoded in a special way (much more memory efficient) when they
374
+ # have at max a given numer of elements, and the biggest element does not
375
+ # exceed a given threshold. You can configure this limits with the following
376
+ # configuration directives.
377
+ hash-max-zipmap-entries 64
378
+ hash-max-zipmap-value 512
379
+
380
+ # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
381
+ # order to help rehashing the main Redis hash table (the one mapping top-level
382
+ # keys to values). The hash table implementation redis uses (see dict.c)
383
+ # performs a lazy rehashing: the more operation you run into an hash table
384
+ # that is rhashing, the more rehashing "steps" are performed, so if the
385
+ # server is idle the rehashing is never complete and some more memory is used
386
+ # by the hash table.
387
+ #
388
+ # The default is to use this millisecond 10 times every second in order to
389
+ # active rehashing the main dictionaries, freeing memory when possible.
390
+ #
391
+ # If unsure:
392
+ # use "activerehashing no" if you have hard latency requirements and it is
393
+ # not a good thing in your environment that Redis can reply form time to time
394
+ # to queries with 2 milliseconds delay.
395
+ #
396
+ # use "activerehashing yes" if you don't have such hard requirements but
397
+ # want to free memory asap when possible.
398
+ activerehashing yes
399
+
400
+ ################################## INCLUDES ###################################
401
+
402
+ # Include one or more other config files here. This is useful if you
403
+ # have a standard template that goes to all redis server but also need
404
+ # to customize a few per-server settings. Include files can include
405
+ # other files, so use this wisely.
406
+ #
407
+ # include /path/to/local.conf
408
+ # include /path/to/other.conf
@@ -0,0 +1,76 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup { Redis.connect }
4
+
5
+ def conf_value(data, key)
6
+ data[/^#{key}\s+(.*)$/, 1]
7
+ end
8
+
9
+ test "path assignment" do
10
+ c = Rprov::Config.generate
11
+ c.path = "/some/path/here/"
12
+
13
+ assert c.path == "/some/path/here"
14
+ end
15
+
16
+ test "url" do
17
+ c = Rprov::Config.generate
18
+
19
+ assert "redis://:#{c.password}@#{c.host}:#{c.port}" == c.url
20
+ end
21
+
22
+ test "generating" do |r|
23
+ config = Rprov::Config.generate
24
+
25
+ assert r.exists("rprov:config:#{config.key}")
26
+
27
+ pass, port = r.hmget("rprov:config:#{config.key}", :password, :port)
28
+
29
+ assert config.password == pass
30
+ assert config.port == port
31
+ end
32
+
33
+ test "generating with a specified key" do
34
+ config = Rprov::Config.generate("foobar")
35
+
36
+ assert config.key == "foobar"
37
+ assert config.password =~ /^[a-zA-Z0-9]{40}$/
38
+ assert config.port.to_i == Rprov::Config::STARTING_PORT
39
+ end
40
+
41
+ test "redis conf" do
42
+ config = Rprov::Config.generate
43
+ config.path = "/data/ferrari"
44
+
45
+ assert conf_value(config.redis_conf, :port) == config.port
46
+ assert conf_value(config.redis_conf, :requirepass) == config.password
47
+ assert conf_value(config.redis_conf, :dir) == "/data/ferrari"
48
+ assert conf_value(config.redis_conf, :pidfile) == "/data/ferrari/redis.pid"
49
+ assert conf_value(config.redis_conf, :logfile) == "/data/ferrari/redis.log"
50
+ assert conf_value(config.redis_conf, :bind) == "127.0.0.1"
51
+ end
52
+
53
+ test "safe mode" do
54
+ config = Rprov::Config.generate
55
+ config.path = "/data/ferrari"
56
+ config.paranoid = true
57
+
58
+ assert config.redis_conf =~ /^rename-command CONFIG [a-zA-Z0-9]{40}$/
59
+ assert config.redis_conf =~ /^rename-command SLAVEOF ""$/
60
+ assert config.redis_conf =~ /^rename-command BGREWRITEAOF ""$/
61
+ assert config.redis_conf =~ /^rename-command SHUTDOWN [a-zA-Z0-9]{40}$/
62
+ assert config.redis_conf =~ /^rename-command SAVE ""$/
63
+ assert config.redis_conf =~ /^rename-command BGSAVE ""$/
64
+ end
65
+
66
+ test "vm settings behavior" do
67
+ config = Rprov::Config.new("some_key")
68
+
69
+ # by default is disabled
70
+ assert config.vm_enabled == "no"
71
+
72
+ # setting max memory enables it
73
+ config.memory = "1gb"
74
+ assert config.vm_enabled == "yes"
75
+ assert config.vm_max_memory == "1gb"
76
+ end
@@ -0,0 +1,66 @@
1
+ require "cutest"
2
+ require "stringio"
3
+
4
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
5
+
6
+ require "rprov"
7
+
8
+ def TMP(*args)
9
+ File.join(File.dirname(__FILE__), "tmp", *args)
10
+ end
11
+
12
+ TMP = TMP()
13
+
14
+ prepare do
15
+ FileUtils.rm_rf(TMP) if File.exist?(TMP)
16
+ FileUtils.mkdir_p(TMP)
17
+
18
+ redis = Redis.connect
19
+ redis.flushdb
20
+ end
21
+
22
+ # Since Kernel::exec transfers control to the external
23
+ # program, we need to change Rprov#exec and use `` instead.
24
+
25
+ class Rprov
26
+ def exec(cmd)
27
+ `#{cmd}`
28
+ end
29
+ end
30
+
31
+ module Cleanup
32
+ def test(*args, &block)
33
+ begin
34
+ super
35
+ rescue Exception => e
36
+ pids = `ps aux`.
37
+ split("\n").
38
+ grep(/redis-server \//).
39
+ map { |e| e.split(/\s+/)[1] }.
40
+ flatten
41
+
42
+ pids.each { |pid| Process.kill("TERM", pid.to_i) }
43
+
44
+ raise e
45
+ ensure
46
+ FileUtils.rm_rf(TMP)
47
+ end
48
+ end
49
+ end
50
+
51
+ include Cleanup
52
+
53
+ class Cutest::Scope
54
+ include Cleanup
55
+ end
56
+
57
+ def silenced
58
+ stdout, $stdout = $stdout, StringIO.new
59
+
60
+ yield
61
+
62
+ $stdout.rewind && $stdout.read
63
+ ensure
64
+ $stdout = stdout
65
+ end
66
+ alias :capture :silenced
@@ -0,0 +1,205 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ setup do
4
+ [Redis.connect, Rprov.new]
5
+ end
6
+
7
+ def redis_conf(path = "redis.*.conf")
8
+ Dir[TMP(path)].first
9
+ end
10
+
11
+ def redis_conf_data
12
+ File.read(redis_conf)
13
+ end
14
+
15
+ def redis_conf_id(conf = redis_conf)
16
+ conf[/redis\.(.*?)\.conf/, 1]
17
+ end
18
+
19
+ def port_pass(redis)
20
+ redis.hmget("rprov:config:#{redis_conf_id}", :port, :password)
21
+ end
22
+
23
+ test "basic setup" do |redis, rprov|
24
+ silenced do
25
+ rprov.setup(TMP)
26
+ end
27
+
28
+ assert redis_conf
29
+ assert redis.exists("rprov:config:#{redis_conf_id}")
30
+ end
31
+
32
+ test "setup on a non existing directory" do |redis, rprov|
33
+ silenced do
34
+ rprov.setup(TMP("subdir"))
35
+ end
36
+
37
+ conf = redis_conf("subdir/redis.*.conf")
38
+
39
+ assert conf
40
+ assert redis.exists("rprov:config:#{redis_conf_id(conf)}")
41
+ end
42
+
43
+ test "setup with memory specification" do |redis, rprov|
44
+ silenced do
45
+ rprov.memory = "1gb"
46
+ rprov.setup(TMP)
47
+ end
48
+
49
+ assert redis_conf_data =~ /^vm-enabled yes$/
50
+ assert redis_conf_data =~ /^vm-max-memory 1gb$/
51
+ end
52
+
53
+ test "setup with host specification" do |redis, rprov|
54
+ silenced do
55
+ rprov.host = "test.server.com"
56
+ rprov.setup(TMP)
57
+ end
58
+
59
+ assert redis_conf_data =~ /^bind test.server.com$/
60
+ end
61
+
62
+ test "setting up on an already setup path" do |redis, rprov|
63
+ rprov.setup(TMP)
64
+
65
+ assert_raise Rprov::Conflict do
66
+ rprov.setup(TMP)
67
+ end
68
+
69
+ configs =
70
+ redis.keys("rprov:config:*").reject { |k|
71
+ k == "rprov:config:port"
72
+ }.size
73
+
74
+ assert configs == 1
75
+ end
76
+
77
+ # starting
78
+ scope do
79
+ setup do
80
+ [Redis.connect, Rprov.new]
81
+ end
82
+
83
+ test "will setup when not existing" do |redis, rprov|
84
+ silenced do
85
+ rprov.setup(TMP)
86
+ rprov.start(TMP)
87
+ end
88
+
89
+ port, pass = port_pass(redis)
90
+
91
+ r = Redis.connect(:port => port, :password => pass)
92
+
93
+ assert r.set("foo", "bar")
94
+ assert r.get("foo") == "bar"
95
+
96
+ r.client.process(:shutdown)
97
+ end
98
+
99
+ test "running existing config" do |redis, rprov|
100
+ silenced do
101
+ rprov.setup(TMP)
102
+ rprov.start(TMP)
103
+ end
104
+
105
+ port, pass = port_pass(redis)
106
+
107
+ r = Redis.connect(:port => port, :password => pass)
108
+
109
+ assert r.set("foo", "bar")
110
+ assert r.get("foo") == "bar"
111
+
112
+ r.client.process(:shutdown)
113
+ end
114
+ end
115
+
116
+ # stopping
117
+ scope do
118
+ setup do
119
+ [Redis.connect, Rprov.new]
120
+ end
121
+
122
+ test "start / stop" do |redis, rprov|
123
+ silenced do
124
+ rprov.setup(TMP)
125
+ rprov.start(TMP)
126
+ end
127
+
128
+ port, pass = port_pass(redis)
129
+
130
+ r = Redis.connect(:port => port, :password => pass)
131
+
132
+ r.set("foo", "bar")
133
+
134
+ rprov.stop(TMP)
135
+
136
+ foo =
137
+ begin
138
+ r.client.get("foo")
139
+ rescue
140
+ end
141
+
142
+ assert foo != "bar"
143
+ end
144
+
145
+ test "start / stop / start" do |redis, rprov|
146
+ silenced do
147
+ rprov.setup(TMP)
148
+ rprov.start(TMP)
149
+ end
150
+
151
+ port, pass = port_pass(redis)
152
+
153
+ r = Redis.connect(:port => port, :password => pass)
154
+
155
+ r.set("foo", "bar")
156
+
157
+ rprov.stop(TMP)
158
+
159
+ silenced do
160
+ rprov.start(TMP)
161
+ end
162
+
163
+ sleep 0.2
164
+
165
+ r = Redis.connect(:port => port, :password => pass)
166
+ assert r.get("foo") == "bar"
167
+
168
+ rprov.stop(TMP)
169
+ end
170
+ end
171
+
172
+ # info
173
+ scope do
174
+ setup do
175
+ [Redis.connect, Rprov.new]
176
+ end
177
+
178
+ test "info" do |redis, rprov|
179
+ silenced do
180
+ rprov.setup(TMP)
181
+ end
182
+
183
+ out = capture { rprov.info(TMP) }
184
+
185
+ conf = Rprov::Config.new(redis_conf_id)
186
+
187
+ expected = (<<-EOT).gsub(/^ {4}/, "")
188
+
189
+ REDIS_URL:
190
+ #{conf.url}
191
+
192
+ Run `rprov start #{TMP}` to start this instance
193
+ EOT
194
+
195
+ assert expected == out
196
+ end
197
+
198
+ test "running info on a non-existent setup" do |redis, rprov|
199
+ assert ! redis_conf
200
+
201
+ assert_raise Rprov::Missing do
202
+ capture { rprov.info(TMP) }
203
+ end
204
+ end
205
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rprov
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
+ - Cyril David
13
+ - Michel Martens
14
+ - Damian Janowski
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-12-03 00:00:00 +08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: redis
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: nest
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: cutest
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :development
60
+ version_requirements: *id003
61
+ description: |-
62
+ Rprov is a simple command line utility which helps
63
+ you provision and manage Redis instances easily.
64
+ email:
65
+ - cyx@pipetodevnull.com
66
+ - michel@soveran.com
67
+ - djanowski@dimaion.com
68
+ executables:
69
+ - rprov
70
+ extensions: []
71
+
72
+ extra_rdoc_files: []
73
+
74
+ files:
75
+ - lib/rprov/config.rb
76
+ - lib/rprov/decorator.rb
77
+ - lib/rprov.rb
78
+ - README
79
+ - LICENSE
80
+ - test/config_test.rb
81
+ - test/helper.rb
82
+ - test/rprov_test.rb
83
+ - templates/redis.conf.erb
84
+ - bin/rprov
85
+ has_rdoc: true
86
+ homepage: http://github.com/cyx/rprov
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 2
116
+ summary: Redis Provisioning
117
+ test_files: []
118
+