repctl 0.0.1
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +80 -0
- data/Rakefile +1 -0
- data/bin/mysql.thor +431 -0
- data/config/bristlecone.properties +29 -0
- data/config/config.rb +50 -0
- data/config/mysql.cnf +149 -0
- data/config/servers.yml +45 -0
- data/lib/repctl/mysql_admin.rb +638 -0
- data/lib/repctl/servers.rb +48 -0
- data/lib/repctl/version.rb +3 -0
- data/lib/repctl.rb +9 -0
- data/repctl.gemspec +25 -0
- metadata +94 -0
@@ -0,0 +1,638 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'delegate'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Repctl
|
7
|
+
|
8
|
+
class Client < DelegateClass(Mysql2::Client)
|
9
|
+
include Servers
|
10
|
+
|
11
|
+
@@clients = {}
|
12
|
+
|
13
|
+
def initialize(instance, opts)
|
14
|
+
@instance = instance
|
15
|
+
server = server_for_instance(@instance)
|
16
|
+
options = {
|
17
|
+
:host => server['hostname'],
|
18
|
+
:username => "root",
|
19
|
+
:port => server['port'],
|
20
|
+
:password => Config::ROOT_PASSWORD
|
21
|
+
}
|
22
|
+
options.delete(:password) if opts[:no_password]
|
23
|
+
@client = Mysql2::Client.new(options)
|
24
|
+
super(@client)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.open(instance, opts = {})
|
28
|
+
timeout = opts[:timeout] || 10
|
29
|
+
opts.delete(:timeout)
|
30
|
+
begin
|
31
|
+
instance = Integer(instance)
|
32
|
+
rescue Mysql2::Error => e
|
33
|
+
puts "Instance value <#{instance}> is invalid."
|
34
|
+
else
|
35
|
+
timeout = Integer(timeout)
|
36
|
+
while timeout >= 0
|
37
|
+
begin
|
38
|
+
@@clients[instance] ||= Client.new(instance, opts)
|
39
|
+
# puts "Connected to instance #{instance}."
|
40
|
+
break
|
41
|
+
rescue Mysql2::Error => e
|
42
|
+
puts "#{e.message}, retrying connection to instance #{instance}..."
|
43
|
+
# puts e.backtrace
|
44
|
+
sleep 1
|
45
|
+
timeout -= 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@@clients[instance]
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@@clients[@instance] = nil
|
54
|
+
@client.close
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset
|
58
|
+
@client.close
|
59
|
+
@@clients[@instance] = nil
|
60
|
+
Client.open(@instance)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
module Commands
|
66
|
+
|
67
|
+
include FileUtils
|
68
|
+
include Repctl::Config
|
69
|
+
include Servers
|
70
|
+
|
71
|
+
def do_secure_accounts(instance)
|
72
|
+
client = Client.open(instance, :no_password => true)
|
73
|
+
q1 = "UPDATE mysql.user SET Password = PASSWORD(\'#{ROOT_PASSWORD}\') where User = \'root\'"
|
74
|
+
q2 = "UPDATE mysql.user SET Password = PASSWORD(\'#{ROOT_PASSWORD}\') where User = \'\'"
|
75
|
+
# q3 = "CREATE USER \'root\'@\'%.#{REPLICATION_DOMAIN}\' IDENTIFIED BY \'#{ROOT_PASSWORD}\'"
|
76
|
+
# For testing with clients whose DHCP assigned IP address is not in DNS.
|
77
|
+
q3 = "CREATE USER \'root\'@\'%' IDENTIFIED BY \'#{ROOT_PASSWORD}\'"
|
78
|
+
q4 = "GRANT ALL PRIVILEGES ON *.* to \'root\'@\'%\' WITH GRANT OPTION"
|
79
|
+
q5 = "FLUSH PRIVILEGES"
|
80
|
+
if client
|
81
|
+
[q1, q2, q3, q4, q5].each do |query|
|
82
|
+
puts query
|
83
|
+
client.query(query)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
rescue Mysql2::Error => e
|
87
|
+
puts e.message
|
88
|
+
ensure
|
89
|
+
client.close if client
|
90
|
+
end
|
91
|
+
|
92
|
+
def do_start(instance)
|
93
|
+
pid = get_mysqld_pid(instance)
|
94
|
+
if pid
|
95
|
+
puts "Instance #{instance} with PID #{pid} is already running."
|
96
|
+
else
|
97
|
+
pid = fork
|
98
|
+
unless pid
|
99
|
+
# We're in the child.
|
100
|
+
puts "Starting instance #{instance} with PID #{Process.pid}."
|
101
|
+
server = server_for_instance(instance)
|
102
|
+
|
103
|
+
exec(["#{MYSQL_HOME}/bin/mysqld", "mysqld"],
|
104
|
+
"--defaults-file=#{server['defaults-file']}",
|
105
|
+
"--datadir=#{server['datadir']}",
|
106
|
+
"--port=#{server['port']}",
|
107
|
+
"--server-id=#{server['server-id']}",
|
108
|
+
"--innodb_data_home_dir=#{server['innodb_data_home_dir']}",
|
109
|
+
"--innodb_log_group_home_dir=#{server['innodb_log_group_home_dir']}",
|
110
|
+
"--relay-log=#{Socket.gethostname}-relay-bin",
|
111
|
+
"--socket=#{server['socket']}",
|
112
|
+
"--user=mysql")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def do_admin(instance, operation)
|
118
|
+
server = server_for_instance(instance)
|
119
|
+
|
120
|
+
cmd = "#{MYSQL_HOME}/bin/mysqladmin " +
|
121
|
+
"--defaults-file=#{server['defaults-file']} " +
|
122
|
+
"--user=root " +
|
123
|
+
"--host=#{server['hostname']} " +
|
124
|
+
"--port=#{server['port']} " +
|
125
|
+
"--password=#{ROOT_PASSWORD} " +
|
126
|
+
operation
|
127
|
+
|
128
|
+
pid = get_mysqld_pid(instance)
|
129
|
+
if pid
|
130
|
+
puts "Running #{operation} on instance #{instance} with pid #{pid}."
|
131
|
+
run_cmd(cmd, false)
|
132
|
+
else
|
133
|
+
puts "Instance #{instance} is not running."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def do_config(instance)
|
138
|
+
server = server_for_instance(instance)
|
139
|
+
FileUtils.rm_rf(server['datadir'])
|
140
|
+
cmd = "./scripts/mysql_install_db " +
|
141
|
+
"--defaults-file=#{server['defaults-file']} " +
|
142
|
+
"--datadir=#{server['datadir']} " +
|
143
|
+
"--server-id=#{server['server-id']} " +
|
144
|
+
"--innodb_data_home_dir=#{server['innodb_data_home_dir']} " +
|
145
|
+
"--innodb_log_group_home_dir=#{server['innodb_log_group_home_dir']} " +
|
146
|
+
"--relay-log=#{Socket.gethostname}-relay-bin"
|
147
|
+
%x( cd #{MYSQL_HOME} && #{cmd} )
|
148
|
+
end
|
149
|
+
|
150
|
+
def do_status(instance)
|
151
|
+
|
152
|
+
status = get_coordinates(instance)
|
153
|
+
puts status
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Treat the instance as a slave and
|
158
|
+
# process the output of "SHOW SLAVE STATUS".
|
159
|
+
#
|
160
|
+
def get_slave_status(instance)
|
161
|
+
keys = [
|
162
|
+
"Instance",
|
163
|
+
"Error",
|
164
|
+
"Slave_IO_State",
|
165
|
+
"Slave_IO_Running",
|
166
|
+
"Slave_SQL_Running",
|
167
|
+
"Last_IO_Error",
|
168
|
+
"Last_SQL_Error",
|
169
|
+
"Seconds_Behind_Master",
|
170
|
+
"Master_Log_File",
|
171
|
+
"Read_Master_Log_Pos",
|
172
|
+
"Relay_Master_Log_File",
|
173
|
+
"Exec_Master_Log_Pos",
|
174
|
+
"Relay_Log_File",
|
175
|
+
"Relay_Log_Pos"
|
176
|
+
]
|
177
|
+
results = {}
|
178
|
+
status = do_slave_status(instance)
|
179
|
+
keys.each do |k|
|
180
|
+
results.merge!(k => status[k]) if (status[k] and status[k] != "")
|
181
|
+
end
|
182
|
+
results
|
183
|
+
end
|
184
|
+
|
185
|
+
def do_crash(instance)
|
186
|
+
pid = get_mysqld_pid(instance)
|
187
|
+
puts "pid is #{pid}"
|
188
|
+
if pid
|
189
|
+
puts "Killing mysqld instance #{instance} with PID #{pid}"
|
190
|
+
Process.kill("KILL", pid.to_i)
|
191
|
+
while get_mysqld_pid(instance)
|
192
|
+
puts "in looop"
|
193
|
+
sleep 1
|
194
|
+
end
|
195
|
+
puts "MySQL server instance #{instance.to_i} has been killed."
|
196
|
+
else
|
197
|
+
puts "MySQL server instance #{instance.to_i} is not running."
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def is_master?(instance)
|
202
|
+
get_slave_coordinates(instance).empty?
|
203
|
+
end
|
204
|
+
|
205
|
+
def is_slave?(instance)
|
206
|
+
!is_master?(instance)
|
207
|
+
end
|
208
|
+
|
209
|
+
# This is an example template to create commands to issue queries.
|
210
|
+
def do_slave_status(instance)
|
211
|
+
client = Client.open(instance)
|
212
|
+
if client
|
213
|
+
results = client.query("SHOW SLAVE STATUS")
|
214
|
+
results.each_with_index do |index, line|
|
215
|
+
puts "#{index}: #{line}"
|
216
|
+
end
|
217
|
+
else
|
218
|
+
puts "Could not open connection to MySQL instance #{instance}."
|
219
|
+
end
|
220
|
+
rescue Mysql2::Error => e
|
221
|
+
puts e.message
|
222
|
+
ensure
|
223
|
+
client.close if client
|
224
|
+
end
|
225
|
+
|
226
|
+
def find_masters()
|
227
|
+
masters = []
|
228
|
+
all_servers.each do |s|
|
229
|
+
masters << s if is_master?(s)
|
230
|
+
end
|
231
|
+
masters
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# From http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html:
|
236
|
+
#
|
237
|
+
# For a filesystem snapshot of innodb, we find that setting
|
238
|
+
# innodb_max_dirty_pages_pct to zero; doing a 'flush tables with
|
239
|
+
# readlock'; and then waiting for the innodb state to reach 'Main thread
|
240
|
+
# process no. \d+, id \d+, state: waiting for server activity' is
|
241
|
+
# sufficient to quiesce innodb.
|
242
|
+
#
|
243
|
+
# You will also need to issue a slave stop if you're backing up a slave
|
244
|
+
# whose relay logs are being written to its data directory.
|
245
|
+
#
|
246
|
+
#
|
247
|
+
# select @@innodb_max_dirty_pages_pct;
|
248
|
+
# flush tables with read lock;
|
249
|
+
# show master status;
|
250
|
+
# ...freeze filesystem; do backup...
|
251
|
+
# set global innodb_max_dirty_pages_pct = 75;
|
252
|
+
#
|
253
|
+
|
254
|
+
def do_change_master(master, slave, coordinates)
|
255
|
+
master_server = server_for_instance(master)
|
256
|
+
begin
|
257
|
+
slave_connection = Client.open(slave)
|
258
|
+
if slave_connection
|
259
|
+
|
260
|
+
# Replication on the slave can't be running if we want to execute
|
261
|
+
# CHANGE MASTER TO.
|
262
|
+
slave_connection.query("STOP SLAVE") rescue Mysql2::Error
|
263
|
+
|
264
|
+
raise "master_server is nil" unless master_server
|
265
|
+
|
266
|
+
cmd = <<-EOT
|
267
|
+
CHANGE MASTER TO
|
268
|
+
MASTER_HOST = \'#{master_server['hostname']}\',
|
269
|
+
MASTER_PORT = #{master_server['port']},
|
270
|
+
MASTER_USER = \'#{REPLICATION_USER}\',
|
271
|
+
MASTER_PASSWORD = \'#{REPLICATION_PASSWORD}\',
|
272
|
+
MASTER_LOG_FILE = \'#{coordinates[:file]}\',
|
273
|
+
MASTER_LOG_POS = #{coordinates[:position]}
|
274
|
+
EOT
|
275
|
+
puts "Executing: #{cmd}"
|
276
|
+
slave_connection.query(cmd)
|
277
|
+
else
|
278
|
+
puts "do_change_master: Could not connnect to MySQL server."
|
279
|
+
end
|
280
|
+
rescue Mysql2::Error => e
|
281
|
+
puts e.message
|
282
|
+
ensure
|
283
|
+
slave_connection.close if slave_connection
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
def do_dump(instance, dumpfile)
|
289
|
+
server = server_for_instance(instance)
|
290
|
+
coordinates = get_coordinates(instance) do
|
291
|
+
cmd = "#{MYSQL_HOME}/bin/mysqldump " +
|
292
|
+
"--defaults-file=#{server['defaults-file']} " +
|
293
|
+
"--user=root " +
|
294
|
+
"--password=#{ROOT_PASSWORD} " +
|
295
|
+
"--socket=#{server['socket']} " +
|
296
|
+
"--all-databases --lock-all-tables > #{DUMP_DIR}/#{dumpfile}"
|
297
|
+
run_cmd(cmd, true)
|
298
|
+
end
|
299
|
+
coordinates
|
300
|
+
end
|
301
|
+
|
302
|
+
def do_restore(instance, dumpfile)
|
303
|
+
server = server_for_instance(instance)
|
304
|
+
|
305
|
+
# Assumes that the instance is running, but is not acting as a slave.
|
306
|
+
cmd = "#{MYSQL_HOME}/bin/mysql " +
|
307
|
+
"--defaults-file=#{server['defaults-file']} " +
|
308
|
+
"--user=root " +
|
309
|
+
"--password=#{ROOT_PASSWORD} " +
|
310
|
+
"--socket=#{server['socket']} " +
|
311
|
+
"< #{DUMP_DIR}/#{dumpfile}"
|
312
|
+
run_cmd(cmd, true)
|
313
|
+
end
|
314
|
+
|
315
|
+
#
|
316
|
+
# Get the status of replication for the master and all slaves.
|
317
|
+
# Return an array of hashes, each hash has the form:
|
318
|
+
# {:instance => <instance id>, :error => <errrmsg>,
|
319
|
+
# :master_file => <binlog-file-name>, :master_position => <binlog-position>,
|
320
|
+
# :slave_file => <binlog-file-name>, :slave_position => <binlog-position>}
|
321
|
+
#
|
322
|
+
def do_slave_status(instance)
|
323
|
+
instance ||= DEFAULT_MASTER
|
324
|
+
locked = false
|
325
|
+
client = Client.open(instance, :timeout => 5)
|
326
|
+
if client
|
327
|
+
client.query("FLUSH TABLES WITH READ LOCK")
|
328
|
+
locked = true
|
329
|
+
results = client.query("SHOW SLAVE STATUS")
|
330
|
+
if results.first
|
331
|
+
results.first.merge("Instance" => instance, "Error" => "Success")
|
332
|
+
else
|
333
|
+
{"Instance" => instance, "Error" => "MySQL server is not a slave."}
|
334
|
+
end
|
335
|
+
else
|
336
|
+
{"Instance" => instance, "Error" => "Could not connect to MySQL server."}
|
337
|
+
end
|
338
|
+
rescue Mysql2::Error => e
|
339
|
+
{:instance => instance, "Error" => e.message}
|
340
|
+
ensure
|
341
|
+
if client
|
342
|
+
client.query("UNLOCK TABLES") if locked
|
343
|
+
client.close
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Get the master coordinates from a MySQL instance. Optionally,
|
348
|
+
# run a block while holding the READ LOCK.
|
349
|
+
def get_coordinates(instance)
|
350
|
+
instance ||= DEFAULT_MASTER
|
351
|
+
locked = false
|
352
|
+
client = Client.open(instance)
|
353
|
+
if client
|
354
|
+
client.query("FLUSH TABLES WITH READ LOCK")
|
355
|
+
locked = true
|
356
|
+
results = client.query("SHOW MASTER STATUS")
|
357
|
+
row = results.first
|
358
|
+
coordinates = if row
|
359
|
+
{:file => row["File"], :position => row["Position"]}
|
360
|
+
else
|
361
|
+
{}
|
362
|
+
end
|
363
|
+
yield coordinates if block_given?
|
364
|
+
# You could copy data from the master to the slave at this point
|
365
|
+
end
|
366
|
+
coordinates
|
367
|
+
rescue Mysql2::Error => e
|
368
|
+
puts e.message
|
369
|
+
# puts e.backtrace
|
370
|
+
ensure
|
371
|
+
if client
|
372
|
+
client.query("UNLOCK TABLES") if locked
|
373
|
+
client.close
|
374
|
+
end
|
375
|
+
# coordinates
|
376
|
+
end
|
377
|
+
|
378
|
+
def get_slave_coordinates(instance)
|
379
|
+
client = Client.open(instance)
|
380
|
+
if client
|
381
|
+
results = client.query("SHOW SLAVE STATUS")
|
382
|
+
row = results.first
|
383
|
+
if row
|
384
|
+
{:file => row["Master_Log_File"], :position => row["Read_Master_Log_Pos"]}
|
385
|
+
else
|
386
|
+
{}
|
387
|
+
end
|
388
|
+
end
|
389
|
+
ensure
|
390
|
+
client.close if client
|
391
|
+
end
|
392
|
+
|
393
|
+
def stop_slave_io_thread(instance)
|
394
|
+
client = Client.open(instance)
|
395
|
+
if client
|
396
|
+
client.query("STOP SLAVE IO_THREAD")
|
397
|
+
end
|
398
|
+
ensure
|
399
|
+
client.close if client
|
400
|
+
end
|
401
|
+
|
402
|
+
def run_mysql_query(instance, cmd)
|
403
|
+
client = Client.open(instance)
|
404
|
+
if client
|
405
|
+
results = client.query(cmd)
|
406
|
+
else
|
407
|
+
puts "Could not open connection to MySQL instance."
|
408
|
+
end
|
409
|
+
results
|
410
|
+
rescue Mysql2::Error => e
|
411
|
+
puts e.message
|
412
|
+
puts e.backtrace
|
413
|
+
ensure
|
414
|
+
client.close if client
|
415
|
+
end
|
416
|
+
|
417
|
+
def start_slave_io_thread(instance)
|
418
|
+
client = Client.open(instance)
|
419
|
+
if client
|
420
|
+
client.query("START SLAVE IO_THREAD")
|
421
|
+
end
|
422
|
+
ensure
|
423
|
+
client.close if client
|
424
|
+
end
|
425
|
+
|
426
|
+
def promote_slave_to_master(instance)
|
427
|
+
client = Client.open(instance)
|
428
|
+
if client
|
429
|
+
client.query("STOP SLAVE")
|
430
|
+
client.query("RESET MASTER")
|
431
|
+
end
|
432
|
+
ensure
|
433
|
+
client.close if client
|
434
|
+
end
|
435
|
+
|
436
|
+
def drain_relay_log(instance)
|
437
|
+
done = false
|
438
|
+
stop_slave_io_thread(instance)
|
439
|
+
client = Client.open(instance)
|
440
|
+
if client
|
441
|
+
|
442
|
+
# If the slave 'sql_thread' is not running, this will loop forever.
|
443
|
+
while !done
|
444
|
+
results = client.query("SHOW PROCESSLIST")
|
445
|
+
results.each do |row|
|
446
|
+
if row["State"] =~ /Slave has read all relay log/
|
447
|
+
done = true
|
448
|
+
puts "Slave has read all relay log."
|
449
|
+
break
|
450
|
+
end
|
451
|
+
end
|
452
|
+
puts "Waiting for slave to read relay log." unless done
|
453
|
+
end
|
454
|
+
else
|
455
|
+
puts "Could not open connection to instance #{instance}."
|
456
|
+
end
|
457
|
+
ensure
|
458
|
+
client.close if client
|
459
|
+
end
|
460
|
+
|
461
|
+
# 'master' is the new master
|
462
|
+
# 'slaves' is contains the list of slaves, one of these may be the current master.
|
463
|
+
def switch_master_to(master, slaves)
|
464
|
+
|
465
|
+
slaves = Array(slaves)
|
466
|
+
|
467
|
+
# Step 1. Make sure all slaves have completely processed their
|
468
|
+
# Relay Log.
|
469
|
+
slaves.each do |s|
|
470
|
+
puts "Draining relay log for slave instance #{s}"
|
471
|
+
drain_relay_log(s) if is_slave?(s)
|
472
|
+
end
|
473
|
+
|
474
|
+
# Step 2. For the slave being promoted to master, issue STOP SLAVE
|
475
|
+
# and RESET MASTER.
|
476
|
+
client = Client.open(master)
|
477
|
+
client.query("STOP SLAVE")
|
478
|
+
client.query("RESET MASTER")
|
479
|
+
client.close
|
480
|
+
|
481
|
+
# Step 3. Change the master for the other slaves (and the former master?)
|
482
|
+
# XXX this is not complete -- what about the coordinates?
|
483
|
+
master_server = server_for_instance(master)
|
484
|
+
master_host = 'localhost' # should be master_server[:hostname]
|
485
|
+
master_user = 'repl'
|
486
|
+
master_password = 'har526'
|
487
|
+
master_port = master_server[:port]
|
488
|
+
slaves.each do |s|
|
489
|
+
client = Client.open(s)
|
490
|
+
cmd = <<-EOT
|
491
|
+
CHANGE MASTER TO
|
492
|
+
MASTER_HOST=\'#{master_host}\',
|
493
|
+
MASTER_PORT=#{master_port},
|
494
|
+
MASTER_USER=\'#{master_user}\',
|
495
|
+
MASTER_PASSWORD=\'#{master_password}\'
|
496
|
+
EOT
|
497
|
+
client.query(cmd)
|
498
|
+
client.query("START SLAVE")
|
499
|
+
client.close
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def do_repl_user(instance)
|
504
|
+
hostname = "127.0.0.1"
|
505
|
+
client = Client.open(instance)
|
506
|
+
cmd = "DROP USER \'#{REPLICATION_USER}\'@\'#{hostname}\'"
|
507
|
+
client.query(cmd) rescue Mysql2::Error
|
508
|
+
|
509
|
+
if client
|
510
|
+
# "CREATE USER \'#{REPLICATION_USER\'@'%.thirdmode.com' IDENTIFIED BY \'#{REPLICATION_PASSWORD}\'"
|
511
|
+
# "GRANT REPLICATION SLAVE ON *.* TO \'#{REPLICATON_USER}\'@\'%.#{REPLICATION_DOMAIN}\'"
|
512
|
+
cmd = "CREATE USER \'#{REPLICATION_USER}\'@\'#{hostname}\' IDENTIFIED BY \'#{REPLICATION_PASSWORD}\'"
|
513
|
+
puts cmd
|
514
|
+
client.query(cmd)
|
515
|
+
cmd = "GRANT REPLICATION SLAVE ON *.* TO \'#{REPLICATION_USER}\'@\'#{hostname}\'"
|
516
|
+
puts cmd
|
517
|
+
client.query(cmd)
|
518
|
+
client.query("FLUSH PRIVILEGES")
|
519
|
+
else
|
520
|
+
puts "Could not open connection to MySQL instance #{instance}."
|
521
|
+
end
|
522
|
+
rescue Mysql2::Error => e
|
523
|
+
puts e.message
|
524
|
+
puts e.backtrace
|
525
|
+
ensure
|
526
|
+
client.close if client
|
527
|
+
end
|
528
|
+
|
529
|
+
|
530
|
+
def do_cluster_user(instance)
|
531
|
+
client = Client.open(instance)
|
532
|
+
|
533
|
+
cmd = "DROP USER \'cluster\'@\'localhost\'"
|
534
|
+
client.query(cmd) rescue Mysql2::Error
|
535
|
+
|
536
|
+
cmd = "DROP USER \'cluster\'@\'%\'"
|
537
|
+
client.query(cmd) rescue Mysql2::Error
|
538
|
+
|
539
|
+
if client
|
540
|
+
cmd = "CREATE USER \'cluster\'@\'localhost\' IDENTIFIED BY \'secret\'"
|
541
|
+
client.query(cmd)
|
542
|
+
cmd = "GRANT ALL PRIVILEGES ON *.* TO \'cluster\'@'\localhost\'"
|
543
|
+
client.query(cmd)
|
544
|
+
cmd = "CREATE USER \'cluster\'@\'%\' IDENTIFIED BY \'secret\'"
|
545
|
+
client.query(cmd)
|
546
|
+
cmd = "GRANT ALL PRIVILEGES ON *.* TO \'cluster\'@\'%\'"
|
547
|
+
client.query(cmd)
|
548
|
+
else
|
549
|
+
puts "Could not open connection to MySQL instance #{instance}."
|
550
|
+
end
|
551
|
+
rescue Mysql2::Error => e
|
552
|
+
puts e.message
|
553
|
+
puts e.backtrace
|
554
|
+
ensure
|
555
|
+
client.close if client
|
556
|
+
end
|
557
|
+
|
558
|
+
# Create the 'widgets' database.
|
559
|
+
def do_create_widgets(instance)
|
560
|
+
client = Client.open(instance)
|
561
|
+
if client
|
562
|
+
client.query("drop database if exists widgets")
|
563
|
+
client.query("create database widgets")
|
564
|
+
else
|
565
|
+
puts "Could not open connection to MySQL instance #{instance}."
|
566
|
+
end
|
567
|
+
rescue Mysql2::Error => e
|
568
|
+
puts e.message
|
569
|
+
puts e.backtrace
|
570
|
+
ensure
|
571
|
+
client.close if client
|
572
|
+
end
|
573
|
+
|
574
|
+
# This is an example template to create commands to issue queries.
|
575
|
+
def template(instance)
|
576
|
+
client = Client.open(instance)
|
577
|
+
if client
|
578
|
+
client.query("some SQL statement")
|
579
|
+
else
|
580
|
+
puts "Could not open connection to MySQL instance #{instance}."
|
581
|
+
end
|
582
|
+
rescue Mysql2::Error => e
|
583
|
+
puts e.message
|
584
|
+
puts e.backtrace
|
585
|
+
ensure
|
586
|
+
client.close if client
|
587
|
+
end
|
588
|
+
|
589
|
+
private
|
590
|
+
|
591
|
+
def run_cmd(cmd, verbose)
|
592
|
+
puts cmd if verbose
|
593
|
+
cmd += " > /dev/null 2>&1" unless verbose
|
594
|
+
output = %x[#{cmd}]
|
595
|
+
puts output if verbose
|
596
|
+
exit_code = $?.exitstatus
|
597
|
+
if exit_code == 0
|
598
|
+
puts "OK"
|
599
|
+
else
|
600
|
+
"FAIL: exit code is #{exit_code}"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# Return the process ID (pid) for an instance.
|
605
|
+
def get_mysqld_pid(instance)
|
606
|
+
server = server_for_instance(instance)
|
607
|
+
pidfile = server['pid-file']
|
608
|
+
return nil unless File.exist?(pidfile)
|
609
|
+
Integer(File.open(pidfile, &:readline).strip)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# STOP SLAVE
|
615
|
+
# RESET MASTER
|
616
|
+
# CHANGE MASTER TO
|
617
|
+
|
618
|
+
class RunExamples
|
619
|
+
include Repctl::Commands
|
620
|
+
|
621
|
+
def runtest
|
622
|
+
|
623
|
+
switch_master_to(3)
|
624
|
+
=begin
|
625
|
+
(1..4).each {|i| ensure_running(i)}
|
626
|
+
puts get_master_coordinates(1)
|
627
|
+
(2..4).each {|i| puts get_slave_coordinates(i)}
|
628
|
+
(1..4).each {|i| puts is_master?(i) }
|
629
|
+
puts "Master is #{find_master}"
|
630
|
+
drain_relay_log(2)
|
631
|
+
# sleep(10)
|
632
|
+
# (1..4).each {|i| crash(i)}
|
633
|
+
=end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Repctl
|
4
|
+
|
5
|
+
module Servers
|
6
|
+
|
7
|
+
include Config
|
8
|
+
|
9
|
+
def all_servers
|
10
|
+
@servers ||= File.open(SERVER_CONFIG) { |yf| YAML::load( yf ) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_instances
|
14
|
+
instances = []
|
15
|
+
all_servers.each do |s|
|
16
|
+
instances << s["instance"]
|
17
|
+
end
|
18
|
+
instances
|
19
|
+
end
|
20
|
+
|
21
|
+
def server_for_instance(instance)
|
22
|
+
all_servers.select {|s| s["instance"] == Integer(instance)}.shift
|
23
|
+
end
|
24
|
+
|
25
|
+
def all_live_servers
|
26
|
+
all_servers.select {|s| get_mysqld_pid(s["instance"]) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_live_instances
|
30
|
+
all_live_servers.map {|s| s["instance"]}
|
31
|
+
end
|
32
|
+
|
33
|
+
def live?(instance)
|
34
|
+
get_mysqld_pid(instance)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Return the process ID (pid) for an instance.
|
40
|
+
def get_mysqld_pid(instance)
|
41
|
+
server = server_for_instance(instance)
|
42
|
+
pidfile = server['pid-file']
|
43
|
+
return nil unless File.exist?(pidfile)
|
44
|
+
Integer(File.open(pidfile, &:readline).strip)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|