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