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 ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in reptile.gemspec
4
+ gemspec
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, and can email if replication appears to be failing.
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
- -s, --status Displays the slave status
18
- -d, --diff Checks the row count difference between master and slave
19
- -r, --report Sends a report email
20
- -b, --heartbeat Checks the heartbeat timestamp difference between master and slave
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 '-e' arg to only spit out errors, which will be sent out by crontab
32
- */15 * * * * replication_status -e
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 'rubygems' unless ENV['NO_RUBYGEMS']
2
- %w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
3
- require File.dirname(__FILE__) + '/lib/reptile'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
4
3
 
5
- begin
6
- require 'jeweler'
7
- Jeweler::Tasks.new do |gem|
8
- gem.name = "reptile"
9
- gem.summary = %Q{Cold blooded mysql replication monitoring.}
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
- require 'newgem/tasks' # load /tasks/*.rake
35
- Dir['tasks/**/*.rake'].each { |t| load t }
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
@@ -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("-s", "--status", "Displays the slave status") do
20
+
21
+ opts.on("--status", "Displays the slave status") do
18
22
  commands << 'check_slaves'
19
23
  end
20
-
21
- opts.on("-d", "--diff", "Checks the row count difference between master and slave") do
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("-r", "--report", "Sends a report email") do
28
+
29
+ opts.on("--report", "Prints a report") do
26
30
  commands << 'report'
27
31
  end
28
-
29
- opts.on("-b", "--heartbeat", "Checks the heartbeat timestamp difference between master and slave") do
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("-x", "--stop_slaves", "Stops all slaves") do
36
+
37
+ opts.on("--stop-slaves", "Stops all slaves") do
34
38
  commands << 'stop_slaves'
35
39
  end
36
-
37
- opts.on("-g", "--start_slaves", "Starts all slaves") do
40
+
41
+ opts.on("--start-slaves", "Starts all slaves") do
38
42
  commands << 'start_slaves'
39
43
  end
40
-
41
- opts.on("-e", "--errors", "Show only errors.") do
42
- commands << 'errors'
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 directory containing the 'reptile.yml' file, or a yml config file itself."
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.directory?(config_location_param) && File.exist?(File.join(config_location_param, config_file_name))
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 directory containing the '#{config_file_name}' file, or the config file itself."
71
+ abort "Please specify the config file."
70
72
  end
71
73
  else
72
- config_file_locations.each do |f|
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)
@@ -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
- puts "****"
45
- puts "Error connecting to database: #{e}"
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
- puts "Table '#{table}' exists on master but not on slave."
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
- get_logger.error "Error: Caught #{e}"
54
- get_logger.error "DB Name: #{db_name}"
55
- get_logger.error "Master Configs: #{master_configs.inspect}"
56
- get_logger.error "Slave Configs: #{slave_configs.inspect}"
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
- get_logger.info "Replication counts A-OK for #{db_name} on #{configs['host']} @ #{Time.now}"
54
+ Log.info "Replication counts A-OK for #{db_name} on #{configs['host']} @ #{Time.now}"
65
55
  else
66
- get_logger.info "Replication Row Count Deltas for #{db_name} on #{configs['host']} @ #{Time.now}"
67
- get_logger.info "There #{non_zero_deltas.size > 1 ? 'are' : 'is'} #{non_zero_deltas.size} #{non_zero_deltas.size > 1 ? 'deltas' : 'delta'}"
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
- get_logger.info " #{table} table: #{delta}" unless delta.zero?
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
@@ -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
- get_logger.info "Wrote heartbeat to #{name} at #{Time.at(heartbeat.unix_time)}"
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
- get_logger.info "No heartbeats found on #{name} at #{Time.now}"
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
- get_logger.info "Read heartbeat from #{name} at #{Time.at(heartbeat.unix_time)}. The delay is #{strfdelay(delay)}"
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
@@ -0,0 +1,6 @@
1
+ require 'mixlib/log'
2
+ module Reptile
3
+ class Log
4
+ extend Mixlib::Log
5
+ end
6
+ end
@@ -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
- puts "Unable to open config file: Permission Denied"
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
- DeltaMonitor.logger = get_logger
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
- queue_replication_warning :host => master["host"], :database => master["database"], :deltas => egregious_deltas, :noticed_at => Time.now
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
- Heartbeat.logger = get_logger
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
- queue_replication_warning :host => name,
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
- queue_replication_warning :host => name,
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
- get_logger.info "'#{slave_name}' is '#{status}'"
90
+ Log.info "'#{slave_name}' is '#{status}'"
101
91
  if status != Status.const_get(:RUNNING)
102
- queue_replication_warning :host => slave_name,
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.queue_replication_warning(options)
111
- email = OpenStruct.new
112
- email.recipients = get_recipients
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
- email.body += "There was a #{options[:delay]} second replication latency, which is greater than the allowed latency of #{configs['delay_threshold_secs']} seconds"
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
- email.body += "The following tables have master/slave row count difference greater than the allowed #{configs['row_difference_threshold']}\n\n"
120
- options[:deltas].each do |table, delta|
121
- email.body += " table '#{table}' was off by #{delta} rows\n"
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
- email.body += " MySQL Status message: #{options[:status_error]}"
111
+ Log.error " MySQL Status message: #{options[:status_error]}"
125
112
  elsif options[:general_error]
126
- email.body += " Error: #{options[:general_error]}"
113
+ Log.error " Error: #{options[:general_error]}"
127
114
  end
128
-
129
- email.body += "\n"
130
- email.body += " Server: #{options[:host]}\n"
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
- email = OpenStruct.new
151
- email.recipients = get_recipients
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
- email.body = out.string
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
@@ -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
- puts "****"
54
- puts "Error connecting to database: #{e}"
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
- puts "Executing #{cmd} on #{name}"
65
- puts slave_configs.inspect
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|
@@ -0,0 +1,3 @@
1
+ module Reptile
2
+ VERSION = "0.1.0"
3
+ end
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
- // 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
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
- version: 0.0.6
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
- - Nick Stielau
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: tlsmail
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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
- version: "0"
24
- version:
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
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
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
- version:
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
- - README.rdoc
73
+ extra_rdoc_files: []
74
+
43
75
  files:
44
76
  - .gitignore
45
- - History.txt
46
- - Manifest.txt
47
- - PostInstall.txt
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
- version: "0"
105
- version:
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.5
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
- - test/test_helper.rb
115
- - test/test_reptile.rb
155
+ test_files: []
156
+
data/History.txt DELETED
@@ -1,4 +0,0 @@
1
- == 0.0.1 2009-06-24
2
-
3
- * 1 major enhancement:
4
- * Initial release
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
@@ -1,5 +0,0 @@
1
-
2
- For more information on reptile, see http://reptile.rubyforge.org
3
-
4
-
5
-
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.0.6