mysql-slaver 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 +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
|
+
|