jackowayed-tyrantmanager 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +34 -0
- data/LICENSE +13 -0
- data/README.rdoc +277 -0
- data/bin/tyrantmanager +10 -0
- data/data/config.rb +94 -0
- data/data/default_instance_config.rb +244 -0
- data/gemspec.rb +53 -0
- data/lib/tyrant_manager.rb +237 -0
- data/lib/tyrant_manager/cli.rb +152 -0
- data/lib/tyrant_manager/command.rb +119 -0
- data/lib/tyrant_manager/commands/create_instance.rb +27 -0
- data/lib/tyrant_manager/commands/list.rb +28 -0
- data/lib/tyrant_manager/commands/replication_status.rb +92 -0
- data/lib/tyrant_manager/commands/start.rb +32 -0
- data/lib/tyrant_manager/commands/stats.rb +24 -0
- data/lib/tyrant_manager/commands/status.rb +26 -0
- data/lib/tyrant_manager/commands/stop.rb +23 -0
- data/lib/tyrant_manager/log.rb +91 -0
- data/lib/tyrant_manager/paths.rb +77 -0
- data/lib/tyrant_manager/runner.rb +33 -0
- data/lib/tyrant_manager/tyrant_instance.rb +380 -0
- data/lib/tyrant_manager/version.rb +27 -0
- data/lib/tyrantmanager.rb +2 -0
- data/spec/command_spec.rb +37 -0
- data/spec/paths_spec.rb +57 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/tyrant_instance_spec.rb +106 -0
- data/spec/tyrant_manager_spec.rb +69 -0
- data/spec/version_spec.rb +16 -0
- data/tasks/announce.rake +43 -0
- data/tasks/config.rb +99 -0
- data/tasks/distribution.rake +38 -0
- data/tasks/documentation.rake +32 -0
- data/tasks/rspec.rake +29 -0
- data/tasks/rubyforge.rake +51 -0
- data/tasks/utils.rb +80 -0
- metadata +172 -0
@@ -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,92 @@
|
|
1
|
+
require 'tyrant_manager/command'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
class TyrantManager
|
5
|
+
module Commands
|
6
|
+
#
|
7
|
+
# Report on the replication status of the server(s)
|
8
|
+
#
|
9
|
+
class ReplicationStatus< Command
|
10
|
+
def self.command_name
|
11
|
+
'replication-status'
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
manager.each_instance do |instance|
|
16
|
+
ilist = options['instances']
|
17
|
+
if ilist == %w[ all ] or ilist.include?( instance.name ) then
|
18
|
+
if instance.running? and instance.is_slave? then
|
19
|
+
s_stat = instance.stat
|
20
|
+
logger.info "#{instance.name} is replicating from #{s_stat['mhost']}:#{s_stat['mport']}"
|
21
|
+
if m_conn = validate_master_connection( instance ) then
|
22
|
+
if validate_master_master( instance.connection, m_conn ) then
|
23
|
+
m_stat = m_conn.stat
|
24
|
+
m_name = "#{s_stat['mhost']}:#{s_stat['mport']}"
|
25
|
+
|
26
|
+
primary, failover = instance.connection, m_conn
|
27
|
+
|
28
|
+
if m_stat['delay'] > s_stat['delay'] then
|
29
|
+
primary, failover = m_conn, instance.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
p_stat = primary.stat
|
33
|
+
p_name = "#{ip_of( primary.host )}:#{primary.port}"
|
34
|
+
|
35
|
+
f_stat = failover.stat
|
36
|
+
f_name = "#{ip_of( failover.host )}:#{failover.port}"
|
37
|
+
|
38
|
+
n_width = [ p_name.length, f_name.length ].max
|
39
|
+
|
40
|
+
logger.info " Primary master : #{p_name} -> #{p_stat['rnum']} records, primary since #{(Time.now - ( Float(primary.stat['delay']))).strftime("%Y-%m-%d %H:%M:%S")}"
|
41
|
+
logger.info " Failover master : #{f_name} -> #{f_stat['rnum']} records, last replicated #{failover.stat['delay']} seconds ago"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
logger.info ""
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validate_master_master( slave, master )
|
53
|
+
m_stat = master.stat
|
54
|
+
s_stat = slave.stat
|
55
|
+
|
56
|
+
if ( m_stat['mhost'] and m_stat['mport'] ) then
|
57
|
+
logger.info " #{s_stat['mhost']}:#{s_stat['mport']} is replicating from #{m_stat['mhost']}:#{m_stat['mport']}"
|
58
|
+
mm_ip = ip_of( m_stat['mhost'] )
|
59
|
+
s_ip = ip_of( slave.host )
|
60
|
+
if ( s_ip == mm_ip ) and ( slave.port == m_stat['mport'].to_i ) then
|
61
|
+
#logger.info " - this is a good master-master relationship"
|
62
|
+
return true
|
63
|
+
else
|
64
|
+
logger.error " Unsupported replication configuration!!!"
|
65
|
+
logger.error " (original hostnames) #{slave.host}:#{slave.port} -> #{(master.host)}:#{master.port} -> #{m_stat['mhost']}:#{m_stat['mport']}"
|
66
|
+
logger.error " (hostnames resolved) #{s_ip}:#{slave.port} -> #{ip_of(master.host)}:#{master.port} -> #{ip_of(m_stat['mhost'])}:#{m_stat['mport']}"
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ip_of( hostname )
|
73
|
+
hostname = Socket.gethostname if %w[ localhost 0.0.0.0 ].include?( hostname )
|
74
|
+
addr_info = Socket.getaddrinfo( hostname, 1978, "AF_INET" )
|
75
|
+
return addr_info.first[3]
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_master_connection( slave )
|
79
|
+
begin
|
80
|
+
m_conn = slave.master_connection
|
81
|
+
m_stat = m_conn.stat
|
82
|
+
return m_conn
|
83
|
+
rescue => e
|
84
|
+
logger.error e
|
85
|
+
s_stat = slave.stat
|
86
|
+
logger.error "Master server #{s_stat["mhost"]}:#{s_stat['mport']} appears to be down."
|
87
|
+
end
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
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,24 @@
|
|
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
|
+
stats = instance.stat
|
14
|
+
stats.keys.sort.each do |k|
|
15
|
+
lhs = k.ljust(10, ".")
|
16
|
+
puts " #{lhs} #{stats[k]}"
|
17
|
+
end
|
18
|
+
puts
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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 ProcessStatus < Command
|
8
|
+
def self.command_name
|
9
|
+
'process-status'
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
manager.each_instance do |instance|
|
14
|
+
ilist = options['instances']
|
15
|
+
if ilist == %w[ all ] or ilist.include?( instance.name ) then
|
16
|
+
if instance.running? then
|
17
|
+
logger.info "#{instance.name} is running as pid #{instance.pid}"
|
18
|
+
else
|
19
|
+
logger.info "#{instance.name} is not running, or its pid file is gone"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
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
|