mysql-slaver 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -14,15 +14,15 @@ ASSUMPTIONS/PRE-REQUISITES
14
14
 
15
15
  * localhost is configured as a mysql replication slave
16
16
  * the current localhost user can ssh to the db master
17
+ * any ssh config settings required to access the master from localhost are set in a ~/.ssh/config file
18
+ * ssh is on the current user's path
17
19
  * 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
20
  * mysql is on the local user's path
20
21
  * mysql and mysqldump are on the remote ssh user's path
21
22
  * 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
23
+ * root user has the same password on this host and the master server
24
+ * mysql socket (if any) is the same on localhost and the db master
24
25
  * db character set is UTF-8
25
- * any ssh config settings for the host are set in a ~/.ssh/config file
26
26
 
27
27
  CAVEATS
28
28
 
@@ -35,7 +35,6 @@ TODO
35
35
  * check ssh connection and permissions
36
36
  * check replication permissions
37
37
  * check slave is setup as a replication slave (i.e. it has a mysql server id)
38
- * allow overriding the mysql port
39
- * allow overriding the mysql root user
38
+ * allow a mysql admin username other than 'root'
40
39
  * allow different root user passwords on slave and master
41
40
  * allow ssh options
@@ -4,13 +4,18 @@ module MysqlSlaver
4
4
  option :database, :required => true, :desc => "The database to copy from the master"
5
5
  option :replication_user, :required => true, :desc => "DB user (on the master host), with replication permissions"
6
6
  option :replication_password, :required => true, :desc => "DB password for the replication user"
7
- option :root_password, :desc => "Password for the mysql root user (on both master and slave)"
7
+
8
+ option :root_password, :desc => "Password for the mysql root user (on both master and slave)"
9
+ option :port, :desc => "Mysql port (if not 3306)"
10
+ option :sock, :desc => "Mysql socket file (if any)"
11
+
8
12
  desc "enslave", "start mysql replication to this host from a master"
9
- long_desc <<-LONGDESC
10
- LONGDESC
13
+
11
14
  def enslave
12
15
  MysqlSlaver::Slaver.new(
13
16
  :master_host => options[:master_host],
17
+ :port => options[:port],
18
+ :socket_file => options[:sock],
14
19
  :mysql_root_password => options[:root_password],
15
20
  :database => options[:database],
16
21
  :replication_user => options[:replication_user],
@@ -2,22 +2,41 @@ module MysqlSlaver
2
2
  class DbCopier
3
3
  include MysqlCommand
4
4
 
5
- attr_accessor :master_host, :mysql_root_password, :database, :executor
5
+ attr_accessor :master_host, :mysql_root_password, :database, :executor, :port, :socket_file
6
6
 
7
7
  def initialize(params)
8
8
  @master_host = params.fetch(:master_host)
9
9
  @mysql_root_password = params.fetch(:mysql_root_password, '')
10
10
  @database = params.fetch(:database)
11
+ @port = params.fetch(:port, nil)
12
+ @socket_file = params.fetch(:socket_file, nil)
11
13
  @executor = params.fetch(:executor) { Executor.new }
12
14
  end
13
15
 
14
16
  def copy!
15
- executor.execute mysql_command("stop slave", mysql_root_password)
16
- cmd = mysqldump(master_host, database, mysql_root_password)
17
+ executor.execute mysql_command("stop slave", mysql_params)
18
+ cmd = mysqldump
17
19
  dump_cmd = executor.ssh_command(cmd, master_host)
18
- load_cmd = ['mysql', mysql_credentials('root', mysql_root_password), database].join(' ')
20
+ load_cmd = ['mysql', mysql_credentials('root', mysql_params), database].join(' ')
19
21
  command = [dump_cmd, load_cmd].join(' | ')
20
22
  executor.execute command
21
23
  end
24
+
25
+ private
26
+
27
+ def mysql_params
28
+ {
29
+ :root_password => mysql_root_password,
30
+ :socket_file => socket_file
31
+ }
32
+ end
33
+
34
+ def mysqldump
35
+ creds = mysql_credentials('root', mysql_params)
36
+ rtn = %[mysqldump]
37
+ rtn << %[ -P #{port}] if port
38
+ rtn << %[ #{creds} -h #{master_host} --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress #{database}]
39
+ rtn
40
+ end
22
41
  end
23
42
  end
@@ -2,10 +2,12 @@ module MysqlSlaver
2
2
  class MasterChanger
3
3
  include MysqlCommand
4
4
 
5
- attr_accessor :master_host, :mysql_root_password, :replication_user, :replication_password, :executor
5
+ attr_accessor :master_host, :mysql_root_password, :replication_user, :replication_password, :executor, :port, :socket_file
6
6
 
7
7
  def initialize(params)
8
8
  @master_host = params.fetch(:master_host)
9
+ @port = params.fetch(:port)
10
+ @socket_file = params.fetch(:socket_file, nil)
9
11
  @mysql_root_password = params.fetch(:mysql_root_password, '')
10
12
  @replication_user = params.fetch(:replication_user)
11
13
  @replication_password = params.fetch(:replication_password)
@@ -18,14 +20,32 @@ module MysqlSlaver
18
20
  change_master(status),
19
21
  'start slave'
20
22
  ]
21
- cmd = mysql_command(cmds.join('; '), mysql_root_password)
23
+ cmd = mysql_command(cmds.join('; '), mysql_params)
22
24
  executor.execute cmd
23
25
  end
24
26
 
25
27
  private
26
28
 
29
+ def mysql_params
30
+ {
31
+ :root_password => mysql_root_password,
32
+ :socket_file => socket_file
33
+ }
34
+ end
35
+
27
36
  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}']
37
+ %[CHANGE MASTER TO #{cmd_values(status)}]
38
+ end
39
+
40
+ def cmd_values(status)
41
+ [
42
+ "MASTER_LOG_FILE='#{status[:file]}'",
43
+ "MASTER_LOG_POS=#{status[:position]}",
44
+ "MASTER_HOST='#{master_host}'",
45
+ "MASTER_PORT=#{port}",
46
+ "MASTER_USER='#{replication_user}'",
47
+ "MASTER_PASSWORD='#{replication_password}'"
48
+ ].join(', ')
29
49
  end
30
50
  end
31
51
  end
@@ -1,19 +1,19 @@
1
1
  module MysqlSlaver
2
2
  module MysqlCommand
3
- def mysql_credentials(user, password)
4
- rtn = "-u #{user} "
5
- rtn << "-p #{password} " unless password.to_s == ""
3
+ def mysql_credentials(user, params)
4
+ password = params.fetch(:root_password, "")
5
+ socket_file = params.fetch(:socket_file, nil)
6
+
7
+ rtn = ""
8
+ rtn << "-S #{socket_file}" if socket_file
9
+ rtn << " -u #{user}"
10
+ rtn << " -p #{password}" unless password.to_s == ""
6
11
  rtn
7
12
  end
8
13
 
9
- def mysql_command(cmd, password)
10
- creds = mysql_credentials('root', password)
14
+ def mysql_command(cmd, params)
15
+ creds = mysql_credentials('root', params)
11
16
  %[mysql #{creds} -e "#{cmd}"]
12
17
  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
18
  end
19
19
  end
@@ -9,12 +9,15 @@ module MysqlSlaver
9
9
  attr_reader :status_fetcher, :data_copier, :master_changer
10
10
 
11
11
  def initialize(params)
12
- mysql_root_password = params.fetch(:mysql_root_password, '')
12
+ mysql_root_password = params.fetch(:mysql_root_password, '')
13
+ port = params.fetch(:port, 3306)
14
+ socket_file = params.fetch(:socket_file, nil)
13
15
 
14
16
  @status_fetcher = params.fetch(:status_fetcher) {
15
17
  StatusFetcher.new(
16
18
  :master_host => params.fetch(:master_host),
17
- :mysql_root_password => mysql_root_password
19
+ :mysql_root_password => mysql_root_password,
20
+ :socket_file => socket_file
18
21
  )
19
22
  }
20
23
 
@@ -22,7 +25,9 @@ module MysqlSlaver
22
25
  DbCopier.new(
23
26
  :master_host => params.fetch(:master_host),
24
27
  :mysql_root_password => mysql_root_password,
25
- :database => params.fetch(:database)
28
+ :database => params.fetch(:database),
29
+ :port => port,
30
+ :socket_file => socket_file
26
31
  )
27
32
  }
28
33
 
@@ -31,7 +36,9 @@ module MysqlSlaver
31
36
  :master_host => params.fetch(:master_host),
32
37
  :mysql_root_password => mysql_root_password,
33
38
  :replication_user => params.fetch(:replication_user),
34
- :replication_password => params.fetch(:replication_password)
39
+ :replication_password => params.fetch(:replication_password),
40
+ :port => port,
41
+ :socket_file => socket_file
35
42
  )
36
43
  }
37
44
  end
@@ -3,16 +3,18 @@ module MysqlSlaver
3
3
  include Logger
4
4
  include MysqlCommand
5
5
 
6
- attr_accessor :master_host, :mysql_root_password, :executor
6
+ attr_accessor :master_host, :mysql_root_password, :executor, :socket_file
7
7
 
8
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 }
9
+ @master_host = params.fetch(:master_host)
10
+ @socket_file = params.fetch(:socket_file, nil)
11
+ @mysql_root_password = params.fetch(:mysql_root_password, '')
12
+ @executor = params.fetch(:executor) { Executor.new }
12
13
  end
13
14
 
14
15
  def status
15
- cmd = mysql_command("show master status\\G", mysql_root_password)
16
+ params = {:root_password => mysql_root_password, :socket_file => socket_file}
17
+ cmd = mysql_command("show master status\\G", params)
16
18
  data = executor.execute executor.ssh_command(cmd, master_host)
17
19
  rtn = parse data
18
20
  log "MASTER STATUS - file: #{rtn[:file]}, position: #{rtn[:position]}"
@@ -17,23 +17,50 @@ module MysqlSlaver
17
17
  describe "#copy!" do
18
18
  it "stops slave" do
19
19
  copier.copy!
20
- stop = %[mysql -u root -p supersekrit -e "stop slave"]
20
+ stop = %[mysql -u root -p supersekrit -e "stop slave"]
21
21
  expect(executor).to have_received(:execute).with(stop)
22
22
  end
23
23
 
24
24
  it "loads data" do
25
- dump_and_load = "dummy-ssh-command | mysql -u root -p supersekrit myappdb"
25
+ dump_and_load = "dummy-ssh-command | mysql -u root -p supersekrit myappdb"
26
26
  expect(executor).to receive(:execute).once.ordered.with(dump_and_load)
27
27
  copier.copy!
28
28
  end
29
29
 
30
30
  context "dumping" do
31
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"
32
+ dump = "mysqldump -u root -p supersekrit -h my.db.host --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress myappdb"
33
33
  expect(executor).to receive(:ssh_command).with(dump, 'my.db.host')
34
34
  copier.copy!
35
35
  end
36
36
  end
37
37
  end
38
+
39
+ context "with a non-standard mysql port" do
40
+ let(:params) { super().merge(:port => 3307) }
41
+
42
+ it "issues mysqldump over ssh" do
43
+ dump = "mysqldump -P 3307 -u root -p supersekrit -h my.db.host --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress myappdb"
44
+ expect(executor).to receive(:ssh_command).with(dump, 'my.db.host')
45
+ copier.copy!
46
+ end
47
+ end
48
+
49
+ context "with a socket file" do
50
+ let(:params) { super().merge(:socket_file => "/tmp/mysql.sock") }
51
+
52
+ it "issues mysqldump over ssh" do
53
+ dump = "mysqldump -S /tmp/mysql.sock -u root -p supersekrit -h my.db.host --master-data --single-transaction --quick --skip-add-locks --skip-lock-tables --default-character-set=utf8 --compress myappdb"
54
+ expect(executor).to receive(:ssh_command).with(dump, 'my.db.host')
55
+ copier.copy!
56
+ end
57
+
58
+ it "loads data" do
59
+ dump_and_load = "dummy-ssh-command | mysql -S /tmp/mysql.sock -u root -p supersekrit myappdb"
60
+ expect(executor).to receive(:execute).once.ordered.with(dump_and_load)
61
+ copier.copy!
62
+ end
63
+
64
+ end
38
65
  end
39
66
  end
@@ -8,6 +8,7 @@ module MysqlSlaver
8
8
  let(:params) {
9
9
  {
10
10
  :master_host => 'my.db.host',
11
+ :port => 3306,
11
12
  :mysql_root_password => 'supersekrit',
12
13
  :replication_user => 'repluser',
13
14
  :replication_password => 'replpassword',
@@ -18,7 +19,27 @@ module MysqlSlaver
18
19
 
19
20
  describe "#change!" do
20
21
  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
+ 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_PORT=3306, MASTER_USER='repluser', MASTER_PASSWORD='replpassword'; start slave"]
23
+ changer.change!(status)
24
+ expect(executor).to have_received(:execute).with(change_cmd)
25
+ end
26
+ end
27
+
28
+ context "with a non-standard mysql port" do
29
+ let(:params) { super().merge(:port => 3307) }
30
+
31
+ it "executes multi-part mysql command" do
32
+ 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_PORT=3307, MASTER_USER='repluser', MASTER_PASSWORD='replpassword'; start slave"]
33
+ changer.change!(status)
34
+ expect(executor).to have_received(:execute).with(change_cmd)
35
+ end
36
+ end
37
+
38
+ context "with a mysql socket file" do
39
+ let(:params) { super().merge(:socket_file => "/tmp/mysql.sock") }
40
+
41
+ it "executes multi-part mysql command" do
42
+ change_cmd = %[mysql -S /tmp/mysql.sock -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_PORT=3306, MASTER_USER='repluser', MASTER_PASSWORD='replpassword'; start slave"]
22
43
  changer.change!(status)
23
44
  expect(executor).to have_received(:execute).with(change_cmd)
24
45
  end
@@ -64,6 +64,36 @@ module MysqlSlaver
64
64
  expect(changer.mysql_root_password).to eq('supersekrit')
65
65
  expect(changer.replication_user).to eq('repluser')
66
66
  expect(changer.replication_password).to eq('replpassword')
67
+ expect(changer.port).to eq(3306)
68
+ end
69
+
70
+ context "with non-standard mysql port" do
71
+ let(:params) { super().merge(:port => 3307) }
72
+
73
+ it "instantiates a master changer" do
74
+ changer = slaver.master_changer
75
+ expect(changer.port).to eq(3307)
76
+ end
77
+ end
78
+
79
+ context "with mysql socket" do
80
+ let(:params) { super().merge(:socket_file => "/tmp/mysql.sock") }
81
+
82
+ it "instantiates a status fetcher" do
83
+ fetcher = slaver.status_fetcher
84
+ expect(fetcher.socket_file).to eq("/tmp/mysql.sock")
85
+ end
86
+
87
+ it "instantiates a master changer" do
88
+ changer = slaver.master_changer
89
+ expect(changer.socket_file).to eq("/tmp/mysql.sock")
90
+ end
91
+
92
+ it "instantiates a data copier" do
93
+ copier = slaver.data_copier
94
+ expect(copier.socket_file).to eq("/tmp/mysql.sock")
95
+ end
96
+
67
97
  end
68
98
  end
69
99
  end
@@ -32,7 +32,7 @@ EOF
32
32
  end
33
33
 
34
34
  it "executes show master command over ssh" do
35
- show_master = %[mysql -u root -p supersekrit -e "show master status\\G"]
35
+ show_master = %[mysql -u root -p supersekrit -e "show master status\\G"]
36
36
  fetcher.status
37
37
  expect(executor).to have_received(:ssh_command).with(show_master, 'my.db.host')
38
38
  end
@@ -41,5 +41,15 @@ EOF
41
41
  expect(fetcher.status).to eq({:file => 'mysql-bin.003219', :position => '37065270'})
42
42
  end
43
43
  end
44
+
45
+ context "using a socket filename" do
46
+ let(:params) { super().merge(:socket_file => "/var/run/mysqld/mysqld.master.sock") }
47
+
48
+ it "executes show master command over ssh" do
49
+ show_master = %[mysql -S /var/run/mysqld/mysqld.master.sock -u root -p supersekrit -e "show master status\\G"]
50
+ fetcher.status
51
+ expect(executor).to have_received(:ssh_command).with(show_master, 'my.db.host')
52
+ end
53
+ end
44
54
  end
45
55
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql-slaver
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 9
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 8
10
- version: 0.1.8
9
+ - 9
10
+ version: 0.1.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Salgado
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2014-04-17 00:00:00 Z
18
+ date: 2014-04-18 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec