jackowayed-tyrantmanager 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,2 @@
1
+ require 'tyrant_manager'
2
+
@@ -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
@@ -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