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.
data/gemspec.rb ADDED
@@ -0,0 +1,51 @@
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("rake", ">= 0.8.1")
24
+ spec.add_dependency( "loquacious", "~> 1.3.0")
25
+ spec.add_dependency( "rufus-tokyo", "~> 1.0.0")
26
+
27
+ spec.add_development_dependency("configuration", ">= 0.0.5")
28
+ spec.add_development_dependency( "rake", "~> 0.8.3")
29
+
30
+ if ext_conf = Configuration.for_if_exist?("extension") then
31
+ spec.extensions << ext_conf.configs
32
+ spec.extensions.flatten!
33
+ end
34
+
35
+ if rdoc = Configuration.for_if_exist?('rdoc') then
36
+ spec.has_rdoc = true
37
+ spec.extra_rdoc_files = pkg.files.rdoc
38
+ spec.rdoc_options = rdoc.options + [ "--main" , rdoc.main_page ]
39
+ else
40
+ spec.has_rdoc = false
41
+ end
42
+
43
+ if test = Configuration.for_if_exist?('testing') then
44
+ spec.test_files = test.files
45
+ end
46
+
47
+ if rf = Configuration.for_if_exist?('rubyforge') then
48
+ spec.rubyforge_project = rf.project
49
+ end
50
+
51
+ end
@@ -0,0 +1,145 @@
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
+
74
+ mode('status') {
75
+ description "Check the running status of all the tyrants listed"
76
+ mixin :option_home
77
+ mixin :option_log_level
78
+
79
+ mixin :argument_instances
80
+
81
+ run { Cli.run_command_with_params( 'status', params ) }
82
+ }
83
+
84
+ mode( 'stats' ) {
85
+ description "Dump the database statistics of each of the tyrants listed"
86
+ mixin :option_home
87
+ mixin :option_log_level
88
+
89
+ mixin :argument_instances
90
+
91
+ run { Cli.run_command_with_params( 'stats', params ) }
92
+ }
93
+
94
+
95
+ mode('list') {
96
+ description "list the instances and their home directories"
97
+ mixin :option_home
98
+ mixin :option_log_level
99
+ mixin :argument_instances
100
+ run { Cli.run_command_with_params( 'list', params ) }
101
+ }
102
+
103
+ #--- Mixins ---
104
+ mixin :option_home do
105
+ option( :home ) do
106
+ description "The home directory of the tyrant manager"
107
+ argument :required
108
+ validate { |v| ::File.directory?( v ) }
109
+ default TyrantManager.default_or_home_directory
110
+ end
111
+ end
112
+
113
+ mixin :option_log_level do
114
+ option('log-level') do
115
+ description "The verbosity of logging, one of [ #{::Logging::LNAMES.map {|l| l.downcase}.join(", ")} ]"
116
+ argument :required
117
+ validate { |l| %w[ debug info warn error fatal off ].include?( l.downcase ) }
118
+ end
119
+ end
120
+
121
+ mixin :argument_instances do
122
+ argument('instances') do
123
+ description "A comman separated list of instance names the tyrant manager knows about"
124
+ argument :required
125
+ cast :list
126
+ default 'all'
127
+ end
128
+ end
129
+ }
130
+
131
+ #
132
+ # Convert the Parameters::List that exists as the parameters from Main
133
+ #
134
+ def Cli.params_to_hash( params )
135
+ (hash = params.to_hash ).keys.each { |key| hash[key] = hash[key].value }
136
+ return hash
137
+ end
138
+
139
+ def Cli.run_command_with_params( command, params )
140
+ phash = Cli.params_to_hash( params )
141
+ TyrantManager::Log.init( phash )
142
+ tm = TyrantManager.new( phash.delete('home') )
143
+ tm.runner_for( phash ).run( command )
144
+ end
145
+ end
@@ -0,0 +1,118 @@
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'
@@ -0,0 +1,27 @@
1
+ require 'pathname'
2
+ require 'tyrant_manager/command'
3
+ class TyrantManager
4
+ module Commands
5
+ #
6
+ # Create a new Tyrant instance
7
+ #
8
+ class CreateInstance < Command
9
+ def self.command_name
10
+ 'create-instance'
11
+ end
12
+
13
+ def run
14
+ path = Pathname.new( options['instance-home'] )
15
+ unless path.absolute? then
16
+ path = Pathname.new( manager.instances_path ) + path
17
+ end
18
+
19
+ unless path.exist? then
20
+ logger.info "Creating instance directory #{path}"
21
+ TyrantManager::TyrantInstance.setup( path.to_s )
22
+ end
23
+ tt = TyrantManager::TyrantInstance.new( path.to_s )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+ require 'tyrant_manager/command'
3
+ class TyrantManager
4
+ module Commands
5
+ #
6
+ # List all known instances that the manager knows about
7
+ #
8
+ class List < Command
9
+ def run
10
+ manager.each_instance do |instance|
11
+ ilist = options['instances']
12
+ if ilist == %w[ all ] or ilist.include?( instance.name ) then
13
+ parts = []
14
+ parts << ("%20s" % instance.name)
15
+ parts << "port #{instance.configuration.port}"
16
+ parts << instance.home_dir
17
+
18
+ if instance.configuration.master_server then
19
+ parts << "server id #{"%2d" % instance.configuration.server_id}"
20
+ parts << "replicating from #{instance.configuration.master_server}:#{instance.configuration.master_port}"
21
+ end
22
+ puts parts.join(" : ")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ require 'tyrant_manager/command'
2
+ class TyrantManager
3
+ module Commands
4
+ #
5
+ # Start one ore more instances
6
+ #
7
+ class Start < Command
8
+ def run
9
+ manager.each_instance do |instance|
10
+ ilist = options['instances']
11
+ if ilist == %w[ all ] or ilist.include?( instance.name ) then
12
+ parts = [ "Starting #{instance.name}" ]
13
+ parts << instance.start_command
14
+
15
+ if options['dry-run'] then
16
+ parts << "(dry-run)"
17
+ logger.info parts.join(" : ")
18
+
19
+ elsif not instance.running? then
20
+ logger.info parts.join(" : ")
21
+ Dir.chdir( instance.home_dir ) do
22
+ instance.start
23
+ end
24
+ else
25
+ logger.info "Instance #{instance.name} already running"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ require 'tyrant_manager/command'
2
+ class TyrantManager
3
+ module Commands
4
+ #
5
+ # List the stats about one or more instances
6
+ #
7
+ class Stats < Command
8
+ def run
9
+ manager.each_instance do |instance|
10
+ ilist = options['instances']
11
+ if ilist == %w[ all ] or ilist.include?( instance.name ) then
12
+ puts "Instance #{instance.name} at #{instance.home_dir}"
13
+ conn = instance.connection
14
+ stats = conn.stat
15
+ stats.keys.sort.each do |k|
16
+ lhs = k.ljust(10, ".")
17
+ puts " #{lhs} #{stats[k]}"
18
+ end
19
+ puts
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'tyrant_manager/command'
2
+ class TyrantManager
3
+ module Commands
4
+ #
5
+ # Report the status of one ore more tyrant intances
6
+ #
7
+ class Status < Command
8
+ def run
9
+ manager.each_instance do |instance|
10
+ ilist = options['instances']
11
+ if ilist == %w[ all ] or ilist.include?( instance.name ) then
12
+ if instance.running? then
13
+ logger.info "#{instance.name} is running as pid #{instance.pid}"
14
+ else
15
+ logger.info "#{instance.name} is not running, or its pid file is gone"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ require 'tyrant_manager/command'
2
+ class TyrantManager
3
+ module Commands
4
+ #
5
+ # Stop one or more tyrant instances
6
+ #
7
+ class Stop < Command
8
+ def run
9
+ manager.each_instance do |instance|
10
+ ilist = options['instances']
11
+ if ilist == %w[ all ] or ilist.include?( instance.name ) then
12
+ if File.exist?( instance.pid_file ) then
13
+ logger.info "Stopping #{instance.name} : pid #{instance.pid}"
14
+ instance.stop
15
+ else
16
+ logger.info "Stopping #{instance.name} : no pid file, nothing to do"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,91 @@
1
+ require 'logging'
2
+ require 'tyrant_manager'
3
+
4
+ class TyrantManager
5
+ ::Logging::Logger[self].level = :info
6
+
7
+ def self.logger
8
+ ::Logging::Logger[self]
9
+ end
10
+
11
+ module Log
12
+ def self.init( options = {} )
13
+ appender = Logging.appenders.stderr
14
+ appender.layout = self.console_layout
15
+ if options['log-file'] then
16
+ appender = ::Logging::Appenders::File.new(
17
+ 'tyrant_manager',
18
+ :filename => options['log-file'],
19
+ :layout => self.layout
20
+ )
21
+ end
22
+
23
+ TyrantManager.logger.add_appenders( appender )
24
+ self.level = options['log-level'] || :info
25
+ end
26
+
27
+ def self.logger
28
+ TyrantManager.logger
29
+ end
30
+
31
+ def self.default_level
32
+ :info
33
+ end
34
+
35
+ def self.console
36
+ Logging.appenders.stderr.level
37
+ end
38
+
39
+ def self.console=( level )
40
+ Logging.appenders.stderr.level = level
41
+ end
42
+
43
+ def self.level
44
+ ::Logging::Logger[TyrantManager].level
45
+ end
46
+
47
+ def self.level=( l )
48
+ ::Logging::Logger[TyrantManager].level = l
49
+ end
50
+
51
+ def self.layout
52
+ @layout ||= Logging::Layouts::Pattern.new(
53
+ :pattern => "[%d] %5l %6p %c : %m\n",
54
+ :date_pattern => "%Y-%m-%d %H:%M:%S"
55
+ )
56
+ end
57
+
58
+ def self.console_layout
59
+ @layout ||= Logging::Layouts::Pattern.new(
60
+ :pattern => "%d %5l : %m\n",
61
+ :date_pattern => "%H:%M:%S"
62
+ )
63
+ end
64
+
65
+ #
66
+ # Turn off the console logging
67
+ #
68
+ def self.silent!
69
+ logger.level = :off
70
+ end
71
+
72
+ #
73
+ # Resume logging
74
+ #
75
+ def self.resume
76
+ logger.level = self.default_level
77
+ end
78
+
79
+ #
80
+ # Turn off logging for the execution of a block
81
+ #
82
+ def self.silent( &block )
83
+ begin
84
+ silent!
85
+ block.call
86
+ ensure
87
+ resume
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,77 @@
1
+ require 'rbconfig'
2
+ class TyrantManager
3
+ module Paths
4
+ # The installation directory of the project is considered to be the parent directory
5
+ # of the 'lib' directory.
6
+ #
7
+ def install_dir
8
+ @install_dir ||= (
9
+ path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
10
+ lib_index = path_parts.rindex("lib")
11
+ path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
12
+ )
13
+ end
14
+
15
+ def install_path( sub, *args )
16
+ sub_path( install_dir, sub, *args )
17
+ end
18
+
19
+ def bin_path( *args )
20
+ install_path( 'bin', *args )
21
+ end
22
+
23
+ def lib_path( *args )
24
+ install_path( "lib", *args )
25
+ end
26
+
27
+ def data_path( *args )
28
+ install_path( "data", *args )
29
+ end
30
+
31
+ def spec_path( *args )
32
+ install_path( "spec", *args )
33
+ end
34
+
35
+ # The home dir is the home directory of the project while it is running
36
+ # by default, this the same as the install_dir. But if this value is set
37
+ # then it affects other paths
38
+ def home_dir
39
+ @home_dir ||= install_dir
40
+ end
41
+
42
+ def home_dir=( other )
43
+ @home_dir = File.expand_path( other )
44
+ end
45
+
46
+ def home_path( sub, *args )
47
+ sub_path( home_dir, sub, *args )
48
+ end
49
+
50
+ def instances_path( *args )
51
+ home_path( "instances", *args )
52
+ end
53
+
54
+ def log_path( *args )
55
+ home_path( "log", *args )
56
+ end
57
+
58
+ def tmp_path( *args )
59
+ home_path( "tmp", *args )
60
+ end
61
+
62
+ def sub_path( parent, sub, *args )
63
+ sp = ::File.join( parent, sub ) + File::SEPARATOR
64
+ sp = ::File.join( sp, *args ) if args
65
+ end
66
+
67
+ extend self
68
+ end
69
+ extend Paths
70
+
71
+ # set the default home_dir for the manager to be the lib/tyrant in the local
72
+ # state dir directory. Instances are below them.
73
+ self.home_dir = File.join( Config::CONFIG['localstatedir'], 'lib', 'tyrant' )
74
+ def self.default_instances_dir
75
+ self.home_path( "instances" )
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+ require 'tyrant_manager/command'
2
+
3
+ class TyrantManager
4
+ class Runner
5
+
6
+ attr_reader :options
7
+ attr_reader :manager
8
+
9
+ def initialize( manager, opts = {} )
10
+ @manager = manager
11
+ @options = opts
12
+ end
13
+
14
+ def logger
15
+ ::Logging::Logger[self]
16
+ end
17
+
18
+ def run( command_name )
19
+ cmd = Command.find( command_name ).new( self.manager, self.options )
20
+ begin
21
+ cmd.before
22
+ cmd.run
23
+ rescue => e
24
+ logger.error "while running #{command_name} : #{e.message}"
25
+ e.backtrace.each do |l|
26
+ logger.warn l.strip
27
+ end
28
+ ensure
29
+ cmd.after
30
+ end
31
+ end
32
+ end
33
+ end