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.
- 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
|