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,53 @@
1
+ require 'rubygems'
2
+ require 'tyrant_manager/version'
3
+ require 'tasks/config'
4
+
5
+ TyrantManager::GEM_SPEC = Gem::Specification.new do |spec|
6
+ proj = Configuration.for('project')
7
+ spec.name = proj.name
8
+ spec.version = TyrantManager::VERSION
9
+
10
+ spec.author = proj.author
11
+ spec.email = proj.email
12
+ spec.homepage = proj.homepage
13
+ spec.summary = proj.summary
14
+ spec.description = proj.description
15
+ spec.platform = Gem::Platform::RUBY
16
+
17
+
18
+ pkg = Configuration.for('packaging')
19
+ spec.files = pkg.files.all
20
+ spec.executables = pkg.files.bin.collect { |b| File.basename(b) }
21
+
22
+ # add dependencies here
23
+ spec.add_dependency( "loquacious", "~> 1.3.0")
24
+ spec.add_dependency( "rufus-tokyo", "~> 1.0.0")
25
+ spec.add_dependency( "logging", "~> 1.1.4" )
26
+ spec.add_dependency( "main", "~> 2.8.4" )
27
+
28
+ # development dependencies
29
+ spec.add_development_dependency("configuration", ">= 0.0.5")
30
+ spec.add_development_dependency( "rake", "~> 0.8.3")
31
+
32
+ if ext_conf = Configuration.for_if_exist?("extension") then
33
+ spec.extensions << ext_conf.configs
34
+ spec.extensions.flatten!
35
+ end
36
+
37
+ if rdoc = Configuration.for_if_exist?('rdoc') then
38
+ spec.has_rdoc = true
39
+ spec.extra_rdoc_files = pkg.files.rdoc
40
+ spec.rdoc_options = rdoc.options + [ "--main" , rdoc.main_page ]
41
+ else
42
+ spec.has_rdoc = false
43
+ end
44
+
45
+ if test = Configuration.for_if_exist?('testing') then
46
+ spec.test_files = test.files
47
+ end
48
+
49
+ if rf = Configuration.for_if_exist?('rubyforge') then
50
+ spec.rubyforge_project = rf.project
51
+ end
52
+
53
+ 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,152 @@
1
+ require 'main'
2
+ require 'tyrant_manager'
3
+
4
+ class TyrantManager
5
+ Cli = Main.create {
6
+ author "Copyright 2009 (c) Jeremy Hinegardner"
7
+ version ::TyrantManager::VERSION
8
+
9
+ description <<-txt
10
+ The command line tool for managing tyrant instances.
11
+
12
+ Run 'tyrantmanager help modename' for more information
13
+ txt
14
+
15
+ run { help! }
16
+
17
+ mode( :setup ) {
18
+ description "Setup an tyrant manager location"
19
+ argument( :home ) {
20
+ description "The home directory of the tyrant manager"
21
+ required
22
+ default TyrantManager.default_or_home_directory
23
+ }
24
+
25
+ run {
26
+ TyrantManager::Log.init
27
+ TyrantManager.setup( params['home'].value )
28
+ }
29
+ }
30
+
31
+ mode( 'create-instance' ) {
32
+ description <<-txt
33
+ Create a new tyrant instance in the specified directory
34
+ txt
35
+
36
+ argument( 'instance-home' ) do
37
+ description <<-txt
38
+ The home directory of the tyrant instance. If this is a full path it
39
+ will be used. If it is a relative path, it will be relative to the
40
+ manager's 'instances' configuration parameter
41
+ txt
42
+ end
43
+
44
+ mixin :option_home
45
+ mixin :option_log_level
46
+
47
+ run { Cli.run_command_with_params( "create-instance", params ) }
48
+ }
49
+
50
+ mode( 'start' ) {
51
+ description "Start all the tyrants listed"
52
+ mixin :option_home
53
+ mixin :option_log_level
54
+ mixin :argument_instances
55
+ option( 'dry-run' ) {
56
+ description "Do not start, just show the commands"
57
+ default false
58
+ }
59
+
60
+ run { Cli.run_command_with_params( 'start', params ) }
61
+ }
62
+
63
+
64
+ mode( 'stop' ) {
65
+ description "Stop all the tyrants listed"
66
+ mixin :option_home
67
+ mixin :option_log_level
68
+ mixin :argument_instances
69
+
70
+ run { Cli.run_command_with_params( 'stop', params ) }
71
+ }
72
+
73
+ mode('replication-status') {
74
+ description "Describe the replication status of those servers using replication"
75
+ mixin :option_home
76
+ mixin :option_log_level
77
+ mixin :argument_instances
78
+ run { Cli.run_command_with_params( 'replication-status', params ) }
79
+ }
80
+
81
+ mode('process-status') {
82
+ description "Check the running status of all the tyrants listed"
83
+ mixin :option_home
84
+ mixin :option_log_level
85
+
86
+ mixin :argument_instances
87
+
88
+ run { Cli.run_command_with_params( 'process-status', params ) }
89
+ }
90
+
91
+ mode( 'stats' ) {
92
+ description "Dump the database statistics of each of the tyrants listed"
93
+ mixin :option_home
94
+ mixin :option_log_level
95
+
96
+ mixin :argument_instances
97
+
98
+ run { Cli.run_command_with_params( 'stats', params ) }
99
+ }
100
+
101
+
102
+ mode('list') {
103
+ description "list the instances and their home directories"
104
+ mixin :option_home
105
+ mixin :option_log_level
106
+ mixin :argument_instances
107
+ run { Cli.run_command_with_params( 'list', params ) }
108
+ }
109
+
110
+ #--- Mixins ---
111
+ mixin :option_home do
112
+ option( :home ) do
113
+ description "The home directory of the tyrant manager"
114
+ argument :required
115
+ validate { |v| ::File.directory?( v ) }
116
+ default TyrantManager.default_or_home_directory
117
+ end
118
+ end
119
+
120
+ mixin :option_log_level do
121
+ option('log-level') do
122
+ description "The verbosity of logging, one of [ #{::Logging::LNAMES.map {|l| l.downcase}.join(", ")} ]"
123
+ argument :required
124
+ validate { |l| %w[ debug info warn error fatal off ].include?( l.downcase ) }
125
+ end
126
+ end
127
+
128
+ mixin :argument_instances do
129
+ argument('instances') do
130
+ description "A comman separated list of instance names the tyrant manager knows about"
131
+ argument :required
132
+ cast :list
133
+ default 'all'
134
+ end
135
+ end
136
+ }
137
+
138
+ #
139
+ # Convert the Parameters::List that exists as the parameters from Main
140
+ #
141
+ def Cli.params_to_hash( params )
142
+ (hash = params.to_hash ).keys.each { |key| hash[key] = hash[key].value }
143
+ return hash
144
+ end
145
+
146
+ def Cli.run_command_with_params( command, params )
147
+ phash = Cli.params_to_hash( params )
148
+ TyrantManager::Log.init( phash )
149
+ tm = TyrantManager.new( phash.delete('home') )
150
+ tm.runner_for( phash ).run( command )
151
+ end
152
+ end
@@ -0,0 +1,119 @@
1
+ require 'tyrant_manager'
2
+ class TyrantManager
3
+ #
4
+ # The Command is the base class for any class to be used as a command
5
+ #
6
+ # All commands run within the context of an existing TyrantManager location.
7
+ # Before the command starts the current working directory will be inside the
8
+ # tyrant manager's home directory.
9
+ #
10
+ # A command has a lifecyle of:
11
+ #
12
+ # * instantiation with a TyrantManager instance and an options hash
13
+ # * one call to before
14
+ # * one call to run
15
+ # * one call to after
16
+ #
17
+ # In case of an exception raised during the lifecycle, the +error+ method will
18
+ # be called. The +after+ method is called no matter what at the end of the
19
+ # lifecycle, even if an error has occurred.
20
+ #
21
+ class Command
22
+ def self.command_name
23
+ name.split("::").last.downcase
24
+ end
25
+
26
+ attr_reader :options
27
+ attr_reader :manager
28
+
29
+ #
30
+ # Instantiated and given the tyrant manager instance it is to operate
31
+ # through and a hash of options
32
+ #
33
+ def initialize( manager, opts = {} )
34
+ @manager = manager
35
+ @options = opts
36
+ end
37
+
38
+ def command_name
39
+ self.class.command_name
40
+ end
41
+
42
+ def logger
43
+ Logging::Logger[self]
44
+ end
45
+
46
+ #------------------------------------------------------------------
47
+ # lifecycle calls
48
+ #------------------------------------------------------------------
49
+
50
+ #
51
+ # call-seq:
52
+ # cmd.before
53
+ #
54
+ # called to allow the command to setup anything post initialization it
55
+ # needs to be able to +run+.
56
+ #
57
+ def before() nil ; end
58
+
59
+ #
60
+ # call-seq:
61
+ # cmd.run
62
+ #
63
+ # Yeah, this is where the work should be done.
64
+ #
65
+ def run()
66
+ raise NotImplementedError, "The #run method must be implemented"
67
+ end
68
+
69
+ #
70
+ # call-seq:
71
+ # cmd.after
72
+ #
73
+ # called no matter what, after the execution of run() or error() if there was
74
+ # an exception. It is here to allow the cmd to do cleanup work.
75
+ #
76
+ def after() nil ; end
77
+
78
+ #
79
+ # call-seq:
80
+ # cmd.error( exception )
81
+ #
82
+ # called if there is an exception during the before() or after() calls. It
83
+ # is passed in the exception that was raised.
84
+ #
85
+ def error(exception) nil ; end
86
+
87
+ #------------------------------------------------------------------
88
+ # registration
89
+ #------------------------------------------------------------------
90
+ class CommandNotFoundError < ::TyrantManager::Error ; end
91
+ class << Command
92
+ def inherited( klass )
93
+ return unless klass.instance_of? Class
94
+ self.list << klass
95
+ end
96
+
97
+ def list
98
+ unless defined? @list
99
+ @list = Set.new
100
+ end
101
+ return @list
102
+ end
103
+
104
+ def find( command )
105
+ klass = list.find { |klass| klass.command_name == command }
106
+ return klass if klass
107
+ names = list.collect { |c| c.command_name }
108
+ raise CommandNotFoundError, "No command for '#{command}' was found. Known commands : #{names.join(",")}"
109
+ end
110
+ end
111
+ end
112
+ end
113
+ require 'tyrant_manager/commands/create_instance'
114
+ require 'tyrant_manager/commands/start'
115
+ require 'tyrant_manager/commands/stop'
116
+ require 'tyrant_manager/commands/status'
117
+ require 'tyrant_manager/commands/stats'
118
+ require 'tyrant_manager/commands/list'
119
+ require 'tyrant_manager/commands/replication_status'