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.
@@ -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