tyrantmanager 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
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