reptile 0.0.6 → 0.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/Gemfile +4 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +22 -0
- data/README.rdoc +22 -35
- data/Rakefile +9 -24
- data/bin/replication_status +28 -26
- data/lib/reptile/databases.rb +8 -10
- data/lib/reptile/delta_monitor.rb +18 -28
- data/lib/reptile/heartbeat.rb +20 -27
- data/lib/reptile/log.rb +6 -0
- data/lib/reptile/replication_monitor.rb +50 -148
- data/lib/reptile/runner.rb +19 -21
- data/lib/reptile/version.rb +3 -0
- data/lib/reptile.rb +2 -2
- data/reptile.gemspec +31 -0
- data/{lib/reptile/dtd.sql → reptile_setup.sql} +3 -3
- metadata +67 -26
- data/History.txt +0 -4
- data/Manifest.txt +0 -20
- data/PostInstall.txt +0 -5
- data/VERSION +0 -1
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
reptile (0.1.0)
|
5
|
+
activerecord
|
6
|
+
mixlib-log (>= 1.2.0)
|
7
|
+
tlsmail (>= 0.0.1)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.0.4)
|
13
|
+
activesupport (= 3.0.4)
|
14
|
+
builder (~> 2.1.2)
|
15
|
+
i18n (~> 0.4)
|
16
|
+
activerecord (3.0.4)
|
17
|
+
activemodel (= 3.0.4)
|
18
|
+
activesupport (= 3.0.4)
|
19
|
+
arel (~> 2.0.2)
|
20
|
+
tzinfo (~> 0.3.23)
|
21
|
+
activesupport (3.0.4)
|
22
|
+
arel (2.0.8)
|
23
|
+
builder (2.1.2)
|
24
|
+
i18n (0.5.0)
|
25
|
+
mixlib-log (1.2.0)
|
26
|
+
tlsmail (0.0.1)
|
27
|
+
tzinfo (0.3.24)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
activerecord
|
34
|
+
bundler (>= 1.0.0)
|
35
|
+
mixlib-log (>= 1.2.0)
|
36
|
+
reptile!
|
37
|
+
tlsmail (>= 0.0.1)
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2010 Nick Stielau
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
= reptile
|
2
2
|
|
3
|
-
Reptile is an easy to use utility that will monitor your MySQL replication, so you can forget about it and focus on the good stuff. It provides a utility for generate replication reports
|
3
|
+
Reptile is an easy to use utility that will monitor your MySQL replication, so you can forget about it and focus on the good stuff. It provides a utility for generate replication reports.
|
4
|
+
|
5
|
+
== STATUS DISCLAIMER:
|
6
|
+
|
7
|
+
I'm not sure if anybody uses this. I PROBABLY WON'T fix a patch you submit. This, as it is, has been used successfully in production environments. It's a good idea, but might not work for your particular case, and I make no guarantee that it will. I certainly hope it does. Drop me a line, and I'll answer any questions you have. NCS 8/2010
|
8
|
+
|
9
|
+
The --diff check can cause problems on large tables, as it does a lot of SELECT COUNT(*) FROM..'s .
|
4
10
|
|
5
11
|
== REQUIREMENTS:
|
6
12
|
|
@@ -14,10 +20,14 @@ Put a config file at /etc/reptile.yml, /etc/reptile/reptile.yml, ./reptile.yml,
|
|
14
20
|
[nick@s34 ~]$ replication_status -h
|
15
21
|
Usage: replication_status [path_to_config_file]
|
16
22
|
-h, --help Displays this help info
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
--status Displays the slave status
|
24
|
+
--diff Checks the row count difference between master and slave
|
25
|
+
--report Prints a report
|
26
|
+
--heartbeat Checks the heartbeat timestamp difference between master and slave
|
27
|
+
--stop-slaves Stops all slaves
|
28
|
+
--start-slaves Starts all slaves
|
29
|
+
-l, --log-level [LEVEL] Specify log level (debug,info,warn,error,fatal)
|
30
|
+
|
21
31
|
[nick@s34 ~]$ replication_status -s
|
22
32
|
* a_database slave is up and running
|
23
33
|
* b_database slave is up and running
|
@@ -28,12 +38,14 @@ Put a config file at /etc/reptile.yml, /etc/reptile/reptile.yml, ./reptile.yml,
|
|
28
38
|
[nick@s34 ~]$ sudo crontab -l
|
29
39
|
Password:
|
30
40
|
MAILTO="user@example.com"
|
31
|
-
# Use the '-
|
32
|
-
*/15 * * * * replication_status -
|
41
|
+
# Use the '-l error' arg to only spit out errors, which will be mailed by crontab
|
42
|
+
*/15 * * * * replication_status -l error
|
43
|
+
# Or, specify certain checks
|
44
|
+
# */15 * * * * replication_status -l error --status
|
33
45
|
|
34
46
|
=== Setup SQL
|
35
47
|
|
36
|
-
Run this SQL to setup the DBs/perms for Reptile. Using different users, since each user requires significantly different permissions.
|
48
|
+
Run this SQL to setup the DBs/perms for Reptile. Using different users, since each user requires significantly different permissions.
|
37
49
|
|
38
50
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SUPER ON *.* TO 'repl'@"localhost" IDENTIFIED BY 'repl_user_pass';
|
39
51
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SUPER ON *.* TO 'repl'@"MONITOR_HOST_IP" IDENTIFIED BY 'repl_user_pass';
|
@@ -47,36 +59,11 @@ Run this SQL to setup the DBs/perms for Reptile. Using different users, since e
|
|
47
59
|
GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.* TO 'heartbeat_user'@"MONITOR_HOST_IP" IDENTIFIED BY 'heartbeat_user_pass';
|
48
60
|
|
49
61
|
CREATE TABLE replication_monitor.heartbeats (
|
50
|
-
unix_time INTEGER NOT NULL,
|
51
|
-
db_time TIMESTAMP NOT NULL,
|
62
|
+
unix_time INTEGER NOT NULL,
|
63
|
+
db_time TIMESTAMP NOT NULL,
|
52
64
|
INDEX time_idx(unix_time)
|
53
65
|
)
|
54
66
|
|
55
67
|
== INSTALL:
|
56
68
|
|
57
69
|
sudo gem install reptile
|
58
|
-
|
59
|
-
== LICENSE:
|
60
|
-
|
61
|
-
(The MIT License)
|
62
|
-
|
63
|
-
Copyright (c) 2009 Nick Stielau
|
64
|
-
|
65
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
66
|
-
a copy of this software and associated documentation files (the
|
67
|
-
'Software'), to deal in the Software without restriction, including
|
68
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
69
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
70
|
-
permit persons to whom the Software is furnished to do so, subject to
|
71
|
-
the following conditions:
|
72
|
-
|
73
|
-
The above copyright notice and this permission notice shall be
|
74
|
-
included in all copies or substantial portions of the Software.
|
75
|
-
|
76
|
-
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
77
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
78
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
79
|
-
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
80
|
-
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
81
|
-
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
82
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,23 +1,11 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require File.dirname(__FILE__) + '/lib/reptile'
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
gem.description = %Q{Cold blooded mysql replication monitoring.}
|
11
|
-
gem.email = "nick.stielau@gmail.com"
|
12
|
-
gem.homepage = "http://reptile.rubyforge.org/"
|
13
|
-
gem.authors = ["Nick Stielau"]
|
14
|
-
gem.add_runtime_dependency 'tlsmail', '>= 0'
|
15
|
-
gem.add_runtime_dependency 'activerecord', '>= 0'
|
16
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
-
end
|
18
|
-
Jeweler::GemcutterTasks.new
|
19
|
-
rescue LoadError
|
20
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
6
|
+
test.libs << 'lib' << 'test'
|
7
|
+
test.pattern = 'test/**/test_*.rb'
|
8
|
+
test.verbose = true
|
21
9
|
end
|
22
10
|
|
23
11
|
desc "Upload current documentation to Rubyforge"
|
@@ -31,8 +19,5 @@ task :upload_site do
|
|
31
19
|
sh "scp -r webgen_site/* nstielau@rubyforge.org:/var/www/gforge-projects/reptile/"
|
32
20
|
end
|
33
21
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
# TODO - want other tests/tasks run by default? Add them to the list
|
38
|
-
# task :default => [:spec, :features]
|
22
|
+
task :test => :check_dependencies
|
23
|
+
task :default => :test
|
data/bin/replication_status
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'optparse'
|
4
|
+
|
5
|
+
require 'rubygems'
|
5
6
|
require 'reptile'
|
6
7
|
|
8
|
+
Reptile::Log.level = :info
|
9
|
+
Mixlib::Log::Formatter.show_time = false
|
10
|
+
|
7
11
|
commands = []
|
8
12
|
|
9
13
|
OptionParser.new do |opts|
|
@@ -13,33 +17,35 @@ OptionParser.new do |opts|
|
|
13
17
|
puts opts
|
14
18
|
exit 0
|
15
19
|
end
|
16
|
-
|
17
|
-
opts.on("
|
20
|
+
|
21
|
+
opts.on("--status", "Displays the slave status") do
|
18
22
|
commands << 'check_slaves'
|
19
23
|
end
|
20
|
-
|
21
|
-
opts.on("
|
24
|
+
|
25
|
+
opts.on("--diff", "Checks the row count difference between master and slave") do
|
22
26
|
commands << 'diff_tables'
|
23
27
|
end
|
24
|
-
|
25
|
-
opts.on("
|
28
|
+
|
29
|
+
opts.on("--report", "Prints a report") do
|
26
30
|
commands << 'report'
|
27
31
|
end
|
28
|
-
|
29
|
-
opts.on("
|
32
|
+
|
33
|
+
opts.on("--heartbeat", "Checks the heartbeat timestamp difference between master and slave") do
|
30
34
|
commands << 'heartbeat'
|
31
35
|
end
|
32
|
-
|
33
|
-
opts.on("-
|
36
|
+
|
37
|
+
opts.on("--stop-slaves", "Stops all slaves") do
|
34
38
|
commands << 'stop_slaves'
|
35
39
|
end
|
36
|
-
|
37
|
-
opts.on("-
|
40
|
+
|
41
|
+
opts.on("--start-slaves", "Starts all slaves") do
|
38
42
|
commands << 'start_slaves'
|
39
43
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
|
45
|
+
log_levels = [:debug, :info, :warn, :error, :fatal]
|
46
|
+
opts.on("-l", "--log-level [LEVEL]", log_levels,
|
47
|
+
"Specify log level (#{log_levels.join(',')})") do |t|
|
48
|
+
Reptile::Log.level = t.to_sym
|
43
49
|
end
|
44
50
|
|
45
51
|
begin
|
@@ -52,24 +58,20 @@ OptionParser.new do |opts|
|
|
52
58
|
end
|
53
59
|
|
54
60
|
if !ARGV.empty? && ARGV.length > 1
|
55
|
-
abort "Too many arguments; please specify only the
|
61
|
+
abort "Too many arguments; please specify only the config file."
|
56
62
|
end
|
57
63
|
|
58
|
-
config_file_name = "reptile.yml"
|
59
|
-
config_file_locations = ["/etc/#{config_file_name}", "/etc/reptile/#{config_file_name}", "./#{config_file_name}"]
|
60
64
|
config_location_param = ARGV.first
|
61
65
|
|
62
66
|
config_file = nil
|
63
67
|
if config_location_param
|
64
|
-
if File.
|
65
|
-
config_file = File.join(config_location_param, config_file_name)
|
66
|
-
elsif File.exist?(config_location_param) && !File.directory?(config_location_param)
|
68
|
+
if File.exist?(config_location_param) && !File.directory?(config_location_param)
|
67
69
|
config_file = config_location_param
|
68
70
|
else
|
69
|
-
abort "Please specify the
|
71
|
+
abort "Please specify the config file."
|
70
72
|
end
|
71
73
|
else
|
72
|
-
|
74
|
+
["/etc/reptile.yml", "/etc/reptile/reptile.yml", "./reptile.yml"].each do |f|
|
73
75
|
if File.exist?(f)
|
74
76
|
config_file = f
|
75
77
|
break
|
@@ -80,11 +82,11 @@ else
|
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
|
-
|
85
|
+
Reptile::Log.debug "Loading config from #{config_file}"
|
84
86
|
Reptile::ReplicationMonitor.load_config_file(config_file)
|
85
87
|
|
86
88
|
if (commands.include?('start_slaves') || commands.include?('stop_slaves'))
|
87
|
-
Reptile::Runner.send(commands[0])
|
89
|
+
Reptile::Runner.send(commands[0])
|
88
90
|
else
|
89
91
|
(commands.empty? ? ['check_slaves', 'heartbeat', 'diff_tables'] : commands).each do |command|
|
90
92
|
Reptile::ReplicationMonitor.send(command)
|
data/lib/reptile/databases.rb
CHANGED
@@ -3,11 +3,11 @@ module Reptile
|
|
3
3
|
# for the master and slave of each particular database.
|
4
4
|
class Databases
|
5
5
|
attr :databases
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(databases)
|
8
8
|
@databases = databases
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# returns an array of the master names
|
12
12
|
def masters
|
13
13
|
@master_configs ||= get_masters
|
@@ -17,7 +17,7 @@ module Reptile
|
|
17
17
|
def slaves
|
18
18
|
@slave_configs ||= get_slaves
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
private
|
22
22
|
|
23
23
|
def get_masters
|
@@ -26,7 +26,7 @@ private
|
|
26
26
|
masters.each_key{|name| masters[name] = masters[name]['master'] }
|
27
27
|
masters
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# TODO: make private
|
31
31
|
def get_slaves
|
32
32
|
dbs = databases.dup
|
@@ -34,17 +34,15 @@ private
|
|
34
34
|
dbs.each_key{|name| dbs[name] = dbs[name]['slave'] }
|
35
35
|
slaves = dbs
|
36
36
|
end
|
37
|
-
|
38
|
-
# Tries to establish a database connection, and returns that connection.
|
37
|
+
|
38
|
+
# Tries to establish a database connection, and returns that connection.
|
39
39
|
# Dumps configs on error
|
40
40
|
def self.connect(configs)
|
41
41
|
ActiveRecord::Base.establish_connection(configs)
|
42
42
|
ActiveRecord::Base.connection
|
43
43
|
rescue Exception => e
|
44
|
-
|
45
|
-
|
46
|
-
puts "****"
|
47
|
-
puts configs.inspect
|
44
|
+
Log.error "Error connecting to database: #{e}"
|
45
|
+
Log.error configs.inspect
|
48
46
|
exit 1
|
49
47
|
end
|
50
48
|
end
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
module Reptile
|
4
|
-
# This monitor compares the row counts for each table for each master and slave.
|
2
|
+
# This monitor compares the row counts for each table for each master and slave.
|
5
3
|
class DeltaMonitor
|
6
4
|
# Set the user settings for a user that has global SELECT privilidgess
|
7
5
|
def self.user=(user_settings)
|
@@ -13,20 +11,12 @@ module Reptile
|
|
13
11
|
raise "You need to specify a user!" if @user.nil?
|
14
12
|
@user
|
15
13
|
end
|
16
|
-
|
17
|
-
def self.logger=(new_logger)
|
18
|
-
@logger = new_logger
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.get_logger
|
22
|
-
@logger ||= Logger.new(STDOUT)
|
23
|
-
end
|
24
|
-
|
14
|
+
|
25
15
|
# Retrieve the active database connection. Nil of none exists.
|
26
16
|
def self.connection
|
27
17
|
ActiveRecord::Base.connection
|
28
18
|
end
|
29
|
-
|
19
|
+
|
30
20
|
# Compares the row counts for master tables and slave tables
|
31
21
|
# Returns a hash of TABLE_NAME => ROW COUNT DELTAs
|
32
22
|
def self.diff(db_name, master_configs, slave_configs)
|
@@ -37,36 +27,36 @@ module Reptile
|
|
37
27
|
master_counts = get_table_counts
|
38
28
|
|
39
29
|
deltas= {}
|
40
|
-
master_counts.each do |table, master_count|
|
30
|
+
master_counts.each do |table, master_count|
|
41
31
|
if slave_counts[table].nil?
|
42
|
-
|
32
|
+
Log.error "Table '#{table}' exists on master but not on slave."
|
43
33
|
next
|
44
|
-
end
|
34
|
+
end
|
45
35
|
delta = master_count.first.to_i - slave_counts[table].first.to_i
|
46
36
|
deltas[table] = delta
|
47
37
|
end
|
48
|
-
|
38
|
+
|
49
39
|
print_deltas(db_name, deltas, master_configs)
|
50
|
-
|
40
|
+
|
51
41
|
deltas
|
52
42
|
rescue Exception => e
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
43
|
+
Log.error "Error: Caught #{e}"
|
44
|
+
Log.error "DB Name: #{db_name}"
|
45
|
+
Log.error "Master Configs: #{master_configs.inspect}"
|
46
|
+
Log.error "Slave Configs: #{slave_configs.inspect}"
|
57
47
|
raise e
|
58
48
|
end
|
59
|
-
|
49
|
+
|
60
50
|
# Prints stats about the differences in number of rows between the master and slave
|
61
51
|
def self.print_deltas(db_name, deltas, configs)
|
62
52
|
non_zero_deltas = deltas.select{|table, delta| not delta.zero?}
|
63
53
|
if non_zero_deltas.size.zero?
|
64
|
-
|
54
|
+
Log.info "Replication counts A-OK for #{db_name} on #{configs['host']} @ #{Time.now}"
|
65
55
|
else
|
66
|
-
|
67
|
-
|
56
|
+
Log.info "Replication Row Count Deltas for #{db_name} on #{configs['host']} @ #{Time.now}"
|
57
|
+
Log.info "There #{non_zero_deltas.size > 1 ? 'are' : 'is'} #{non_zero_deltas.size} #{non_zero_deltas.size > 1 ? 'deltas' : 'delta'}"
|
68
58
|
non_zero_deltas.each do |table, delta|
|
69
|
-
|
59
|
+
Log.info " #{table} table: #{delta}" unless delta.zero?
|
70
60
|
end
|
71
61
|
end
|
72
62
|
end
|
@@ -78,7 +68,7 @@ module Reptile
|
|
78
68
|
connection.execute('SHOW TABLES').each { |row| tables << row }
|
79
69
|
tables
|
80
70
|
end
|
81
|
-
|
71
|
+
|
82
72
|
# Returns a hash of TABLE_NAME => # Rows for all tables in current db
|
83
73
|
def self.get_table_counts
|
84
74
|
tables = get_tables
|
data/lib/reptile/heartbeat.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
3
|
module Reptile
|
4
|
-
# MySQL DTD for setting up Heartbeats. Change HEARTBEAT_USER, HEARTBEAT_PASS, and MONITORING_BOX
|
5
|
-
# to ip of the monitoring server.
|
4
|
+
# MySQL DTD for setting up Heartbeats. Change HEARTBEAT_USER, HEARTBEAT_PASS, and MONITORING_BOX
|
5
|
+
# to ip of the monitoring server.
|
6
6
|
#
|
7
|
-
# GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.*
|
7
|
+
# GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.*
|
8
8
|
# TO 'HEARTBEAT_USER'@"localhost" IDENTIFIED BY 'HEARTBEAT_PASS'; GRANT SELECT, INSERT, UPDATE,
|
9
9
|
# ALTER ON replication_monitor.* TO 'HEARTBEAT_USER'@"MONITORING_BOX" IDENTIFIED BY 'HEARTBEAT_PASS';
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# CREATE DATABASE replication_monitor;
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# CREATE TABLE replication_monitor.heartbeats (
|
14
|
-
# unix_time INTEGER NOT NULL,
|
15
|
-
# db_time TIMESTAMP NOT NULL,
|
14
|
+
# unix_time INTEGER NOT NULL,
|
15
|
+
# db_time TIMESTAMP NOT NULL,
|
16
16
|
# INDEX time_idx(unix_time)
|
17
17
|
# )
|
18
18
|
#
|
@@ -23,59 +23,52 @@ module Reptile
|
|
23
23
|
def self.user=(default_configs)
|
24
24
|
@user = default_configs
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
# The default connection settings which override per-database settings.
|
28
28
|
def self.user
|
29
29
|
@user ||= {}
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def self.connect(configs)
|
33
33
|
Databases.connect(configs.merge(user).merge("database" => 'replication_monitor'))
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# Write a heartbeat.
|
37
37
|
# Thump thump.
|
38
38
|
def self.write(name, configs)
|
39
39
|
self.connect(configs)
|
40
40
|
heartbeat = Heartbeat.create(:unix_time => Time.now.to_i, :db_time => "NOW()")
|
41
|
-
|
41
|
+
Log.debug "Wrote heartbeat to #{name} at #{Time.at(heartbeat.unix_time)}"
|
42
42
|
end
|
43
43
|
|
44
44
|
# Read the most recent heartbeat and return the delay in seconds, or nil if no heartbeat are found.
|
45
45
|
def self.read(name, configs)
|
46
46
|
self.connect(configs)
|
47
|
-
|
47
|
+
|
48
48
|
current_time = Time.now
|
49
49
|
|
50
50
|
delay = nil
|
51
51
|
heartbeat = Heartbeat.find(:first, :order => 'db_time DESC')
|
52
|
-
|
52
|
+
|
53
53
|
# No heartbeats at all!
|
54
54
|
if heartbeat.nil?
|
55
|
-
|
55
|
+
Log.info "No heartbeats found on #{name} at #{Time.now}"
|
56
56
|
return nil;
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# Not sure why we have both, (one is easier to read?).
|
60
60
|
# Use one or the other to calculate delay...
|
61
61
|
delay = (Time.now - Time.at(heartbeat.unix_time)).round
|
62
62
|
#delay = (Time.now - heartbeat.db_time)
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
Log.debug "Read heartbeat from #{name} at #{Time.at(heartbeat.unix_time)}."
|
65
|
+
Log.info "The delay is #{strfdelay(delay)}"
|
66
|
+
|
66
67
|
delay
|
67
68
|
end
|
68
|
-
|
69
|
-
def self.logger=(new_logger)
|
70
|
-
@logger = new_logger
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.get_logger
|
74
|
-
@logger ||= Logger.new(STDOUT)
|
75
|
-
end
|
76
|
-
|
69
|
+
|
77
70
|
private
|
78
|
-
|
71
|
+
|
79
72
|
# Format the delay (in seconds) as a human-readable string.
|
80
73
|
def self.strfdelay(delay)
|
81
74
|
seconds = delay % 60
|
data/lib/reptile/log.rb
ADDED
@@ -1,166 +1,130 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
1
|
module Reptile
|
4
2
|
class ReplicationMonitor
|
5
|
-
|
3
|
+
|
6
4
|
# Attempts to load the replication.yml configuration file.
|
7
|
-
def self.load_config_file(databases_file)
|
5
|
+
def self.load_config_file(databases_file)
|
8
6
|
@databases_file = databases_file
|
9
7
|
yaml = YAML::load(File.read(@databases_file))
|
10
8
|
@configs = yaml.delete('config')
|
11
9
|
@users = Users.new(yaml.delete('users'))
|
12
10
|
@databases = Databases.new(yaml)
|
13
|
-
|
11
|
+
|
14
12
|
Heartbeat.user = users.heartbeat_user
|
15
13
|
Runner.user = users.replication_user
|
16
14
|
Status.user = users.replication_user
|
17
15
|
DeltaMonitor.user = users.ro_user
|
18
16
|
Runner.databases = databases
|
19
|
-
|
17
|
+
|
20
18
|
raise "Please specify a delay threshold 'delay_threshold_secs: 360'" if @configs['delay_threshold_secs'].nil?
|
21
19
|
raise "Please specify a row delta threshold 'row_difference_threshold: 10'" if @configs['row_difference_threshold'].nil?
|
22
|
-
|
23
20
|
rescue Errno::EACCES => e
|
24
|
-
|
21
|
+
Log.error "Unable to open config file: Permission Denied"
|
25
22
|
end
|
26
|
-
|
23
|
+
|
27
24
|
# Returns the configs from the replication.yml file
|
28
25
|
def self.configs
|
29
26
|
@configs
|
30
27
|
end
|
31
|
-
|
28
|
+
|
32
29
|
# Returns the databases from the yml file.
|
33
30
|
def self.databases
|
34
31
|
@databases
|
35
32
|
end
|
36
|
-
|
33
|
+
|
37
34
|
# Returns the +Users+ loaded from the replication.yml file
|
38
35
|
def self.users
|
39
36
|
@users
|
40
37
|
end
|
41
|
-
|
42
|
-
def self.errors
|
43
|
-
get_logger.sev_threshold = Logger::ERROR
|
44
|
-
check_slaves
|
45
|
-
heartbeat
|
46
|
-
diff_tables
|
47
|
-
end
|
48
|
-
|
38
|
+
|
49
39
|
def self.diff_tables
|
40
|
+
Log.info "Checking row counts."
|
50
41
|
unsynced_dbs = 0
|
51
|
-
|
52
|
-
|
42
|
+
|
53
43
|
databases.databases.each_pair do |name, roles|
|
54
44
|
master, slave = roles['master'], roles['slave']
|
55
45
|
deltas = DeltaMonitor.diff(name, master, slave)
|
56
|
-
|
57
|
-
egregious_deltas = deltas.select{|table, delta| delta > configs['row_difference_threshold'] }
|
46
|
+
|
47
|
+
egregious_deltas = deltas.select{|table, delta| delta > configs['row_difference_threshold'] }
|
58
48
|
if egregious_deltas.size > 0
|
59
|
-
|
49
|
+
log_replication_error :host => master["host"], :database => master["database"], :deltas => egregious_deltas, :noticed_at => Time.now
|
60
50
|
unsynced_dbs += 1
|
61
51
|
end
|
62
52
|
end
|
63
|
-
|
53
|
+
|
64
54
|
unsynced_dbs.zero?
|
65
55
|
end
|
66
|
-
|
56
|
+
|
67
57
|
def self.heartbeat
|
68
|
-
|
69
|
-
|
58
|
+
Log.info "Checking heartbeats."
|
70
59
|
databases.masters.each_pair do |name, configs|
|
71
60
|
Heartbeat.write(name, configs)
|
72
61
|
end
|
73
|
-
|
62
|
+
|
74
63
|
overdue_slaves = 0
|
75
|
-
|
64
|
+
|
76
65
|
databases.slaves.each_pair do |name, db_configs|
|
77
66
|
delay = Heartbeat.read(name, db_configs)
|
78
67
|
if delay.nil?
|
79
|
-
|
80
|
-
:database => configs[:database],
|
81
|
-
:general_error => "Error: No Heartbeats found.",
|
68
|
+
log_replication_error :host => name,
|
69
|
+
:database => configs[:database],
|
70
|
+
:general_error => "Error: No Heartbeats found.",
|
82
71
|
:noticed_at => Time.now
|
83
72
|
overdue_slaves += 1
|
84
73
|
elsif delay > configs['delay_threshold_secs']
|
85
|
-
|
86
|
-
:database => configs[:database],
|
87
|
-
:delay => Heartbeat.strfdelay(delay),
|
74
|
+
log_replication_error :host => name,
|
75
|
+
:database => configs[:database],
|
76
|
+
:delay => Heartbeat.strfdelay(delay),
|
88
77
|
:noticed_at => Time.now
|
89
78
|
overdue_slaves += 1
|
90
79
|
end
|
91
80
|
end
|
92
|
-
|
81
|
+
|
93
82
|
overdue_slaves.zero?
|
94
83
|
end
|
95
|
-
|
84
|
+
|
96
85
|
# Checks the status of each slave.
|
97
86
|
def self.check_slaves
|
87
|
+
Log.info "Checking slave status."
|
98
88
|
databases.slaves.each do |slave_name, slave_configs|
|
99
89
|
status = Status.check_slave_status(slave_name, slave_configs)
|
100
|
-
|
90
|
+
Log.info "'#{slave_name}' is '#{status}'"
|
101
91
|
if status != Status.const_get(:RUNNING)
|
102
|
-
|
103
|
-
:database => configs[:database],
|
104
|
-
:status_error => Status.get_error_message(status),
|
92
|
+
log_replication_error :host => slave_name,
|
93
|
+
:database => configs[:database],
|
94
|
+
:status_error => Status.get_error_message(status),
|
105
95
|
:noticed_at => Time.now
|
106
96
|
end
|
107
97
|
end
|
108
98
|
end
|
109
|
-
|
110
|
-
def self.
|
111
|
-
|
112
|
-
|
113
|
-
email.subject = "A replication error occured on #{options[:host]} at #{Time.now}"
|
114
|
-
email.body = ''
|
115
|
-
|
99
|
+
|
100
|
+
def self.log_replication_error(options)
|
101
|
+
Log.error = "A replication error occured on #{options[:host]} at #{Time.now}"
|
102
|
+
|
116
103
|
if options[:delay]
|
117
|
-
|
104
|
+
Log.error "There was a #{options[:delay]} second replication latency, which is greater than the allowed latency of #{configs['delay_threshold_secs']} seconds"
|
118
105
|
elsif options[:deltas]
|
119
|
-
|
120
|
-
options[:deltas].each do |table, delta|
|
121
|
-
|
106
|
+
Log.error "The following tables have master/slave row count difference greater than the allowed #{configs['row_difference_threshold']}"
|
107
|
+
options[:deltas].each do |table, delta|
|
108
|
+
Log.error " table '#{table}' was off by #{delta} rows"
|
122
109
|
end
|
123
110
|
elsif options[:status_error]
|
124
|
-
|
111
|
+
Log.error " MySQL Status message: #{options[:status_error]}"
|
125
112
|
elsif options[:general_error]
|
126
|
-
|
113
|
+
Log.error " Error: #{options[:general_error]}"
|
127
114
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
email.body += " Database: #{options[:database]}\n" unless options[:database].blank?
|
132
|
-
|
133
|
-
# Print out email body to STDOUT
|
134
|
-
get_logger.error email.body
|
135
|
-
|
136
|
-
send_email(email)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Gets the 'email_to' value from the 'configs' section of the replication.yml file
|
140
|
-
def self.get_recipients
|
141
|
-
configs['email_to']
|
142
|
-
end
|
143
|
-
|
144
|
-
# Gets the 'email_from' value from the 'configs' section of the replication.yml file
|
145
|
-
def self.get_sender
|
146
|
-
configs['email_from']
|
115
|
+
|
116
|
+
Log.error " Server: #{options[:host]}\n"
|
117
|
+
Log.error " Database: #{options[:database]}\n" unless options[:database].blank?
|
147
118
|
end
|
148
|
-
|
119
|
+
|
149
120
|
def self.report
|
150
|
-
|
151
|
-
|
152
|
-
email.sender = get_sender
|
153
|
-
raise "Please specify report recipients 'email_to: user@address.com'" if email.recipients.nil?
|
154
|
-
raise "Please specify report recipients 'email_from: user@address.com'" if email.sender.nil?
|
155
|
-
|
156
|
-
email.subject = "Daily Replication Report for #{Time.now.strftime('%D')}"
|
157
|
-
|
158
|
-
puts "Generating report email"
|
159
|
-
|
121
|
+
Log.info "Generating report"
|
122
|
+
|
160
123
|
old_stdout = $stdout
|
161
124
|
out = StringIO.new
|
162
125
|
$stdout = out
|
163
126
|
begin
|
127
|
+
puts "Daily Replication Report for #{Time.now.strftime('%D')}"
|
164
128
|
puts " Checking slave status"
|
165
129
|
puts
|
166
130
|
self.check_slaves
|
@@ -181,69 +145,7 @@ module Reptile
|
|
181
145
|
ensure
|
182
146
|
$stdout = old_stdout
|
183
147
|
end
|
184
|
-
|
185
|
-
|
186
|
-
puts "Sending report email"
|
187
|
-
|
188
|
-
send_email(email)
|
189
|
-
|
190
|
-
puts "Report sent to #{get_recipients}"
|
148
|
+
puts out.string
|
191
149
|
end
|
192
|
-
|
193
|
-
def self.send_exception_email(ex)
|
194
|
-
email = OpenStruct.new
|
195
|
-
email.recipients = get_recipients
|
196
|
-
email.sender = get_sender
|
197
|
-
email.subject = "An exception occured while checking replication at #{Time.now}"
|
198
|
-
email.body = 'Expception\n\n'
|
199
|
-
email.body += "#{ex.message}\n"
|
200
|
-
ex.backtrace.each do |line|
|
201
|
-
email.body += "#{line}\n"
|
202
|
-
end
|
203
|
-
|
204
|
-
send_email(email)
|
205
|
-
end
|
206
|
-
|
207
|
-
def self.send_email(email)
|
208
|
-
return unless configs['email_server'] && configs['email_port'] && configs['email_domain'] &&
|
209
|
-
configs['email_password'] && configs['email_auth_type']
|
210
|
-
|
211
|
-
# TODO: could do Net::SMTP.respond_to?(enable_tls) ? enable_TLS : puts "Install TLS gem to use SSL/TLS"
|
212
|
-
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
|
213
|
-
Net::SMTP.start(configs['email_server'],
|
214
|
-
configs['email_port'],
|
215
|
-
configs['email_domain'],
|
216
|
-
get_sender,
|
217
|
-
configs['email_password'],
|
218
|
-
configs['email_auth_type'].to_sym) do |smtp|
|
219
|
-
email.recipients.each do |email_addy|
|
220
|
-
hdr = "From: #{email.sender}\n"
|
221
|
-
hdr += "To: #{email_addy} <#{email_addy}>\n"
|
222
|
-
hdr += "Subject: #{email.subject}\n\n"
|
223
|
-
msg = hdr + email.body
|
224
|
-
puts "Sending to #{email_addy}"
|
225
|
-
smtp.send_message msg, email.sender, email_addy
|
226
|
-
end
|
227
|
-
end
|
228
|
-
# TODO: could try and recover
|
229
|
-
# rescue Net::SMTPAuthenticationError => e
|
230
|
-
# if e.message =~ /504 5.7.4 Unrecognized authentication type/
|
231
|
-
# puts "Attempting to load necesary files for TLS/SSL authentication"
|
232
|
-
# puts "Make sure openssl and the tlsmail gem are installed"
|
233
|
-
# require 'openssl'
|
234
|
-
# require 'rubygems'
|
235
|
-
# has_tlsmail_gem = require 'tlsmail'
|
236
|
-
# raise "Please install the 'tlsmail' gem" unless has_tlsmail_gem
|
237
|
-
# Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
|
238
|
-
# send_email(email)
|
239
|
-
# end
|
240
|
-
end
|
241
|
-
|
242
|
-
private
|
243
|
-
|
244
|
-
def self.get_logger
|
245
|
-
@@logger ||= Logger.new(STDOUT)
|
246
|
-
end
|
247
|
-
|
248
150
|
end
|
249
151
|
end
|
data/lib/reptile/runner.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Reptile
|
2
|
-
# The runner class is responsible for running command on each slave.
|
2
|
+
# The runner class is responsible for running command on each slave.
|
3
3
|
# The commands are run sequentially, no guaranteed order (though probably from yml file).
|
4
4
|
class Runner
|
5
5
|
# Set the user settings for a user that has REPLICATION SLAVE privilidgess
|
@@ -21,7 +21,7 @@ module Reptile
|
|
21
21
|
# The databases to run commands upon.
|
22
22
|
def self.databases
|
23
23
|
@databases
|
24
|
-
end
|
24
|
+
end
|
25
25
|
|
26
26
|
# Set the slaves to run command upon.
|
27
27
|
def self.slaves=(slaves)
|
@@ -42,18 +42,16 @@ module Reptile
|
|
42
42
|
raise "You need to specify the slaves to run against!" if @slaves.nil?
|
43
43
|
@slaves
|
44
44
|
end
|
45
|
-
|
46
|
-
|
47
|
-
# Tries to establish a database connection, and returns that connection.
|
45
|
+
|
46
|
+
|
47
|
+
# Tries to establish a database connection, and returns that connection.
|
48
48
|
# Dumps configs on error.
|
49
49
|
def self.connect(configs)
|
50
50
|
ActiveRecord::Base.establish_connection(configs)
|
51
51
|
ActiveRecord::Base.connection
|
52
52
|
rescue Exception => e
|
53
|
-
|
54
|
-
|
55
|
-
puts "****"
|
56
|
-
puts YAML::dump(configs)
|
53
|
+
Log.error "Error connecting to database: #{e}"
|
54
|
+
Log.error YAML::dump(configs)
|
57
55
|
exit 1
|
58
56
|
end
|
59
57
|
|
@@ -61,23 +59,23 @@ module Reptile
|
|
61
59
|
# Takes an optional set of connection paramters to override defaults.
|
62
60
|
def self.execute_on_slaves(cmd, configs={})
|
63
61
|
slaves.each do |name, slave_configs|
|
64
|
-
|
65
|
-
|
62
|
+
Log.info "Executing #{cmd} on #{name}"
|
63
|
+
Log.info slave_configs.inspect
|
66
64
|
connection = connect(slave_configs.merge(user).merge(configs))
|
67
65
|
connection.execute(cmd)
|
68
|
-
end
|
66
|
+
end
|
69
67
|
end
|
70
68
|
|
71
69
|
# Execute STOP SLAVE on all slaves;
|
72
70
|
def self.stop_slaves
|
73
71
|
execute_on_slaves("STOP SLAVE;")
|
74
72
|
end
|
75
|
-
|
73
|
+
|
76
74
|
# Execute START SLAVE on all slaves.
|
77
75
|
def self.start_slaves
|
78
76
|
execute_on_slaves("START SLAVE;")
|
79
|
-
end
|
80
|
-
|
77
|
+
end
|
78
|
+
|
81
79
|
# Creates users with specific permissions on the different mysql servers, both masters and slaves.
|
82
80
|
# Prompts for username and password of an account that has GRANT priviledges.
|
83
81
|
def self.setup
|
@@ -90,21 +88,21 @@ module Reptile
|
|
90
88
|
execute_on_slaves("GRANT select ON *.* TO #{ro_user}@???? INDENTIFIED BY #{ro_password}", grant_user_configs)
|
91
89
|
execute_on_slaves("GRANT select ON *.* TO #{repl_user}@???? INDENTIFIED BY #{repl_password}", grant_user_configs)
|
92
90
|
end
|
93
|
-
|
91
|
+
|
94
92
|
def self.setup_heartbeat
|
95
93
|
# MySQL DTD for setting up Heartbeats. Change HEARTBEAT_USER, HEARTBEAT_PASS, and MONITORING_BOX to ip of the monitoring server.
|
96
94
|
# GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.* TO 'HEARTBEAT_USER'@"localhost" IDENTIFIED BY 'HEARTBEAT_PASS';
|
97
95
|
# GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.* TO 'HEARTBEAT_USER'@"MONITORING_BOX" IDENTIFIED BY 'HEARTBEAT_PASS';
|
98
|
-
#
|
96
|
+
#
|
99
97
|
# CREATE DATABASE replication_monitor;
|
100
|
-
#
|
98
|
+
#
|
101
99
|
# CREATE TABLE replication_monitor.heartbeats (
|
102
|
-
# unix_time INTEGER NOT NULL,
|
103
|
-
# db_time TIMESTAMP NOT NULL,
|
100
|
+
# unix_time INTEGER NOT NULL,
|
101
|
+
# db_time TIMESTAMP NOT NULL,
|
104
102
|
# INDEX time_idx(unix_time)
|
105
103
|
# )
|
106
104
|
end
|
107
|
-
|
105
|
+
|
108
106
|
def self.test_connections
|
109
107
|
databases.each do |db|
|
110
108
|
databases.roles.each do |role|
|
data/lib/reptile.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
2
|
|
4
3
|
require 'ostruct'
|
5
4
|
require 'openssl'
|
@@ -7,6 +6,7 @@ require 'rubygems'
|
|
7
6
|
require 'tlsmail'
|
8
7
|
require 'net/smtp'
|
9
8
|
|
9
|
+
require 'reptile/log'
|
10
10
|
require 'reptile/heartbeat'
|
11
11
|
require 'reptile/delta_monitor'
|
12
12
|
require 'reptile/replication_monitor'
|
data/reptile.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/reptile/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "reptile"
|
6
|
+
s.version = Reptile::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
|
9
|
+
s.authors = ["nick.stielau@gmail.com"]
|
10
|
+
s.date = "2010-01-05"
|
11
|
+
s.default_executable = "replication_status"
|
12
|
+
s.description = "Cold blooded mysql replication monitoring."
|
13
|
+
s.email = "nick.stielau@gmail.com"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
17
|
+
s.require_path = 'lib'
|
18
|
+
|
19
|
+
s.homepage = %q{http://reptile.rubyforge.org/}
|
20
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
s.required_rubygems_version = ">= 1.3.6"
|
23
|
+
|
24
|
+
s.summary = "Cold blooded mysql replication monitoring."
|
25
|
+
|
26
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
27
|
+
|
28
|
+
s.add_runtime_dependency "activerecord", ">= 0"
|
29
|
+
s.add_runtime_dependency "mixlib-log", ">= 1.2.0"
|
30
|
+
end
|
31
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# Replace 'REPLICATION_USER', 'MONITORING_BOX', 'REPLICATION_PASS', 'READ_ONLY_PASS', 'READ_ONLY_USER', 'HEARTBEAT_USER' and 'HEARTBEAT_PASS', then execute this SQL as root on the master, slave, and monitoring server
|
2
2
|
|
3
3
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SUPER ON *.* TO 'REPLICATION_USER'@"localhost" IDENTIFIED BY 'REPLICATION_PASS';
|
4
4
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SUPER ON *.* TO 'REPLICATION_USER'@"MONITORING_BOX" IDENTIFIED BY 'REPLICATION_PASS';
|
@@ -12,7 +12,7 @@ GRANT SELECT, INSERT, UPDATE, ALTER ON replication_monitor.* TO 'HEARTBEAT_USER'
|
|
12
12
|
CREATE DATABASE replication_monitor;
|
13
13
|
|
14
14
|
CREATE TABLE replication_monitor.heartbeats (
|
15
|
-
unix_time INTEGER NOT NULL,
|
16
|
-
db_time TIMESTAMP NOT NULL,
|
15
|
+
unix_time INTEGER NOT NULL,
|
16
|
+
db_time TIMESTAMP NOT NULL,
|
17
17
|
INDEX time_idx(unix_time)
|
18
18
|
)
|
metadata
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reptile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
|
-
-
|
13
|
+
- nick.stielau@gmail.com
|
8
14
|
autorequire:
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
@@ -13,52 +19,80 @@ date: 2010-01-05 00:00:00 -08:00
|
|
13
19
|
default_executable: replication_status
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
name: bundler
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
38
|
name: activerecord
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
43
|
- - ">="
|
32
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
33
48
|
version: "0"
|
34
|
-
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: mixlib-log
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 31
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 2
|
63
|
+
- 0
|
64
|
+
version: 1.2.0
|
65
|
+
type: :runtime
|
66
|
+
version_requirements: *id003
|
35
67
|
description: Cold blooded mysql replication monitoring.
|
36
68
|
email: nick.stielau@gmail.com
|
37
69
|
executables:
|
38
70
|
- replication_status
|
39
71
|
extensions: []
|
40
72
|
|
41
|
-
extra_rdoc_files:
|
42
|
-
|
73
|
+
extra_rdoc_files: []
|
74
|
+
|
43
75
|
files:
|
44
76
|
- .gitignore
|
45
|
-
-
|
46
|
-
-
|
47
|
-
-
|
77
|
+
- Gemfile
|
78
|
+
- Gemfile.lock
|
79
|
+
- LICENSE
|
48
80
|
- README.rdoc
|
49
81
|
- Rakefile
|
50
|
-
- VERSION
|
51
82
|
- bin/replication_status
|
52
83
|
- lib/reptile.rb
|
53
84
|
- lib/reptile/databases.rb
|
54
85
|
- lib/reptile/delta_monitor.rb
|
55
|
-
- lib/reptile/dtd.sql
|
56
86
|
- lib/reptile/heartbeat.rb
|
87
|
+
- lib/reptile/log.rb
|
57
88
|
- lib/reptile/replication_monitor.rb
|
58
89
|
- lib/reptile/runner.rb
|
59
90
|
- lib/reptile/status.rb
|
60
91
|
- lib/reptile/users.rb
|
92
|
+
- lib/reptile/version.rb
|
61
93
|
- replication.yml.sample
|
94
|
+
- reptile.gemspec
|
95
|
+
- reptile_setup.sql
|
62
96
|
- script/console
|
63
97
|
- script/destroy
|
64
98
|
- script/generate
|
@@ -92,24 +126,31 @@ rdoc_options:
|
|
92
126
|
require_paths:
|
93
127
|
- lib
|
94
128
|
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
95
130
|
requirements:
|
96
131
|
- - ">="
|
97
132
|
- !ruby/object:Gem::Version
|
133
|
+
hash: 3
|
134
|
+
segments:
|
135
|
+
- 0
|
98
136
|
version: "0"
|
99
|
-
version:
|
100
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
101
139
|
requirements:
|
102
140
|
- - ">="
|
103
141
|
- !ruby/object:Gem::Version
|
104
|
-
|
105
|
-
|
142
|
+
hash: 23
|
143
|
+
segments:
|
144
|
+
- 1
|
145
|
+
- 3
|
146
|
+
- 6
|
147
|
+
version: 1.3.6
|
106
148
|
requirements: []
|
107
149
|
|
108
150
|
rubyforge_project:
|
109
|
-
rubygems_version: 1.3.
|
151
|
+
rubygems_version: 1.3.7
|
110
152
|
signing_key:
|
111
153
|
specification_version: 3
|
112
154
|
summary: Cold blooded mysql replication monitoring.
|
113
|
-
test_files:
|
114
|
-
|
115
|
-
- test/test_reptile.rb
|
155
|
+
test_files: []
|
156
|
+
|
data/History.txt
DELETED
data/Manifest.txt
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
History.txt
|
2
|
-
Manifest.txt
|
3
|
-
PostInstall.txt
|
4
|
-
README.rdoc
|
5
|
-
Rakefile
|
6
|
-
bin/replication_status
|
7
|
-
lib/reptile.rb
|
8
|
-
lib/reptile/databases.rb
|
9
|
-
lib/reptile/delta_monitor.rb
|
10
|
-
lib/reptile/dtd.sql
|
11
|
-
lib/reptile/heartbeat.rb
|
12
|
-
lib/reptile/replication_monitor.rb
|
13
|
-
lib/reptile/runner.rb
|
14
|
-
lib/reptile/status.rb
|
15
|
-
lib/reptile/users.rb
|
16
|
-
script/console
|
17
|
-
script/destroy
|
18
|
-
script/generate
|
19
|
-
test/test_helper.rb
|
20
|
-
test/test_reptile.rb
|
data/PostInstall.txt
DELETED
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.0.6
|