mysql-slaver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +13 -0
- data/Gemfile.lock +43 -0
- data/README.md +43 -0
- data/Rakefile +80 -0
- data/bin/mysql_slaver +29 -0
- data/lib/mysql_slaver/db_copier.rb +23 -0
- data/lib/mysql_slaver/executor.rb +15 -0
- data/lib/mysql_slaver/logger.rb +7 -0
- data/lib/mysql_slaver/master_changer.rb +31 -0
- data/lib/mysql_slaver/mysql_command.rb +19 -0
- data/lib/mysql_slaver/slaver.rb +45 -0
- data/lib/mysql_slaver/status_fetcher.rb +39 -0
- data/lib/mysql_slaver.rb +7 -0
- data/spec/mysql_slaver/db_copier_spec.rb +39 -0
- data/spec/mysql_slaver/executor_spec.rb +13 -0
- data/spec/mysql_slaver/master_changer_spec.rb +27 -0
- data/spec/mysql_slaver/slaver_spec.rb +70 -0
- data/spec/mysql_slaver/status_fetcher_spec.rb +45 -0
- data/spec/spec_helper.rb +4 -0
- metadata +97 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
columnize (0.3.6)
|
5
|
+
diff-lcs (1.2.5)
|
6
|
+
gem-this (0.3.7)
|
7
|
+
gemcutter (0.7.1)
|
8
|
+
json (1.8.1)
|
9
|
+
linecache (0.46)
|
10
|
+
rbx-require-relative (> 0.0.4)
|
11
|
+
rake (10.3.0)
|
12
|
+
rbx-require-relative (0.0.9)
|
13
|
+
rdoc (4.1.1)
|
14
|
+
json (~> 1.4)
|
15
|
+
rdoc-data (4.0.1)
|
16
|
+
rdoc (~> 4.0)
|
17
|
+
rspec (2.14.1)
|
18
|
+
rspec-core (~> 2.14.0)
|
19
|
+
rspec-expectations (~> 2.14.0)
|
20
|
+
rspec-mocks (~> 2.14.0)
|
21
|
+
rspec-core (2.14.8)
|
22
|
+
rspec-expectations (2.14.5)
|
23
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
24
|
+
rspec-mocks (2.14.6)
|
25
|
+
ruby-debug (0.10.4)
|
26
|
+
columnize (>= 0.1)
|
27
|
+
ruby-debug-base (~> 0.10.4.0)
|
28
|
+
ruby-debug-base (0.10.4)
|
29
|
+
linecache (>= 0.3)
|
30
|
+
thor (0.19.1)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
gem-this
|
37
|
+
gemcutter
|
38
|
+
rake
|
39
|
+
rdoc
|
40
|
+
rdoc-data
|
41
|
+
rspec
|
42
|
+
ruby-debug
|
43
|
+
thor
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Tool to setup a mysql replication slave by copying data and master status from a remote mysql master over ssh.
|
2
|
+
|
3
|
+
USAGE
|
4
|
+
|
5
|
+
mysql_slaver help enslave
|
6
|
+
|
7
|
+
mysql_slaver enslave --database=DATABASE --master-host=MASTER_HOST --replication-password=REPLICATION_PASSWORD --replication-user=REPLICATION_USER
|
8
|
+
|
9
|
+
BLOGPOST
|
10
|
+
|
11
|
+
http://digitalronin.github.io/2014/04/16/mysql-slaver-gem-setup-mysql-replication/
|
12
|
+
|
13
|
+
ASSUMPTIONS/PRE-REQUISITES
|
14
|
+
|
15
|
+
* localhost is configured as a mysql replication slave
|
16
|
+
* the current localhost user can ssh to the db master
|
17
|
+
* your mysql administrator user is called 'root', locally and on the db master
|
18
|
+
* root user has the same password on this host and the master server
|
19
|
+
* mysql is on the local user''s path
|
20
|
+
* mysql and mysqldump are on the remote ssh user''s path
|
21
|
+
* replication permissions from the local host to the db master are already setup
|
22
|
+
* mysql is running on the default port (3306)
|
23
|
+
* ssh is on the current user''s path
|
24
|
+
* db character set is UTF-8
|
25
|
+
* any ssh config settings for the host are set in a ~/.ssh/config file
|
26
|
+
|
27
|
+
CAVEATS
|
28
|
+
|
29
|
+
* destructively replaces the target database on localhost with no backup
|
30
|
+
|
31
|
+
TODO
|
32
|
+
|
33
|
+
* accept (and insist on) command-line parameters
|
34
|
+
* add a "no copying" mode that only updates master log file and position
|
35
|
+
* add a "dry-run" mode
|
36
|
+
* package as a gem
|
37
|
+
* check ssh connection and permissions
|
38
|
+
* check replication permissions
|
39
|
+
* check slave is setup as a replication slave (i.e. it has a mysql server id)
|
40
|
+
* allow overriding the mysql port
|
41
|
+
* allow overriding the mysql root user
|
42
|
+
* allow different root user passwords on slave and master
|
43
|
+
* allow ssh options
|
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rubygems/package_task"
|
3
|
+
require "rdoc/task"
|
4
|
+
|
5
|
+
require "rspec"
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
RSpec::Core::RakeTask.new do |t|
|
8
|
+
t.rspec_opts = %w(--format documentation --colour)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
task :default => ["spec"]
|
13
|
+
|
14
|
+
# This builds the actual gem. For details of what all these options
|
15
|
+
# mean, and other ones you can add, check the documentation here:
|
16
|
+
#
|
17
|
+
# http://rubygems.org/read/chapter/20
|
18
|
+
#
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
|
21
|
+
# Change these as appropriate
|
22
|
+
s.name = "mysql-slaver"
|
23
|
+
s.version = "0.1.0"
|
24
|
+
s.summary = "What this thing does"
|
25
|
+
s.author = "David Salgado"
|
26
|
+
s.email = "david@digitalronin.com"
|
27
|
+
s.homepage = "http://yoursite.example.com"
|
28
|
+
|
29
|
+
s.has_rdoc = true
|
30
|
+
s.extra_rdoc_files = %w(README.md)
|
31
|
+
s.rdoc_options = %w(--main README.md)
|
32
|
+
|
33
|
+
# Add any extra files to include in the gem
|
34
|
+
s.files = %w(Gemfile Gemfile.lock Rakefile README.md) + Dir.glob("{bin,spec,lib}/**/*")
|
35
|
+
s.executables = FileList["bin/**"].map { |f| File.basename(f) }
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
|
38
|
+
# If you want to depend on other gems, add them here, along with any
|
39
|
+
# relevant versions
|
40
|
+
# s.add_dependency("some_other_gem", "~> 0.1.0")
|
41
|
+
|
42
|
+
# If your tests use any gems, include them here
|
43
|
+
s.add_development_dependency("rspec")
|
44
|
+
end
|
45
|
+
|
46
|
+
# This task actually builds the gem. We also regenerate a static
|
47
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
48
|
+
# be automatically building a gem for this project. If you're not
|
49
|
+
# using GitHub, edit as appropriate.
|
50
|
+
#
|
51
|
+
# To publish your gem online, install the 'gemcutter' gem; Read more
|
52
|
+
# about that here: http://gemcutter.org/pages/gem_docs
|
53
|
+
Gem::PackageTask.new(spec) do |pkg|
|
54
|
+
pkg.gem_spec = spec
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
58
|
+
task :gemspec do
|
59
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
60
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
61
|
+
end
|
62
|
+
|
63
|
+
# If you don't want to generate the .gemspec file, just remove this line. Reasons
|
64
|
+
# why you might want to generate a gemspec:
|
65
|
+
# - using bundler with a git source
|
66
|
+
# - building the gem without rake (i.e. gem build blah.gemspec)
|
67
|
+
# - maybe others?
|
68
|
+
task :package => :gemspec
|
69
|
+
|
70
|
+
# Generate documentation
|
71
|
+
RDoc::Task.new do |rd|
|
72
|
+
rd.main = "README.md"
|
73
|
+
rd.rdoc_files.include("README.md", "lib/**/*.rb")
|
74
|
+
rd.rdoc_dir = "rdoc"
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'Clear out RDoc and generated packages'
|
78
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
79
|
+
rm "#{spec.name}.gemspec"
|
80
|
+
end
|
data/bin/mysql_slaver
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor'
|
5
|
+
require 'lib/mysql_slaver'
|
6
|
+
|
7
|
+
module MysqlSlaver
|
8
|
+
class CLI < Thor
|
9
|
+
option :master_host, :required => true, :desc => "The server which will be the replication master, for this slave"
|
10
|
+
option :database, :required => true, :desc => "The database to copy from the master"
|
11
|
+
option :replication_user, :required => true, :desc => "DB user (on the master host), with replication permissions"
|
12
|
+
option :replication_password, :required => true, :desc => "DB password for the replication user"
|
13
|
+
option :root_password, :desc => "Password for the mysql root user (on both master and slave)"
|
14
|
+
desc "enslave", "start mysql replication to this host from a master"
|
15
|
+
long_desc <<-LONGDESC
|
16
|
+
LONGDESC
|
17
|
+
def enslave
|
18
|
+
MysqlSlaver::Slaver.new(
|
19
|
+
:master_host => options[:master_host],
|
20
|
+
:mysql_root_password => options[:root_password],
|
21
|
+
:database => options[:database],
|
22
|
+
:replication_user => options[:replication_user],
|
23
|
+
:replication_password => options[:replication_password]
|
24
|
+
).enslave!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
MysqlSlaver::CLI.start(ARGV)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MysqlSlaver
|
2
|
+
class DbCopier
|
3
|
+
include MysqlCommand
|
4
|
+
|
5
|
+
attr_accessor :master_host, :mysql_root_password, :database, :executor
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@master_host = params.fetch(:master_host)
|
9
|
+
@mysql_root_password = params.fetch(:mysql_root_password, '')
|
10
|
+
@database = params.fetch(:database)
|
11
|
+
@executor = params.fetch(:executor) { Executor.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy!
|
15
|
+
executor.execute mysql_command("stop slave", mysql_root_password)
|
16
|
+
cmd = mysqldump(master_host, database, mysql_root_password)
|
17
|
+
dump_cmd = executor.ssh_command(cmd, master_host)
|
18
|
+
load_cmd = ['mysql', mysql_credentials('root', mysql_root_password), database].join(' ')
|
19
|
+
command = [dump_cmd, load_cmd].join(' | ')
|
20
|
+
executor.execute command
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MysqlSlaver
|
2
|
+
class MasterChanger
|
3
|
+
include MysqlCommand
|
4
|
+
|
5
|
+
attr_accessor :master_host, :mysql_root_password, :replication_user, :replication_password, :executor
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@master_host = params.fetch(:master_host)
|
9
|
+
@mysql_root_password = params.fetch(:mysql_root_password, '')
|
10
|
+
@replication_user = params.fetch(:replication_user)
|
11
|
+
@replication_password = params.fetch(:replication_password)
|
12
|
+
@executor = params.fetch(:executor) { Executor.new }
|
13
|
+
end
|
14
|
+
|
15
|
+
def change!(status)
|
16
|
+
cmds = [
|
17
|
+
'stop slave',
|
18
|
+
change_master(status),
|
19
|
+
'start slave'
|
20
|
+
]
|
21
|
+
cmd = mysql_command(cmds.join('; '), mysql_root_password)
|
22
|
+
executor.execute cmd
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def change_master(status)
|
28
|
+
%[CHANGE MASTER TO MASTER_LOG_FILE='#{status[:file]}', MASTER_LOG_POS=#{status[:position]}, MASTER_HOST='#{master_host}', MASTER_USER='#{replication_user}', MASTER_PASSWORD='#{replication_password}']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MysqlSlaver
|
2
|
+
module MysqlCommand
|
3
|
+
def mysql_credentials(user, password)
|
4
|
+
rtn = "-u #{user} "
|
5
|
+
rtn << "-p #{password} " unless password.to_s == ""
|
6
|
+
rtn
|
7
|
+
end
|
8
|
+
|
9
|
+
def mysql_command(cmd, password)
|
10
|
+
creds = mysql_credentials('root', password)
|
11
|
+
%[mysql #{creds} -e "#{cmd}"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def mysqldump(host, database, password)
|
15
|
+
creds = mysql_credentials('root', password)
|
16
|
+
%[mysqldump -h #{host} #{creds} --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress #{database}]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Setup the current server as a replication slave of a master mysql server,
|
2
|
+
# by dumping and loading a database, and issuing the appropriate change master
|
3
|
+
# command to mysql.
|
4
|
+
# This assumes that the current user can issue ssh commands to the master
|
5
|
+
# server (to get its master status, and to dump and load data over the ssh
|
6
|
+
# connection)
|
7
|
+
module MysqlSlaver
|
8
|
+
class Slaver
|
9
|
+
attr_reader :status_fetcher, :data_copier, :master_changer
|
10
|
+
|
11
|
+
def initialize(params)
|
12
|
+
mysql_root_password = params.fetch(:mysql_root_password, '')
|
13
|
+
|
14
|
+
@status_fetcher = params.fetch(:status_fetcher) {
|
15
|
+
StatusFetcher.new(
|
16
|
+
:master_host => params.fetch(:master_host),
|
17
|
+
:mysql_root_password => mysql_root_password
|
18
|
+
)
|
19
|
+
}
|
20
|
+
|
21
|
+
@data_copier = params.fetch(:data_copier) {
|
22
|
+
DbCopier.new(
|
23
|
+
:master_host => params.fetch(:master_host),
|
24
|
+
:mysql_root_password => mysql_root_password,
|
25
|
+
:database => params.fetch(:database)
|
26
|
+
)
|
27
|
+
}
|
28
|
+
|
29
|
+
@master_changer = params.fetch(:master_changer) {
|
30
|
+
MasterChanger.new(
|
31
|
+
:master_host => params.fetch(:master_host),
|
32
|
+
:mysql_root_password => mysql_root_password,
|
33
|
+
:replication_user => params.fetch(:replication_user),
|
34
|
+
:replication_password => params.fetch(:replication_password)
|
35
|
+
)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def enslave!
|
40
|
+
master_status = status_fetcher.status
|
41
|
+
data_copier.copy!
|
42
|
+
master_changer.change!(master_status)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MysqlSlaver
|
2
|
+
class StatusFetcher
|
3
|
+
include Logger
|
4
|
+
include MysqlCommand
|
5
|
+
|
6
|
+
attr_accessor :master_host, :mysql_root_password, :executor
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@master_host = params.fetch(:master_host)
|
10
|
+
@mysql_root_password = params.fetch(:mysql_root_password, '')
|
11
|
+
@executor = params.fetch(:executor) { Executor.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
cmd = mysql_command("show master status\\G", mysql_root_password)
|
16
|
+
data = executor.execute executor.ssh_command(cmd, master_host)
|
17
|
+
rtn = parse data
|
18
|
+
log "MASTER STATUS - file: #{rtn[:file]}, position: #{rtn[:position]}"
|
19
|
+
rtn
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parse(text)
|
25
|
+
file = nil
|
26
|
+
position = nil
|
27
|
+
text.split("\n").each do |line|
|
28
|
+
case line
|
29
|
+
when /File: (mysql-bin\.\d+)/
|
30
|
+
file = $1
|
31
|
+
when /Position: (\d+)/
|
32
|
+
position = $1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
{:file => file, :position => position}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/mysql_slaver.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'lib/mysql_slaver/logger'
|
2
|
+
require 'lib/mysql_slaver/executor'
|
3
|
+
require 'lib/mysql_slaver/mysql_command'
|
4
|
+
require 'lib/mysql_slaver/slaver'
|
5
|
+
require 'lib/mysql_slaver/status_fetcher'
|
6
|
+
require 'lib/mysql_slaver/db_copier'
|
7
|
+
require 'lib/mysql_slaver/master_changer'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module MysqlSlaver
|
4
|
+
describe DbCopier do
|
5
|
+
let(:executor) { double(Executor, :execute => true, :ssh_command => "dummy-ssh-command") }
|
6
|
+
|
7
|
+
let(:params) {
|
8
|
+
{
|
9
|
+
:master_host => 'my.db.host',
|
10
|
+
:mysql_root_password => 'supersekrit',
|
11
|
+
:database => 'myappdb',
|
12
|
+
:executor => executor
|
13
|
+
}
|
14
|
+
}
|
15
|
+
subject(:copier) { described_class.new(params) }
|
16
|
+
|
17
|
+
describe "#copy!" do
|
18
|
+
it "stops slave" do
|
19
|
+
copier.copy!
|
20
|
+
stop = %[mysql -u root -p supersekrit -e "stop slave"]
|
21
|
+
expect(executor).to have_received(:execute).with(stop)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "loads data" do
|
25
|
+
dump_and_load = "dummy-ssh-command | mysql -u root -p supersekrit myappdb"
|
26
|
+
expect(executor).to receive(:execute).once.ordered.with(dump_and_load)
|
27
|
+
copier.copy!
|
28
|
+
end
|
29
|
+
|
30
|
+
context "dumping" do
|
31
|
+
it "issues mysqldump over ssh" do
|
32
|
+
dump = "mysqldump -h my.db.host -u root -p supersekrit --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress myappdb"
|
33
|
+
expect(executor).to receive(:ssh_command).with(dump, 'my.db.host')
|
34
|
+
copier.copy!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module MysqlSlaver
|
4
|
+
describe Executor do
|
5
|
+
subject(:executor) { described_class.new }
|
6
|
+
|
7
|
+
describe "#ssh_command" do
|
8
|
+
it "formats command" do
|
9
|
+
expect(executor.ssh_command("foo", "myhost")).to eq("ssh myhost 'foo'")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module MysqlSlaver
|
4
|
+
describe MasterChanger do
|
5
|
+
let(:executor) { double(Executor, :execute => true) }
|
6
|
+
|
7
|
+
let(:status) { {:file => 'mysql-bin.001555', :position => 18426246} }
|
8
|
+
let(:params) {
|
9
|
+
{
|
10
|
+
:master_host => 'my.db.host',
|
11
|
+
:mysql_root_password => 'supersekrit',
|
12
|
+
:replication_user => 'repluser',
|
13
|
+
:replication_password => 'replpassword',
|
14
|
+
:executor => executor
|
15
|
+
}
|
16
|
+
}
|
17
|
+
subject(:changer) { described_class.new(params) }
|
18
|
+
|
19
|
+
describe "#change!" do
|
20
|
+
it "executes multi-part mysql command" do
|
21
|
+
change_cmd = %[mysql -u root -p supersekrit -e "stop slave; CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.001555', MASTER_LOG_POS=18426246, MASTER_HOST='my.db.host', MASTER_USER='repluser', MASTER_PASSWORD='replpassword'; start slave"]
|
22
|
+
changer.change!(status)
|
23
|
+
expect(executor).to have_received(:execute).with(change_cmd)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module MysqlSlaver
|
4
|
+
describe Slaver do
|
5
|
+
subject(:slaver) { described_class.new(params) }
|
6
|
+
|
7
|
+
describe "#enslave!" do
|
8
|
+
let(:status_fetcher) { double(StatusFetcher, :status => ()) }
|
9
|
+
let(:data_copier) { double(DbCopier, :copy! => true) }
|
10
|
+
let(:master_changer) { double(MasterChanger, :change! => true) }
|
11
|
+
|
12
|
+
let(:params) {
|
13
|
+
{
|
14
|
+
:status_fetcher => status_fetcher,
|
15
|
+
:data_copier => data_copier,
|
16
|
+
:master_changer => master_changer
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
it "fetches master status" do
|
21
|
+
slaver.enslave!
|
22
|
+
expect(status_fetcher).to have_received(:status)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "copies data" do
|
26
|
+
slaver.enslave!
|
27
|
+
expect(data_copier).to have_received(:copy!)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "changes master status" do
|
31
|
+
slaver.enslave!
|
32
|
+
expect(master_changer).to have_received(:change!)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
context "instantiating collaborators" do
|
38
|
+
let(:params) {
|
39
|
+
{
|
40
|
+
:master_host => 'my.db.host',
|
41
|
+
:mysql_root_password => 'supersekrit',
|
42
|
+
:database => 'myappdb',
|
43
|
+
:replication_user => 'repluser',
|
44
|
+
:replication_password => 'replpassword'
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
it "instantiates a status fetcher" do
|
49
|
+
fetcher = slaver.status_fetcher
|
50
|
+
expect(fetcher.master_host).to eq('my.db.host')
|
51
|
+
expect(fetcher.mysql_root_password).to eq('supersekrit')
|
52
|
+
end
|
53
|
+
|
54
|
+
it "instantiates a data copier" do
|
55
|
+
copier = slaver.data_copier
|
56
|
+
expect(copier.master_host).to eq('my.db.host')
|
57
|
+
expect(copier.mysql_root_password).to eq('supersekrit')
|
58
|
+
expect(copier.database).to eq('myappdb')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "instantiates a master changer" do
|
62
|
+
changer = slaver.master_changer
|
63
|
+
expect(changer.master_host).to eq('my.db.host')
|
64
|
+
expect(changer.mysql_root_password).to eq('supersekrit')
|
65
|
+
expect(changer.replication_user).to eq('repluser')
|
66
|
+
expect(changer.replication_password).to eq('replpassword')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module MysqlSlaver
|
4
|
+
describe StatusFetcher do
|
5
|
+
let(:executor) { double(Executor, :execute => "", :ssh_command => "dummy-ssh-command") }
|
6
|
+
|
7
|
+
let(:params) {
|
8
|
+
{
|
9
|
+
:master_host => 'my.db.host',
|
10
|
+
:mysql_root_password => 'supersekrit',
|
11
|
+
:executor => executor
|
12
|
+
}
|
13
|
+
}
|
14
|
+
subject(:fetcher) { described_class.new(params) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
fetcher.stub(:log)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#status" do
|
21
|
+
let(:output) { <<EOF
|
22
|
+
*************************** 1. row ***************************
|
23
|
+
File: mysql-bin.003219
|
24
|
+
Position: 37065270
|
25
|
+
Binlog_Do_DB:
|
26
|
+
Binlog_Ignore_DB:
|
27
|
+
EOF
|
28
|
+
}
|
29
|
+
|
30
|
+
before do
|
31
|
+
executor.stub(:execute => output)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "executes show master command over ssh" do
|
35
|
+
show_master = %[mysql -u root -p supersekrit -e "show master status\\G"]
|
36
|
+
fetcher.status
|
37
|
+
expect(executor).to have_received(:ssh_command).with(show_master, 'my.db.host')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "parses show master output" do
|
41
|
+
expect(fetcher.status).to eq({:file => 'mysql-bin.003219', :position => '37065270'})
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql-slaver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- David Salgado
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2014-04-16 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: david@digitalronin.com
|
36
|
+
executables:
|
37
|
+
- mysql_slaver
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.md
|
42
|
+
files:
|
43
|
+
- Gemfile
|
44
|
+
- Gemfile.lock
|
45
|
+
- Rakefile
|
46
|
+
- README.md
|
47
|
+
- bin/mysql_slaver
|
48
|
+
- spec/mysql_slaver/db_copier_spec.rb
|
49
|
+
- spec/mysql_slaver/executor_spec.rb
|
50
|
+
- spec/mysql_slaver/master_changer_spec.rb
|
51
|
+
- spec/mysql_slaver/slaver_spec.rb
|
52
|
+
- spec/mysql_slaver/status_fetcher_spec.rb
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
- lib/mysql_slaver/db_copier.rb
|
55
|
+
- lib/mysql_slaver/executor.rb
|
56
|
+
- lib/mysql_slaver/logger.rb
|
57
|
+
- lib/mysql_slaver/master_changer.rb
|
58
|
+
- lib/mysql_slaver/mysql_command.rb
|
59
|
+
- lib/mysql_slaver/slaver.rb
|
60
|
+
- lib/mysql_slaver/status_fetcher.rb
|
61
|
+
- lib/mysql_slaver.rb
|
62
|
+
homepage: https://github.com/digitalronin/mysql-slaver
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --main
|
68
|
+
- README.md
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.25
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Setup mysql replication on a slave (localhost), from a remote master, over SSH
|
96
|
+
test_files: []
|
97
|
+
|