repctl 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/TODO +9 -0
- data/bin/repctl +45 -17
- data/config/mysql.cnf +3 -0
- data/lib/repctl/mysql_admin.rb +42 -73
- data/lib/repctl/servers.rb +9 -0
- data/lib/repctl/version.rb +1 -1
- metadata +9 -8
data/TODO
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
1. Add a slave to an existing slave without
|
2
|
+
breaking the first slave's relationship to
|
3
|
+
its master.
|
4
|
+
|
5
|
+
Use an option to add_slave?
|
6
|
+
|
7
|
+
2. If you stop a slave and then restart it, it
|
8
|
+
becomes a slave of instance 1, even if it was
|
9
|
+
slaving off a different master before the shutdown.
|
data/bin/repctl
CHANGED
@@ -22,6 +22,10 @@ module Repctl
|
|
22
22
|
run_mysql_query(instance, "START SLAVE")
|
23
23
|
end
|
24
24
|
|
25
|
+
def do_stop_slave(instance)
|
26
|
+
run_mysql_query(instance, "STOP SLAVE")
|
27
|
+
end
|
28
|
+
|
25
29
|
def do_restart(instance)
|
26
30
|
do_admin(instance, "shutown")
|
27
31
|
do_start(instance)
|
@@ -141,7 +145,7 @@ class RepctlCmds < Thor
|
|
141
145
|
end
|
142
146
|
end
|
143
147
|
|
144
|
-
desc "restart INSTANCE"
|
148
|
+
desc "restart INSTANCE", "Stop and start a server instance."
|
145
149
|
def restart(instance)
|
146
150
|
say "Restarting instance #{instance}", :green
|
147
151
|
do_stop(instance)
|
@@ -153,6 +157,12 @@ class RepctlCmds < Thor
|
|
153
157
|
say "Starting slave #{slave}", :green
|
154
158
|
do_start_slave(slave)
|
155
159
|
end
|
160
|
+
|
161
|
+
desc "stop_slave SLAVE", "Issue STOP SLAVE on the SLAVE MySQL instance."
|
162
|
+
def stop_slave(slave)
|
163
|
+
say "Stopping slave #{slave}", :green
|
164
|
+
do_stop_slave(slave)
|
165
|
+
end
|
156
166
|
|
157
167
|
desc "change_master MASTER SLAVE FILE POSITION", "Execute CHANGE MASTER TO on the SLAVE."
|
158
168
|
def change_master(master, slave, file, position)
|
@@ -160,6 +170,12 @@ class RepctlCmds < Thor
|
|
160
170
|
do_change_master(master, slave, :file => file, :position => position)
|
161
171
|
end
|
162
172
|
|
173
|
+
desc "switch_master MASTER SLAVES", "Change the master of a running cluster."
|
174
|
+
def switch_master(master, *slaves)
|
175
|
+
do_switch_master(master, slaves)
|
176
|
+
say "Switching to master #{master}", :green
|
177
|
+
end
|
178
|
+
|
163
179
|
desc "crash INSTANCE", "Crash a running server."
|
164
180
|
def crash(instance)
|
165
181
|
say "Crashing instance #{instance}", :red
|
@@ -186,10 +202,10 @@ class RepctlCmds < Thor
|
|
186
202
|
def status
|
187
203
|
todos = options[:servers] || all_live_instances
|
188
204
|
unless todos.any?
|
189
|
-
say "No Running Servers.", :blue
|
205
|
+
say "No Running Servers. Are you running as the mysql user?", :blue
|
190
206
|
return
|
191
207
|
end
|
192
|
-
header = sprintf("%-5s%-
|
208
|
+
header = sprintf("%-5s%-27s%-27s%-27s%-8s\n",
|
193
209
|
"inst", "master", "received", "applied", "lag")
|
194
210
|
|
195
211
|
loop do
|
@@ -197,29 +213,32 @@ class RepctlCmds < Thor
|
|
197
213
|
|
198
214
|
todos.each do |i|
|
199
215
|
coordinates = get_coordinates(i)
|
200
|
-
slave_status = get_slave_status(i)
|
201
|
-
is_slave = !(slave_status["Error"] == "MySQL server is not a slave.")
|
202
216
|
master_file = coordinates[:file]
|
203
217
|
master_pos = coordinates[:position]
|
204
|
-
|
218
|
+
slave = is_slave?(i)
|
219
|
+
if slave
|
220
|
+
slave_status = get_slave_status(i)
|
205
221
|
recv_file = slave_status["Master_Log_File"]
|
206
222
|
recv_pos = slave_status["Read_Master_Log_Pos"]
|
207
223
|
apply_file = slave_status["Relay_Master_Log_File"]
|
208
224
|
apply_pos = slave_status["Exec_Master_Log_Pos"]
|
209
225
|
lag = slave_status["Seconds_Behind_Master"]
|
226
|
+
master_host = slave_status["Master_Host"]
|
227
|
+
master_port = slave_status["Master_Port"]
|
228
|
+
master_instance = instance_for(master_host, master_port)
|
210
229
|
end
|
211
|
-
|
212
|
-
|
213
|
-
if is_slave
|
230
|
+
|
231
|
+
if slave
|
214
232
|
if lag
|
215
233
|
lag = lag.to_s
|
216
234
|
else
|
217
235
|
lag = "-"
|
218
236
|
end
|
219
|
-
format
|
220
|
-
str = sprintf(format, i,
|
221
|
-
apply_file, apply_pos, lag)
|
237
|
+
format = "%1d%-4s%16s:%-10d%16s:%-10d%16s:%-10d%-8s"
|
238
|
+
str = sprintf(format, i, "(#{master_instance.to_s})", master_file,
|
239
|
+
master_pos, recv_file, recv_pos, apply_file, apply_pos, lag)
|
222
240
|
else
|
241
|
+
format = "%-5d%16s:%-10d"
|
223
242
|
str = sprintf(format, i, master_file, master_pos)
|
224
243
|
end
|
225
244
|
|
@@ -257,14 +276,18 @@ class RepctlCmds < Thor
|
|
257
276
|
if options[:populate]
|
258
277
|
invoke "utils:bench", [master, "/opt/MySQL/b2.properties"]
|
259
278
|
end
|
260
|
-
file, position = invoke "
|
279
|
+
file, position = invoke "dump", [master]
|
261
280
|
|
262
281
|
# Slave is running, but it is not yet configured as a slave.
|
263
282
|
# Load slave from the dump file...
|
264
|
-
invoke "
|
283
|
+
invoke "restore", [slave]
|
265
284
|
|
266
285
|
do_change_master(master, slave, :file => file, :position => position)
|
267
286
|
do_start_slave(slave)
|
287
|
+
|
288
|
+
do_cluster_user(slave)
|
289
|
+
do_repl_user(slave)
|
290
|
+
|
268
291
|
end
|
269
292
|
|
270
293
|
DEFAULT_MASTER = 1
|
@@ -383,13 +406,18 @@ class RepctlCmds < Thor
|
|
383
406
|
do_reset(master)
|
384
407
|
do_reset(slave1)
|
385
408
|
do_reset(slave2)
|
386
|
-
do_cluster_user(master)
|
387
|
-
do_repl_user(master)
|
388
409
|
else
|
389
410
|
do_restart(master)
|
390
411
|
do_restart(slave1)
|
391
412
|
do_restart(slave2)
|
392
413
|
end
|
414
|
+
# Set up the replication accounts for all servers, in case we
|
415
|
+
# decide to switch masters later.
|
416
|
+
[master, slave1, slave2].each do |s|
|
417
|
+
do_cluster_user(s)
|
418
|
+
do_repl_user(s)
|
419
|
+
end
|
420
|
+
|
393
421
|
coordinates = get_coordinates(master)
|
394
422
|
file = coordinates[:file]
|
395
423
|
position = coordinates[:position]
|
@@ -400,7 +428,7 @@ class RepctlCmds < Thor
|
|
400
428
|
do_start_slave(slave2)
|
401
429
|
end
|
402
430
|
|
403
|
-
desc "
|
431
|
+
desc "install_sample_configs DIRECTORY", "After initial install, use these "
|
404
432
|
"as templates for configuration. The target directory must exist and be empty."
|
405
433
|
def install_sample_configs(dest_dir)
|
406
434
|
source_dir = File.join(Repctl::Config.const_get(:GEM_HOME), "config")
|
data/config/mysql.cnf
CHANGED
@@ -39,6 +39,8 @@ query_cache_size= 16M
|
|
39
39
|
# Try number of CPU's*2 for thread_concurrency
|
40
40
|
thread_concurrency = 8
|
41
41
|
plugin-dir=/usr/local/mysql/lib/plugin
|
42
|
+
general-log=1
|
43
|
+
log-output=FILE # or TABLE
|
42
44
|
|
43
45
|
# Don't listen on a TCP/IP port at all. This can be a security enhancement,
|
44
46
|
# if all processes that need to connect to mysqld run on the same host.
|
@@ -52,6 +54,7 @@ plugin-dir=/usr/local/mysql/lib/plugin
|
|
52
54
|
# binary logging is required for replication
|
53
55
|
log-bin=mysql-bin
|
54
56
|
log-slave-updates
|
57
|
+
replicate-ignore-db = china
|
55
58
|
|
56
59
|
# binary logging format - mixed recommended
|
57
60
|
binlog_format=mixed
|
data/lib/repctl/mysql_admin.rb
CHANGED
@@ -82,6 +82,8 @@ module Repctl
|
|
82
82
|
# do_repl_user
|
83
83
|
# do_cluster_user
|
84
84
|
# do_create_widgets
|
85
|
+
# do_switch_master
|
86
|
+
# do_stop_slave
|
85
87
|
# get_coordinates
|
86
88
|
# get_mysqld_pid
|
87
89
|
# get_slave_status
|
@@ -186,7 +188,9 @@ module Repctl
|
|
186
188
|
"Relay_Master_Log_File",
|
187
189
|
"Exec_Master_Log_Pos",
|
188
190
|
"Relay_Log_File",
|
189
|
-
"Relay_Log_Pos"
|
191
|
+
"Relay_Log_Pos",
|
192
|
+
"Master_Host",
|
193
|
+
"Master_Port"
|
190
194
|
]
|
191
195
|
results = {}
|
192
196
|
status = do_slave_status(instance)
|
@@ -232,18 +236,18 @@ module Repctl
|
|
232
236
|
# set global innodb_max_dirty_pages_pct = 75;
|
233
237
|
#
|
234
238
|
|
235
|
-
def do_change_master(master, slave, coordinates)
|
239
|
+
def do_change_master(master, slave, coordinates, opts = {})
|
236
240
|
master_server = server_for_instance(master)
|
241
|
+
raise "master_server is nil" unless master_server
|
242
|
+
|
237
243
|
begin
|
238
244
|
slave_connection = Client.open(slave)
|
239
245
|
if slave_connection
|
240
246
|
|
241
|
-
# Replication on the slave can't be running if we want to
|
242
|
-
# CHANGE MASTER TO.
|
247
|
+
# Replication on the slave can't be running if we want to
|
248
|
+
# execute CHANGE MASTER TO.
|
243
249
|
slave_connection.query("STOP SLAVE") rescue Mysql2::Error
|
244
250
|
|
245
|
-
raise "master_server is nil" unless master_server
|
246
|
-
|
247
251
|
cmd = <<-EOT
|
248
252
|
CHANGE MASTER TO
|
249
253
|
MASTER_HOST = \'#{master_server['hostname']}\',
|
@@ -261,7 +265,10 @@ EOT
|
|
261
265
|
rescue Mysql2::Error => e
|
262
266
|
puts e.message
|
263
267
|
ensure
|
264
|
-
|
268
|
+
if slave_connection
|
269
|
+
slave_connection.query("START SLAVE") if opts[:restart]
|
270
|
+
slave_connection.close
|
271
|
+
end
|
265
272
|
end
|
266
273
|
end
|
267
274
|
|
@@ -416,6 +423,31 @@ EOT
|
|
416
423
|
Integer(File.open(pidfile, &:readline).strip)
|
417
424
|
end
|
418
425
|
|
426
|
+
# 'master' is currently a slave that is to be the new master.
|
427
|
+
# 'slaves' contains the list of slaves, one of these may be the
|
428
|
+
# current master.
|
429
|
+
def do_switch_master(master, slaves)
|
430
|
+
master = master.to_i
|
431
|
+
slaves = slaves.map(&:to_i)
|
432
|
+
|
433
|
+
# Step 1. Make sure all slaves have completely processed their
|
434
|
+
# Relay Log.
|
435
|
+
slaves.each do |s|
|
436
|
+
# This will also stop the slave threads.
|
437
|
+
drain_relay_log(s) if is_slave?(s)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Step 2. For the slave being promoted to master, issue STOP SLAVE
|
441
|
+
# and RESET MASTER. Get the coordinates of its binlog.
|
442
|
+
promote_slave_to_master(master)
|
443
|
+
coordinates = get_coordinates(master)
|
444
|
+
|
445
|
+
# Step 3. Change the master for the other slaves.
|
446
|
+
slaves.each do |s|
|
447
|
+
do_change_master(master, s, coordinates, :restart => true)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
419
451
|
private
|
420
452
|
|
421
453
|
# This is an example template to create commands to issue queries.
|
@@ -433,54 +465,13 @@ EOT
|
|
433
465
|
client.close if client
|
434
466
|
end
|
435
467
|
|
436
|
-
# 'master' is the new master
|
437
|
-
# 'slaves' is contains the list of slaves, one of these may be the current master.
|
438
|
-
def switch_master_to(master, slaves)
|
439
|
-
|
440
|
-
slaves = Array(slaves)
|
441
|
-
|
442
|
-
# Step 1. Make sure all slaves have completely processed their
|
443
|
-
# Relay Log.
|
444
|
-
slaves.each do |s|
|
445
|
-
puts "Draining relay log for slave instance #{s}"
|
446
|
-
drain_relay_log(s) if is_slave?(s)
|
447
|
-
end
|
448
|
-
|
449
|
-
# Step 2. For the slave being promoted to master, issue STOP SLAVE
|
450
|
-
# and RESET MASTER.
|
451
|
-
client = Client.open(master)
|
452
|
-
client.query("STOP SLAVE")
|
453
|
-
client.query("RESET MASTER")
|
454
|
-
client.close
|
455
|
-
|
456
|
-
# Step 3. Change the master for the other slaves (and the former master?)
|
457
|
-
# XXX this is not complete -- what about the coordinates?
|
458
|
-
master_server = server_for_instance(master)
|
459
|
-
master_host = 'localhost' # should be master_server[:hostname]
|
460
|
-
master_user = 'repl'
|
461
|
-
master_password = 'har526'
|
462
|
-
master_port = master_server[:port]
|
463
|
-
slaves.each do |s|
|
464
|
-
client = Client.open(s)
|
465
|
-
cmd = <<-EOT
|
466
|
-
CHANGE MASTER TO
|
467
|
-
MASTER_HOST=\'#{master_host}\',
|
468
|
-
MASTER_PORT=#{master_port},
|
469
|
-
MASTER_USER=\'#{master_user}\',
|
470
|
-
MASTER_PASSWORD=\'#{master_password}\'
|
471
|
-
EOT
|
472
|
-
client.query(cmd)
|
473
|
-
client.query("START SLAVE")
|
474
|
-
client.close
|
475
|
-
end
|
476
|
-
end
|
477
468
|
|
478
469
|
def is_master?(instance)
|
479
470
|
get_slave_coordinates(instance).empty?
|
480
471
|
end
|
481
472
|
|
482
473
|
def is_slave?(instance)
|
483
|
-
|
474
|
+
get_slave_status(instance).has_key?("Slave_IO_State")
|
484
475
|
end
|
485
476
|
|
486
477
|
def find_masters()
|
@@ -513,7 +504,6 @@ EOT
|
|
513
504
|
client.close if client
|
514
505
|
end
|
515
506
|
|
516
|
-
# unused
|
517
507
|
def promote_slave_to_master(instance)
|
518
508
|
client = Client.open(instance)
|
519
509
|
if client
|
@@ -566,7 +556,8 @@ EOT
|
|
566
556
|
end
|
567
557
|
end
|
568
558
|
puts "Waiting for slave to read relay log." unless done
|
569
|
-
end
|
559
|
+
end
|
560
|
+
client.query("STOP SLAVE")
|
570
561
|
else
|
571
562
|
puts "Could not open connection to instance #{instance}."
|
572
563
|
end
|
@@ -574,24 +565,6 @@ EOT
|
|
574
565
|
client.close if client
|
575
566
|
end
|
576
567
|
|
577
|
-
=begin
|
578
|
-
# This is an example template to create commands to issue queries.
|
579
|
-
def do_slave_status(instance)
|
580
|
-
client = Client.open(instance)
|
581
|
-
if client
|
582
|
-
results = client.query("SHOW SLAVE STATUS")
|
583
|
-
results.each_with_index do |index, line|
|
584
|
-
puts "#{index}: #{line}"
|
585
|
-
end
|
586
|
-
else
|
587
|
-
puts "Could not open connection to MySQL instance #{instance}."
|
588
|
-
end
|
589
|
-
rescue Mysql2::Error => e
|
590
|
-
puts e.message
|
591
|
-
ensure
|
592
|
-
client.close if client
|
593
|
-
end
|
594
|
-
=end
|
595
568
|
#
|
596
569
|
# Get the status of replication for the master and all slaves.
|
597
570
|
# Return an array of hashes, each hash has the form:
|
@@ -629,10 +602,6 @@ EOT
|
|
629
602
|
end
|
630
603
|
end
|
631
604
|
|
632
|
-
# STOP SLAVE
|
633
|
-
# RESET MASTER
|
634
|
-
# CHANGE MASTER TO
|
635
|
-
|
636
605
|
class RunExamples
|
637
606
|
include Repctl::Commands
|
638
607
|
|
data/lib/repctl/servers.rb
CHANGED
@@ -34,6 +34,15 @@ module Repctl
|
|
34
34
|
get_mysqld_pid(instance)
|
35
35
|
end
|
36
36
|
|
37
|
+
def instance_for(host, port)
|
38
|
+
@servers.each do |s|
|
39
|
+
if s["hostname"] == host && s["port"].to_i == port.to_i
|
40
|
+
return s["instance"].to_i
|
41
|
+
end
|
42
|
+
end
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
|
37
46
|
private
|
38
47
|
|
39
48
|
# Return the process ID (pid) for an instance.
|
data/lib/repctl/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: repctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-02-17 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &76697300 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *76697300
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: thor
|
27
|
-
requirement: &
|
27
|
+
requirement: &76696880 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *76696880
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mysql2
|
38
|
-
requirement: &
|
38
|
+
requirement: &76696460 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *76696460
|
47
47
|
description: Ruby gem with Thor script to manage MySQL and PostgreSQL replication
|
48
48
|
email:
|
49
49
|
- lydianblues@gmail.com
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- Gemfile
|
57
57
|
- README.md
|
58
58
|
- Rakefile
|
59
|
+
- TODO
|
59
60
|
- bin/repctl
|
60
61
|
- config/bristlecone.properties
|
61
62
|
- config/config.rb
|