mysql-slaver 0.1.8 → 0.1.9

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/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