repctl 0.0.3 → 0.0.4

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/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" , "Stop and start a server 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%-25s%-25s%-25s%-8s\n",
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
- if is_slave
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
- format = "%-5d%16s:%-8d"
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 += "%16s:%-8d%16s:%-8d%-8s"
220
- str = sprintf(format, i, master_file, master_pos, recv_file, recv_pos,
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 "mysql:dump", [master]
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 "mysql:restore", [slave]
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 "install sample config files to DIRECTORY", "After initial install, use these "
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
@@ -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 execute
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
- slave_connection.close if slave_connection
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
- !is_master?(instance)
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
 
@@ -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.
@@ -1,3 +1,3 @@
1
1
  module Repctl
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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.3
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-01-26 00:00:00.000000000Z
12
+ date: 2012-02-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &55282760 !ruby/object:Gem::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: *55282760
24
+ version_requirements: *76697300
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &55282340 !ruby/object:Gem::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: *55282340
35
+ version_requirements: *76696880
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mysql2
38
- requirement: &55281920 !ruby/object:Gem::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: *55281920
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