tyrantmanager 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,340 @@
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
+ file_pairs << "#{node.name}=#{node.obj}" if node.obj
254
+ end
255
+
256
+ file_name_and_params = "#{db_file}##{file_pairs.join("#")}"
257
+
258
+ parts << file_name_and_params
259
+
260
+ return parts.join( " " )
261
+ end
262
+
263
+ #
264
+ # Start the tyrant
265
+ #
266
+ def start
267
+ o = %x[ #{start_command} ]
268
+ logger.info o
269
+ end
270
+
271
+ #
272
+ # kill the proc
273
+ #
274
+ def stop
275
+ begin
276
+ _pid = self.pid
277
+ Process.kill( "TERM" , _pid )
278
+ logger.info "Sent signal TERM to #{_pid}"
279
+ rescue Errno::EPERM
280
+ logger.info "Process #{_pid} is beyond my control"
281
+ rescue Errno::ESRCH
282
+ logger.info "Process #{_pid} is dead"
283
+ rescue => e
284
+ logger.error "Problem sending kill(TERM, #{_pid}) : #{e}"
285
+ end
286
+ end
287
+
288
+
289
+ #
290
+ # check if process is alive
291
+ #
292
+ def running?
293
+ begin
294
+ if File.exist?( self.pid_file ) then
295
+ _pid = self.pid
296
+ Process.kill( 0, _pid )
297
+ return true
298
+ else
299
+ return false
300
+ end
301
+ rescue Errno::EPERM
302
+ logger.info "Process #{_pid} is beyond my control"
303
+ rescue Errno::ESRCH
304
+ logger.info "Process #{_pid} is dead"
305
+ return false
306
+ rescue => e
307
+ logger.error "Problem sending kill(0, #{_pid}) : #{e}"
308
+ end
309
+ end
310
+
311
+ #
312
+ # return a network connection to this instance
313
+ #
314
+ def connection
315
+ Rufus::Tokyo::Tyrant.new( configuration.host, configuration.port.to_i )
316
+ end
317
+
318
+
319
+ private
320
+
321
+ #
322
+ # take the given path, and if it is not an absolute path append it
323
+ # to the home directory of the instance.
324
+ def append_to_home_if_not_absolute( p )
325
+ path = Pathname.new( p )
326
+ unless path.absolute? then
327
+ path = Pathname.new( home_dir ) + path
328
+ end
329
+ return path.to_s
330
+ end
331
+
332
+ #
333
+ # retrieve the cascading option from our config or the managers config if we
334
+ # don't have it.
335
+ #
336
+ def cascading_config( name )
337
+ configuration[name] || manager.configuration.instance_defaults[name]
338
+ end
339
+ end
340
+ 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 = 0
10
+ BUILD = 9
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,237 @@
1
+ #--
2
+ # Copyright (c) 2009 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'rubygems'
7
+ require 'loquacious'
8
+ require 'tyrant_manager/version'
9
+ require 'tyrant_manager/paths'
10
+ require 'tyrant_manager/log'
11
+
12
+ class TyrantManager
13
+ include TyrantManager::Paths
14
+
15
+ class Error < StandardError; end
16
+
17
+ class << TyrantManager
18
+ #
19
+ # The basename of the default config file
20
+ #
21
+ def config_file_basename
22
+ "config.rb"
23
+ end
24
+
25
+ #
26
+ # The basename of the directory that holds the tyrant manager system
27
+ #
28
+ def basedir
29
+ "tyrant"
30
+ end
31
+
32
+ #
33
+ # is the given directory a tyrant root directory. A tyrant root has a
34
+ # +config_file_basename+ file in the top level
35
+ #
36
+ def is_tyrant_root?( dir )
37
+ cfg = File.join( dir, config_file_basename )
38
+ return true if File.directory?( dir ) and File.exist?( cfg )
39
+ return false
40
+ end
41
+
42
+ #
43
+ # Return the path of the tyrant dir if there is one relative to the current
44
+ # working directory. This means that there is a +config_file_basename+ in
45
+ # the current working directory.
46
+ #
47
+ # returns Dir.pwd if this is the case, nil otherwise
48
+ #
49
+ def cwd_default_directory
50
+ default_dir = Dir.pwd
51
+ return default_dir if is_tyrant_root?( default_dir )
52
+ return nil
53
+ end
54
+
55
+ #
56
+ # Return the path of the tyrant dir as it pertains to the
57
+ # TYRANT_MANAGER_HOME environment variable. If the directory is
58
+ # a tyrant root, then return that directory, otherwise return nil
59
+ #
60
+ def env_default_directory
61
+ default_dir = ENV['TYRANT_MANAGER_HOME']
62
+ return default_dir if default_dir and is_tyrant_root?( default_dir )
63
+ return nil
64
+ end
65
+
66
+ #
67
+ # Return the path of the tyrant dir as it pertains to the default global
68
+ # setting of 'lcoalstatedir' which is /opt/local/var, /var, or similar
69
+ #
70
+ def localstate_default_directory
71
+ default_dir = File.join( Config::CONFIG['localstatedir'], basedir )
72
+ return default_dir if is_tyrant_root?( default_dir )
73
+ return nil
74
+ end
75
+
76
+ #
77
+ # The default tyrant directory. It is the first of these that matches:
78
+ #
79
+ # * current directory if there is a +config_file_basename+ file in the
80
+ # current dirctory
81
+ # * the value of the TYRANT_MANAGER_HOME environment variable
82
+ # * File.join( Config::CONFIG['localstatedir'], basedir )
83
+ #
84
+ def default_directory
85
+ defaults = [ self.cwd_default_directory,
86
+ self.env_default_directory,
87
+ self.localstate_default_directory ]
88
+ dd = nil
89
+ loop do
90
+ dd = defaults.shift
91
+ break if dd or defaults.empty?
92
+ end
93
+ raise Error, "No default_directory found" unless dd
94
+ return dd
95
+ end
96
+
97
+ #
98
+ # Return the default directory if it exists, otherwise fallback to .home_dir
99
+ #
100
+ def default_or_home_directory
101
+ hd = TyrantManager.home_dir
102
+ begin
103
+ hd = TyrantManager.default_directory
104
+ rescue => e
105
+ # yup, using home
106
+ end
107
+ return hd
108
+ end
109
+
110
+
111
+ #
112
+ # Setup the tyrant manager in the given directory. This means creating it
113
+ # if it does not exist.
114
+ #
115
+ def setup( dir = default_directory )
116
+ unless File.directory?( dir )
117
+ logger.info "Creating directory #{dir}"
118
+ FileUtils.mkdir_p( dir )
119
+ end
120
+
121
+ cfg = File.join( dir, config_file_basename )
122
+
123
+ unless File.exist?( cfg )
124
+ template = TyrantManager::Paths.data_path( config_file_basename )
125
+ logger.info "Creating default config file #{cfg}"
126
+ FileUtils.cp( template, dir )
127
+ end
128
+
129
+ %w[ instances log tmp ].each do |subdir|
130
+ subdir = File.join( dir, subdir )
131
+ unless File.directory?( subdir ) then
132
+ logger.info "Creating directory #{subdir}"
133
+ FileUtils.mkdir subdir
134
+ end
135
+ end
136
+ return TyrantManager.new( dir )
137
+ end
138
+ end
139
+
140
+ #
141
+ # Initialize the manager, which is nothing more than creating the instance and
142
+ # setting the home directory.
143
+ #
144
+ def initialize( directory = TyrantManager.default_directory )
145
+ self.home_dir = File.expand_path( directory )
146
+ if File.exist?( self.config_file ) then
147
+ configuration # force a load
148
+ else
149
+ raise Error, "#{home_dir} is not a valid archive. #{self.config_file} does not exist"
150
+ end
151
+ end
152
+
153
+ def logger
154
+ Logging::Logger[self]
155
+ end
156
+
157
+ #
158
+ # The configuration file for the manager
159
+ #
160
+ def config_file
161
+ @config_file ||= File.join( home_dir, TyrantManager.config_file_basename )
162
+ end
163
+
164
+ #
165
+ # load the configuration
166
+ #
167
+ def configuration
168
+ unless @configuration
169
+ eval( IO.read( self.config_file ) )
170
+ @configuration = Loquacious::Configuration.for("manager")
171
+ end
172
+ return @configuration
173
+ end
174
+
175
+ #
176
+ # Create a runner instance with the given options
177
+ #
178
+ def runner_for( options )
179
+ Runner.new( self, options )
180
+ end
181
+
182
+ #
183
+ # Return the list of instances that the manager knows about
184
+ #
185
+ def instances
186
+ unless @instances then
187
+ candidates = [ self.instances_path ]
188
+ if configuration.instances then
189
+ candidates = configuration.instances
190
+ end
191
+
192
+ @instances = {}
193
+ while not candidates.empty? do
194
+ candidate = candidates.pop
195
+ cpath = append_to_home_if_not_absolute( candidate )
196
+ begin
197
+ t = TyrantInstance.new( cpath )
198
+ t.manager = self
199
+ @instances[t.name] = t
200
+ rescue TyrantManager::Error => e
201
+ if File.directory?( cpath ) then
202
+ Dir.glob( "#{cpath}/*" ).each do |epath|
203
+ if File.directory?( epath ) then
204
+ candidates.push epath
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end #while
210
+ end
211
+ return @instances
212
+ end
213
+
214
+ def each_instance
215
+ instances.keys.sort.each do |name|
216
+ yield instances[name]
217
+ end
218
+ end
219
+
220
+ private
221
+ #
222
+ # take the given path, and if it is not an absolute path append it
223
+ # to the home directory of the instance.
224
+ def append_to_home_if_not_absolute( p )
225
+ path = Pathname.new( p )
226
+ unless path.absolute? then
227
+ path = Pathname.new( home_dir ) + path
228
+ end
229
+ return path.to_s
230
+ end
231
+
232
+
233
+ end
234
+
235
+ require 'tyrant_manager/cli'
236
+ require 'tyrant_manager/runner'
237
+ require 'tyrant_manager/tyrant_instance'
@@ -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