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