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