reptile 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|