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