jackowayed-tyrantmanager 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|