jackowayed-tyrantmanager 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +34 -0
- data/LICENSE +13 -0
- data/README.rdoc +277 -0
- data/bin/tyrantmanager +10 -0
- data/data/config.rb +94 -0
- data/data/default_instance_config.rb +244 -0
- data/gemspec.rb +53 -0
- data/lib/tyrant_manager.rb +237 -0
- data/lib/tyrant_manager/cli.rb +152 -0
- data/lib/tyrant_manager/command.rb +119 -0
- data/lib/tyrant_manager/commands/create_instance.rb +27 -0
- data/lib/tyrant_manager/commands/list.rb +28 -0
- data/lib/tyrant_manager/commands/replication_status.rb +92 -0
- data/lib/tyrant_manager/commands/start.rb +32 -0
- data/lib/tyrant_manager/commands/stats.rb +24 -0
- data/lib/tyrant_manager/commands/status.rb +26 -0
- data/lib/tyrant_manager/commands/stop.rb +23 -0
- data/lib/tyrant_manager/log.rb +91 -0
- data/lib/tyrant_manager/paths.rb +77 -0
- data/lib/tyrant_manager/runner.rb +33 -0
- data/lib/tyrant_manager/tyrant_instance.rb +380 -0
- data/lib/tyrant_manager/version.rb +27 -0
- data/lib/tyrantmanager.rb +2 -0
- data/spec/command_spec.rb +37 -0
- data/spec/paths_spec.rb +57 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/tyrant_instance_spec.rb +106 -0
- data/spec/tyrant_manager_spec.rb +69 -0
- data/spec/version_spec.rb +16 -0
- data/tasks/announce.rake +43 -0
- data/tasks/config.rb +99 -0
- data/tasks/distribution.rake +38 -0
- data/tasks/documentation.rake +32 -0
- data/tasks/rspec.rake +29 -0
- data/tasks/rubyforge.rake +51 -0
- data/tasks/utils.rb +80 -0
- metadata +172 -0
@@ -0,0 +1,380 @@
|
|
1
|
+
require 'tyrant_manager'
|
2
|
+
require 'rufus/tokyo/tyrant'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
class TyrantManager
|
6
|
+
class TyrantInstance
|
7
|
+
class << TyrantInstance
|
8
|
+
def logger
|
9
|
+
Logging::Logger[self]
|
10
|
+
end
|
11
|
+
#
|
12
|
+
# Create all the directories needed for a tyrant
|
13
|
+
#
|
14
|
+
def setup( dir )
|
15
|
+
unless File.directory?( dir )
|
16
|
+
logger.info "Creating directory #{dir}"
|
17
|
+
FileUtils.mkdir_p( dir )
|
18
|
+
end
|
19
|
+
|
20
|
+
cfg = File.join( dir, TyrantManager.config_file_basename )
|
21
|
+
instance_name = File.basename( dir )
|
22
|
+
|
23
|
+
unless File.exist?( cfg )
|
24
|
+
template = TyrantManager::Paths.data_path( "default_instance_config.rb" )
|
25
|
+
logger.info "Creating default config file #{cfg}"
|
26
|
+
File.open( cfg, "w+" ) do |f|
|
27
|
+
f.write ERB.new( IO.read( template ) ).result( binding )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
%w[ ulog data lua log ].each do |subdir|
|
32
|
+
subdir = File.join( dir, subdir )
|
33
|
+
unless File.directory?( subdir ) then
|
34
|
+
logger.info "Creating directory #{subdir}"
|
35
|
+
FileUtils.mkdir subdir
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return TyrantInstance.new( dir )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# the full path to the instance home directory
|
44
|
+
attr_reader :home_dir
|
45
|
+
|
46
|
+
# the name of this instance
|
47
|
+
attr_reader :name
|
48
|
+
|
49
|
+
# the manager that is associated with this instance
|
50
|
+
attr_accessor :manager
|
51
|
+
|
52
|
+
#
|
53
|
+
# Create an instance connected to the tyrant located in the given directory
|
54
|
+
#
|
55
|
+
def initialize( dir )
|
56
|
+
@home_dir = File.expand_path( dir )
|
57
|
+
@name = File.basename( @home_dir )
|
58
|
+
if File.exist?( self.config_file ) then
|
59
|
+
configuration # force a load
|
60
|
+
else
|
61
|
+
raise Error, "#{home_dir} is not a valid archive. #{self.config_file} does not exist"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def logger
|
66
|
+
Logging::Logger[self]
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# The configuration file for the instance
|
71
|
+
#
|
72
|
+
def config_file
|
73
|
+
@config_file ||= File.join( home_dir, TyrantManager.config_file_basename )
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# load the configuration
|
78
|
+
#
|
79
|
+
def configuration
|
80
|
+
unless @configuration then
|
81
|
+
eval( IO.read( self.config_file ) )
|
82
|
+
@configuration = Loquacious::Configuration.for( name )
|
83
|
+
end
|
84
|
+
return @configuration
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# The pid file
|
89
|
+
#
|
90
|
+
def pid_file
|
91
|
+
@pid_file ||= append_to_home_if_not_absolute( configuration.pid_file )
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# The pid of the service
|
96
|
+
#
|
97
|
+
def pid
|
98
|
+
Float( IO.read( pid_file ).strip ).to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# The log file
|
103
|
+
#
|
104
|
+
def log_file
|
105
|
+
@log_file ||= append_to_home_if_not_absolute( configuration.log_file )
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# The lua extension file to load on start
|
110
|
+
#
|
111
|
+
def lua_extension_file
|
112
|
+
@lua_extension_file ||= append_to_home_if_not_absolute( configuration.lua_extension_file )
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# The lua extension file to load on start
|
117
|
+
#
|
118
|
+
def replication_timestamp_file
|
119
|
+
@replication_timestamp_file ||= append_to_home_if_not_absolute( configuration.replication_timestamp_file )
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# The directory housing the database file
|
124
|
+
#
|
125
|
+
def data_dir
|
126
|
+
@data_dir ||= append_to_home_if_not_absolute( configuration.data_dir )
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# The full path to the database file.
|
131
|
+
#
|
132
|
+
def db_file( type = configuration.type )
|
133
|
+
unless @db_file then
|
134
|
+
@db_file = case type
|
135
|
+
when "memory-hash" then "*"
|
136
|
+
when "memory-tree" then "+"
|
137
|
+
when "hash" then File.join( data_dir, "#{name}.tch" )
|
138
|
+
when "tree" then File.join( data_dir, "#{name}.tcb" )
|
139
|
+
when "fixed" then File.join( data_dir, "#{name}.tcf" )
|
140
|
+
when "table" then File.join( data_dir, "#{name}.tct" )
|
141
|
+
else
|
142
|
+
raise Error, "Unknown configuration type [#{configuration.type}]"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
return @db_file
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# The directory housing the database file
|
150
|
+
#
|
151
|
+
def ulog_dir
|
152
|
+
@ulog_dir ||= append_to_home_if_not_absolute( configuration.ulog_dir )
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# The lua extension file
|
157
|
+
#
|
158
|
+
def lua_extension_file
|
159
|
+
@lua_extension_file ||= append_to_home_if_not_absolute( configuration.lua_extension_file )
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# The replication timestamp file
|
164
|
+
#
|
165
|
+
def replication_timestamp_file
|
166
|
+
@replication_timestamp_file ||= append_to_home_if_not_absolute( configuration.replication_timestamp_file )
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
#
|
171
|
+
# Start command.
|
172
|
+
#
|
173
|
+
# This is a bit convoluted to bring together all the options and put them
|
174
|
+
# into one big commandline item.
|
175
|
+
#
|
176
|
+
def start_command
|
177
|
+
|
178
|
+
##-- ttserver executable
|
179
|
+
parts = [ manager.configuration.ttserver ]
|
180
|
+
|
181
|
+
##-- host and port
|
182
|
+
parts << "-host #{configuration.host}" if configuration.host
|
183
|
+
parts << "-port #{configuration.port}" if configuration.port
|
184
|
+
|
185
|
+
##-- thread options
|
186
|
+
if thnum = cascading_config( 'thread_count' ) then
|
187
|
+
parts << "-thnum #{thnum}"
|
188
|
+
end
|
189
|
+
if tout = cascading_config( 'session_timeout' ) then
|
190
|
+
parts << "-tout #{tout}"
|
191
|
+
end
|
192
|
+
|
193
|
+
##-- daemoization and pid
|
194
|
+
parts << "-dmn" if cascading_config( 'daemonize' )
|
195
|
+
parts << "-pid #{pid_file}"
|
196
|
+
|
197
|
+
|
198
|
+
##-- logging
|
199
|
+
parts << "-log #{log_file}"
|
200
|
+
if log_level = cascading_config( 'log_level' ) then
|
201
|
+
if log_level == "error" then
|
202
|
+
parts << "-le"
|
203
|
+
elsif log_level == "debug" then
|
204
|
+
parts << "-ld"
|
205
|
+
elsif log_level == "info" then
|
206
|
+
# leave it at info
|
207
|
+
else
|
208
|
+
raise Error, "Invalid log level setting [#{log_level}]"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
##-- update logs
|
213
|
+
parts << "-ulog #{ulog_dir}"
|
214
|
+
if ulim = cascading_config( 'update_log_size' )then
|
215
|
+
parts << "-ulim #{ulim}"
|
216
|
+
end
|
217
|
+
parts << "-uas" if cascading_config( 'update_log_async' )
|
218
|
+
|
219
|
+
##-- replication items, server id, master, replication timestamp file
|
220
|
+
parts << "-sid #{configuration.server_id}" if configuration.server_id
|
221
|
+
parts << "-mhost #{configuration.master_server}" if configuration.master_server
|
222
|
+
parts << "-mport #{configuration.master_port}" if configuration.master_port
|
223
|
+
parts << "-rts #{replication_timestamp_file}" if configuration.replication_timestamp_file
|
224
|
+
|
225
|
+
##-- lua extension
|
226
|
+
if configuration.lua_extension_file then
|
227
|
+
if File.exist?( lua_extension_file ) then
|
228
|
+
parts << "-ext #{lua_extension_file}"
|
229
|
+
if pc = configuration.periodic_command then
|
230
|
+
if pc.name and pc.period then
|
231
|
+
parts << "-extpc #{pc.name} #{pc.period}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
##-- command permissiosn
|
238
|
+
if deny = cascading_config( "deny_commands" ) then
|
239
|
+
parts << "-mask #{deny.join(",")}"
|
240
|
+
end
|
241
|
+
|
242
|
+
if allow = cascading_config( "allow_commands" ) then
|
243
|
+
parts << "-unmask #{allow.join(",")}"
|
244
|
+
end
|
245
|
+
|
246
|
+
##-- now for the filename. The format is
|
247
|
+
# filename.ext#opts=ld#mode=wc#tuning_param=value#tuning_param=value...
|
248
|
+
#
|
249
|
+
file_pairs = []
|
250
|
+
file_pairs << "opts=#{configuration.opts}"
|
251
|
+
file_pairs << "mode=#{configuration.mode}"
|
252
|
+
Loquacious::Configuration::Iterator.new( configuration.tuning_params ).each do |node|
|
253
|
+
# allow for multiple full-text indices
|
254
|
+
if node.name == 'idx'
|
255
|
+
node.obj.split(',').each do |idx|
|
256
|
+
file_pairs << "idx=#{idx}"
|
257
|
+
end if node.obj
|
258
|
+
else
|
259
|
+
file_pairs << "#{node.name}=#{node.obj}" if node.obj
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
file_name_and_params = "#{db_file}##{file_pairs.join("#")}"
|
264
|
+
|
265
|
+
parts << file_name_and_params
|
266
|
+
|
267
|
+
return parts.join( " " )
|
268
|
+
end
|
269
|
+
|
270
|
+
#
|
271
|
+
# Start the tyrant
|
272
|
+
#
|
273
|
+
def start
|
274
|
+
o = %x[ #{start_command} ]
|
275
|
+
logger.info o
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# kill the proc
|
280
|
+
#
|
281
|
+
def stop
|
282
|
+
begin
|
283
|
+
_pid = self.pid
|
284
|
+
Process.kill( "TERM" , _pid )
|
285
|
+
logger.info "Sent signal TERM to #{_pid}"
|
286
|
+
rescue Errno::EPERM
|
287
|
+
logger.info "Process #{_pid} is beyond my control"
|
288
|
+
rescue Errno::ESRCH
|
289
|
+
logger.info "Process #{_pid} is dead"
|
290
|
+
rescue => e
|
291
|
+
logger.error "Problem sending kill(TERM, #{_pid}) : #{e}"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
#
|
297
|
+
# check if process is alive
|
298
|
+
#
|
299
|
+
def running?
|
300
|
+
begin
|
301
|
+
if File.exist?( self.pid_file ) then
|
302
|
+
_pid = self.pid
|
303
|
+
Process.kill( 0, _pid )
|
304
|
+
return true
|
305
|
+
else
|
306
|
+
return false
|
307
|
+
end
|
308
|
+
rescue Errno::EPERM
|
309
|
+
logger.info "Process #{_pid} is beyond my control"
|
310
|
+
rescue Errno::ESRCH
|
311
|
+
logger.info "Process #{_pid} is dead"
|
312
|
+
return false
|
313
|
+
rescue => e
|
314
|
+
logger.error "Problem sending kill(0, #{_pid}) : #{e}"
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
# return the stats for this instance
|
320
|
+
#
|
321
|
+
def stat
|
322
|
+
connection.stat
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# return a network connection to this instance
|
327
|
+
#
|
328
|
+
def connection
|
329
|
+
host = configuration.host
|
330
|
+
|
331
|
+
# you cannot connect to 0.0.0.0
|
332
|
+
if host == "0.0.0.0" then
|
333
|
+
host = "localhost"
|
334
|
+
end
|
335
|
+
Rufus::Tokyo::Tyrant.new( configuration.host, configuration.port.to_i )
|
336
|
+
end
|
337
|
+
|
338
|
+
#
|
339
|
+
# Is this instance a slave of another server? This means it could be in a
|
340
|
+
# master-slave or master-master relationship
|
341
|
+
#
|
342
|
+
def is_slave?
|
343
|
+
s = connection.stat
|
344
|
+
return (s['mhost'] and s['mport'])
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
# return a network connection to the master server of this instance
|
349
|
+
#
|
350
|
+
def master_connection
|
351
|
+
if is_slave? then
|
352
|
+
s = self.stat
|
353
|
+
return Rufus::Tokyo::Tyrant.new( s['mhost'], s['mport'].to_i )
|
354
|
+
end
|
355
|
+
return nil
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
#
|
362
|
+
# take the given path, and if it is not an absolute path append it
|
363
|
+
# to the home directory of the instance.
|
364
|
+
def append_to_home_if_not_absolute( p )
|
365
|
+
path = Pathname.new( p )
|
366
|
+
unless path.absolute? then
|
367
|
+
path = Pathname.new( home_dir ) + path
|
368
|
+
end
|
369
|
+
return path.to_s
|
370
|
+
end
|
371
|
+
|
372
|
+
#
|
373
|
+
# retrieve the cascading option from our config or the managers config if we
|
374
|
+
# don't have it.
|
375
|
+
#
|
376
|
+
def cascading_config( name )
|
377
|
+
configuration[name] || manager.configuration.instance_defaults[name]
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details
|
4
|
+
#++
|
5
|
+
|
6
|
+
class TyrantManager
|
7
|
+
module Version
|
8
|
+
MAJOR = 1
|
9
|
+
MINOR = 1
|
10
|
+
BUILD = 0
|
11
|
+
|
12
|
+
def to_a
|
13
|
+
[MAJOR, MINOR, BUILD]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
to_a.join(".")
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{ :major => MAJOR, :minor => MINOR, :build => BUILD }
|
22
|
+
end
|
23
|
+
|
24
|
+
extend self
|
25
|
+
end
|
26
|
+
VERSION = Version.to_s
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path( File.join( File.dirname( __FILE__ ),"spec_helper.rb"))
|
2
|
+
|
3
|
+
require 'tyrant_manager/command'
|
4
|
+
|
5
|
+
class Junk < TyrantManager::Command; end
|
6
|
+
|
7
|
+
describe TyrantManager::Command do
|
8
|
+
before( :each ) do
|
9
|
+
@cmd = TyrantManager::Command.new( nil )
|
10
|
+
end
|
11
|
+
|
12
|
+
it "has a command name" do
|
13
|
+
@cmd.command_name.should == "command"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can log" do
|
17
|
+
@cmd.logger.info "this is a log statement"
|
18
|
+
spec_log.should =~ /this is a log statement/
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an error if it cannot find the class required" do
|
22
|
+
lambda { TyrantManager::Command.find( "foo" ) }.should raise_error( TyrantManager::Command::CommandNotFoundError,
|
23
|
+
/No command for 'foo' was found./ )
|
24
|
+
end
|
25
|
+
|
26
|
+
it "registers inherited classes" do
|
27
|
+
TyrantManager::Command.list.should be_include( Junk )
|
28
|
+
TyrantManager::Command.list.delete( Junk )
|
29
|
+
TyrantManager::Command.list.should_not be_include(Junk)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "classes cannot be run without implementing 'run'" do
|
33
|
+
j = Junk.new( nil )
|
34
|
+
j.respond_to?(:run).should == true
|
35
|
+
lambda { j.run }.should raise_error( NotImplementedError, /The #run method must be implemented/)
|
36
|
+
end
|
37
|
+
end
|
data/spec/paths_spec.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__),"spec_helper.rb"))
|
2
|
+
|
3
|
+
require 'tyrant_manager/paths'
|
4
|
+
|
5
|
+
describe TyrantManager::Paths do
|
6
|
+
before(:each) do
|
7
|
+
@install_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
8
|
+
@install_dir += "/"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "root dir should be correct" do
|
12
|
+
TyrantManager::Paths.install_dir.should == @install_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
it "home dir should be correct" do
|
16
|
+
TyrantManager::Paths.home_dir.should == @install_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
%w[ bin lib spec data log tmp ].each do |d|
|
20
|
+
it "#{d} path should be correct" do
|
21
|
+
TyrantManager::Paths.send( "#{d}_path" ).should == File.join(@install_dir, "#{d}/" )
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "setting home_dir" do
|
26
|
+
before( :each ) do
|
27
|
+
@tmp_dir = "/some/location/#{Process.pid}"
|
28
|
+
TyrantManager::Paths.home_dir = @tmp_dir
|
29
|
+
end
|
30
|
+
|
31
|
+
%w[ log tmp instances ].each do |d|
|
32
|
+
check_path = "#{d}_path"
|
33
|
+
it "affects the location of #{check_path}" do
|
34
|
+
p = TyrantManager::Paths.send( check_path )
|
35
|
+
p.should == ( File.join( @tmp_dir, d ) + File::SEPARATOR )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
%w[ bin lib spec data ].each do |d|
|
40
|
+
check_path = "#{d}_path"
|
41
|
+
it "does not affect the location of #{check_path}" do
|
42
|
+
p = TyrantManager::Paths.send( check_path )
|
43
|
+
p.should == ( File.join( @install_dir, d ) + File::SEPARATOR )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Default TyrantManager home_dir" do
|
49
|
+
it 'has a different default home_dir for the top level TyrantManager' do
|
50
|
+
TyrantManager.home_dir.should == File.join( Config::CONFIG['localstatedir'], 'lib', 'tyrant' )
|
51
|
+
end
|
52
|
+
|
53
|
+
it "has an instances dir" do
|
54
|
+
TyrantManager.default_instances_dir.should == File.join( Config::CONFIG['localstatedir'], 'lib', 'tyrant', 'instances' ) + "/"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|