tyrantmanager 1.0.9

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,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