repctl 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -0
- data/bin/repctl +194 -186
- data/config/config.rb +3 -1
- data/lib/repctl/mysql_admin.rb +209 -191
- data/lib/repctl/version.rb +1 -1
- data/lib/repctl.rb +5 -1
- metadata +7 -7
data/README.md
CHANGED
@@ -25,6 +25,9 @@ MySQL from source, then do `make install`, then you are done! No MySQL
|
|
25
25
|
post-installation steps are necessary. All post-install configuration is
|
26
26
|
handled by `repctl`.
|
27
27
|
|
28
|
+
You will need to set an environment variable `REPCTL_CONFIG_DIR` in your shell
|
29
|
+
so that `repctl` can find the configuration files it needs.
|
30
|
+
|
28
31
|
The top
|
29
32
|
|
30
33
|
== Available Commands
|
data/bin/repctl
CHANGED
@@ -1,195 +1,189 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'socket'
|
4
|
+
require 'fileutils'
|
4
5
|
require 'repctl'
|
5
6
|
require 'thor'
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
module Helpers
|
12
|
-
|
13
|
-
include Repctl::Config
|
14
|
-
include Repctl::Servers
|
15
|
-
|
16
|
-
def start(instance)
|
17
|
-
say "Starting instance #{instance}.", :green
|
18
|
-
do_start(instance)
|
19
|
-
end
|
20
|
-
|
21
|
-
def start_all
|
22
|
-
all_instances.each do |instance|
|
23
|
-
start(instance)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def stop(instance)
|
28
|
-
say "Stopping instance #{instance}.", :green
|
29
|
-
do_admin(instance, "shutdown")
|
30
|
-
end
|
31
|
-
|
32
|
-
def stop_all
|
33
|
-
all_live_instances.each do |instance|
|
34
|
-
stop(instance)
|
8
|
+
module Repctl
|
9
|
+
module Helpers
|
10
|
+
def do_stop(instance)
|
11
|
+
do_admin(instance, "shutdown")
|
35
12
|
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def config(instance)
|
39
|
-
say "Initializing new data directory for instance #{instance}."
|
40
|
-
do_config(instance)
|
41
|
-
end
|
42
|
-
|
43
|
-
def config_all
|
44
|
-
all_instances.each do |instance|
|
45
|
-
config(instance)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def secure_accounts(instance)
|
50
|
-
do_secure_accounts(instance)
|
51
|
-
end
|
52
13
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
def reset(instance)
|
60
|
-
stop(instance)
|
61
|
-
config(instance)
|
62
|
-
start(instance)
|
63
|
-
secure_accounts(instance)
|
64
|
-
end
|
65
|
-
|
66
|
-
def restart(instance)
|
67
|
-
stop(instance)
|
68
|
-
start(instance)
|
69
|
-
end
|
70
|
-
|
71
|
-
def reset_all
|
72
|
-
all_instances.each do |instance|
|
73
|
-
reset(instance)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def change_master(master, slave, file, position)
|
78
|
-
say "Changing master: master = #{master}, slave = #{slave}, file = #{file}, position = #{position}"
|
79
|
-
do_change_master(master, slave, :file => file, :position => position)
|
80
|
-
end
|
81
|
-
|
82
|
-
def start_slave(slave)
|
83
|
-
say "Starting slave #{slave}", :green
|
84
|
-
run_mysql_query(slave, "START SLAVE")
|
85
|
-
end
|
86
|
-
|
87
|
-
def crash(instance)
|
88
|
-
say "Crashing instance #{instance}", :red
|
89
|
-
do_crash(instance)
|
90
|
-
end
|
14
|
+
def do_reset(instance)
|
15
|
+
do_stop(instance)
|
16
|
+
do_config(instance)
|
17
|
+
do_start(instance)
|
18
|
+
do_secure_accounts(instance)
|
19
|
+
end
|
91
20
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
21
|
+
def do_start_slave(instance)
|
22
|
+
run_mysql_query(instance, "START SLAVE")
|
23
|
+
end
|
96
24
|
|
97
|
-
|
98
|
-
|
99
|
-
|
25
|
+
def do_restart(instance)
|
26
|
+
do_admin(instance, "shutown")
|
27
|
+
do_start(instance)
|
28
|
+
end
|
100
29
|
end
|
101
|
-
|
102
|
-
end # Module helpers
|
30
|
+
end
|
103
31
|
|
104
|
-
class
|
32
|
+
class RepctlCmds < Thor
|
105
33
|
|
106
34
|
include Thor::Actions
|
107
35
|
include Repctl::Config
|
108
36
|
include Repctl::Commands
|
109
37
|
include Repctl::Servers
|
110
|
-
include Helpers
|
111
|
-
|
112
|
-
desc "start
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
38
|
+
include Repctl::Helpers
|
39
|
+
|
40
|
+
desc "start [INSTANCES]", "Start one or more defined server instances."
|
41
|
+
method_option :all, :type => :boolean, :aliases => "-a",
|
42
|
+
:default => false, :desc => "Start all defined instances"
|
43
|
+
long_desc <<-EOS
|
44
|
+
Start one or more defined server instances. With the --all option,
|
45
|
+
all defined servers are started. Otherwise, you can give a list of
|
46
|
+
servers to start. If a server instance is already running, this
|
47
|
+
command has no effect on the instance.
|
48
|
+
EOS
|
49
|
+
def start(*instances)
|
50
|
+
if options[:all]
|
51
|
+
todos = all_instances
|
52
|
+
else
|
53
|
+
todos = instances
|
54
|
+
end
|
55
|
+
todos.each do |instance|
|
56
|
+
say "Starting instance #{instance}.", :green
|
57
|
+
do_start(instance)
|
58
|
+
end
|
125
59
|
end
|
126
60
|
|
127
|
-
desc "
|
128
|
-
|
129
|
-
|
61
|
+
desc "stop [INSTANCES]", "Stop one or more running server instances."
|
62
|
+
method_option :all, :type => :boolean, :aliases => "-a",
|
63
|
+
:default => false, :desc => "Stop all running instances"
|
64
|
+
long_desc <<-EOS
|
65
|
+
Stop one or more running server instances. With the --all option,
|
66
|
+
all running servers are stopped. Otherwise, you can give a list of
|
67
|
+
servers to stop.
|
68
|
+
EOS
|
69
|
+
def stop(*instances)
|
70
|
+
if options[:all]
|
71
|
+
todos = all_live_instances
|
72
|
+
else
|
73
|
+
todos = instances
|
74
|
+
end
|
75
|
+
todos.each do |instance|
|
76
|
+
say "Stopping instance #{instance}.", :green
|
77
|
+
do_stop(instance)
|
78
|
+
end
|
130
79
|
end
|
131
80
|
|
132
|
-
desc "config
|
133
|
-
|
134
|
-
|
81
|
+
desc "config [INSTANCES]", "Initialize the data directory for server instances."
|
82
|
+
method_option :all, :type => :boolean, :aliases => "-a",
|
83
|
+
:default => false, :desc => "Initialize all defined instances"
|
84
|
+
long_desc <<-EOS
|
85
|
+
Initialize the data directory for one or more server instances.
|
86
|
+
With the --all option, the data directory for all defined servers
|
87
|
+
is initialized. Otherwise, you can give a list of defined server
|
88
|
+
instances.
|
89
|
+
EOS
|
90
|
+
def config(*instances)
|
91
|
+
if options[:all]
|
92
|
+
todos = all_instances
|
93
|
+
else
|
94
|
+
todos = instances
|
95
|
+
end
|
96
|
+
todos.each do |instance|
|
97
|
+
say "Initializing new data directory for instance #{instance}.", :green
|
98
|
+
do_config(instance)
|
99
|
+
end
|
135
100
|
end
|
136
|
-
|
137
|
-
desc "
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
101
|
+
|
102
|
+
desc "secure_accounts [INSTANCES]", "Add password to root and anonymous accounts."
|
103
|
+
method_option :all, :type => :boolean, :aliases => "-a",
|
104
|
+
:default => false, :desc => "Secure accounts for all running instances"
|
105
|
+
long_desc <<-EOS
|
106
|
+
Add a password to the existing root and anonymous accounts of one or more
|
107
|
+
running server instances. With the --all option, all running servers
|
108
|
+
secured. Otherwise, you can give a list of servers to process.
|
109
|
+
EOS
|
110
|
+
def secure_accounts(*instances)
|
111
|
+
if options[:all]
|
112
|
+
todos = all_live_instances
|
113
|
+
else
|
114
|
+
todos = instances
|
115
|
+
end
|
116
|
+
todos.each do |instance|
|
117
|
+
say "Securing accounts for instance #{instance}.", :green
|
118
|
+
do_secure_accounts(instance)
|
119
|
+
end
|
145
120
|
end
|
146
121
|
|
147
|
-
desc "
|
148
|
-
|
149
|
-
|
122
|
+
desc "reset [INSTANCES]", "Reset one or more define server instances."
|
123
|
+
method_option :all, :type => :boolean, :aliases => "-a",
|
124
|
+
:default => false, :desc => "Reset all defined instances"
|
125
|
+
long_desc <<-EOS
|
126
|
+
Reset one or more defined server instances. If the --all option is
|
127
|
+
given the target servers are all defined servers, otherwise the
|
128
|
+
target servers are those specified in the instance argument.
|
129
|
+
If a target server instance is running, stop it. Then configure its
|
130
|
+
data directory, (re)start the server, and secure the accounts.
|
131
|
+
EOS
|
132
|
+
def reset(*instances)
|
133
|
+
if options[:all]
|
134
|
+
todos = all_instances
|
135
|
+
else
|
136
|
+
todos = instances
|
137
|
+
end
|
138
|
+
todos.each do |instance|
|
139
|
+
say "Resetting instance #{instance}.", :green
|
140
|
+
do_reset(instance)
|
141
|
+
end
|
150
142
|
end
|
151
143
|
|
152
|
-
desc "
|
153
|
-
def
|
154
|
-
|
144
|
+
desc "restart INSTANCE" , "Stop and start a server instance."
|
145
|
+
def restart(instance)
|
146
|
+
say "Restarting instance #{instance}", :green
|
147
|
+
do_stop(instance)
|
148
|
+
do_start(instance)
|
155
149
|
end
|
156
150
|
|
157
|
-
desc "reset_all", "Remove all databases and restart MySQL instances."
|
158
|
-
def reset_all
|
159
|
-
super
|
160
|
-
end
|
161
|
-
|
162
151
|
desc "start_slave SLAVE", "Issue START SLAVE on the SLAVE MySQL instance."
|
163
152
|
def start_slave(slave)
|
164
|
-
|
153
|
+
say "Starting slave #{slave}", :green
|
154
|
+
do_start_slave(slave)
|
165
155
|
end
|
166
156
|
|
167
157
|
desc "change_master MASTER SLAVE FILE POSITION", "Execute CHANGE MASTER TO on the SLAVE."
|
168
158
|
def change_master(master, slave, file, position)
|
169
|
-
|
159
|
+
say "Changing master: master = #{master}, slave = #{slave}, file = #{file}, position = #{position}"
|
160
|
+
do_change_master(master, slave, :file => file, :position => position)
|
170
161
|
end
|
171
162
|
|
172
|
-
desc "crash INSTANCE", "Crash a running
|
163
|
+
desc "crash INSTANCE", "Crash a running server."
|
173
164
|
def crash(instance)
|
174
|
-
|
165
|
+
say "Crashing instance #{instance}", :red
|
166
|
+
do_crash(instance)
|
175
167
|
end
|
176
168
|
|
177
169
|
desc "repl_user INSTANCE", "Create the replication user account on a MySQL instance."
|
178
170
|
def repl_user(instance)
|
179
|
-
|
171
|
+
say "Creating replication account on instance #{instance}.", :green
|
172
|
+
do_repl_user(instance)
|
180
173
|
end
|
181
174
|
|
182
175
|
desc "cluster_user INSTANCE", "Create the cluster user account on a MySQL instance."
|
183
176
|
def cluster_user(instance)
|
184
|
-
|
177
|
+
say "Installing cluster user for instance #{instance}.", :green
|
178
|
+
do_cluster_user(instance)
|
185
179
|
end
|
186
|
-
|
187
|
-
desc "
|
180
|
+
|
181
|
+
desc "status", "Show the status of replication."
|
188
182
|
method_option :continuous, :aliases => "-c", :type => :numeric,
|
189
183
|
:desc => "Continuous output at specified interval (in seconds)."
|
190
184
|
method_option :servers, :aliases => "-s", :type => :array,
|
191
185
|
:desc => "Only check the status of given servers."
|
192
|
-
def
|
186
|
+
def status
|
193
187
|
todos = options[:servers] || all_live_instances
|
194
188
|
unless todos.any?
|
195
189
|
say "No Running Servers.", :blue
|
@@ -250,9 +244,31 @@ class Mysql < Thor
|
|
250
244
|
def restore(slave, dumpfile = DEFAULT_DUMPFILE)
|
251
245
|
do_restore(slave, dumpfile)
|
252
246
|
end
|
253
|
-
|
247
|
+
|
248
|
+
#
|
249
|
+
# Setting Up Replication with Existing Data using the 'mysqldump' utility. The
|
250
|
+
# master has existing data.
|
251
|
+
#
|
252
|
+
desc "add_slave MASTER SLAVE", "Master has some data that is used to initialize the slave."
|
253
|
+
method_option :populate, :type => :boolean, :default => false
|
254
|
+
def add_slave(master, slave)
|
255
|
+
reset(slave)
|
256
|
+
|
257
|
+
if options[:populate]
|
258
|
+
invoke "utils:bench", [master, "/opt/MySQL/b2.properties"]
|
259
|
+
end
|
260
|
+
file, position = invoke "mysql:dump", [master]
|
261
|
+
|
262
|
+
# Slave is running, but it is not yet configured as a slave.
|
263
|
+
# Load slave from the dump file...
|
264
|
+
invoke "mysql:restore", [slave]
|
265
|
+
|
266
|
+
do_change_master(master, slave, :file => file, :position => position)
|
267
|
+
do_start_slave(slave)
|
268
|
+
end
|
269
|
+
|
254
270
|
DEFAULT_MASTER = 1
|
255
|
-
|
271
|
+
|
256
272
|
desc "bench [INSTANCE] [PROPS]", "Run the Tungsten Bristlecone benchmarker.
|
257
273
|
The INSTANCE specifies the instance to perform all operations to, and PROPS
|
258
274
|
is the properties file to use. The INSTANCE defaults to #{DEFAULT_MASTER} and
|
@@ -334,9 +350,8 @@ class Mysql < Thor
|
|
334
350
|
msecs = options[:delay]
|
335
351
|
sleep(msecs / 1000.0) if msecs > 0
|
336
352
|
end
|
337
|
-
|
338
353
|
end
|
339
|
-
|
354
|
+
|
340
355
|
#
|
341
356
|
# Setting Up Replication with New Master and Slaves.
|
342
357
|
# Here, we stop all MySQL servers, remove the data directories, reinitialize
|
@@ -347,16 +362,16 @@ class Mysql < Thor
|
|
347
362
|
"Set up a single master/slave replication pair from the very beginning."
|
348
363
|
def repl_pair(master, slave)
|
349
364
|
say "master is #{master}, slave is #{slave}", :green
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
365
|
+
do_reset(master)
|
366
|
+
do_reset(slave)
|
367
|
+
do_cluster_user(master)
|
368
|
+
do_repl_user(master)
|
354
369
|
coordinates = get_coordinates(master)
|
355
370
|
file = coordinates[:file]
|
356
371
|
position = coordinates[:position]
|
357
372
|
say "File is #{file}, Position is #{position}", :green
|
358
|
-
|
359
|
-
|
373
|
+
do_change_master(master, slave, :file => file, :position => position)
|
374
|
+
do_start_slave(slave)
|
360
375
|
end
|
361
376
|
|
362
377
|
desc "repl_trio MASTER SLAVE1 SLAVE2",
|
@@ -365,48 +380,41 @@ class Mysql < Thor
|
|
365
380
|
def repl_trio(master, slave1, slave2)
|
366
381
|
say "master is #{master}, slaves are #{slave1} and #{slave2}", :green
|
367
382
|
if options[:reset]
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
383
|
+
do_reset(master)
|
384
|
+
do_reset(slave1)
|
385
|
+
do_reset(slave2)
|
386
|
+
do_cluster_user(master)
|
387
|
+
do_repl_user(master)
|
373
388
|
else
|
374
|
-
|
375
|
-
|
376
|
-
|
389
|
+
do_restart(master)
|
390
|
+
do_restart(slave1)
|
391
|
+
do_restart(slave2)
|
377
392
|
end
|
378
393
|
coordinates = get_coordinates(master)
|
379
394
|
file = coordinates[:file]
|
380
395
|
position = coordinates[:position]
|
381
396
|
say "File is #{file}, Position is #{position}", :green
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
397
|
+
do_change_master(master, slave1, :file => file, :position => position)
|
398
|
+
do_start_slave(slave1)
|
399
|
+
do_change_master(master, slave2, :file => file, :position => position)
|
400
|
+
do_start_slave(slave2)
|
386
401
|
end
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
402
|
+
|
403
|
+
desc "install sample config files to DIRECTORY", "After initial install, use these "
|
404
|
+
"as templates for configuration. The target directory must exist and be empty."
|
405
|
+
def install_sample_configs(dest_dir)
|
406
|
+
source_dir = File.join(Repctl::Config.const_get(:GEM_HOME), "config")
|
407
|
+
source_files = Dir.glob(File.join(source_dir, '*'))
|
408
|
+
if File.directory?(dest_dir)
|
409
|
+
if Dir.entries(dest_dir).size == 2 # directory is empty
|
410
|
+
FileUtils.copy(source_files, dest_dir)
|
411
|
+
else
|
412
|
+
say "Target directory must be empty.", :red
|
413
|
+
end
|
414
|
+
else
|
415
|
+
say "Config target #{to_dir} is not a directory.", :red
|
399
416
|
end
|
400
|
-
file, position = invoke "mysql:dump", [master]
|
401
|
-
|
402
|
-
# Slave is running, but it is not yet configured as a slave.
|
403
|
-
# Load slave from the dump file...
|
404
|
-
invoke "mysql:restore", [slave]
|
405
|
-
|
406
|
-
change_master(master, slave, file, position)
|
407
|
-
start_slave(slave)
|
408
417
|
end
|
409
|
-
|
410
418
|
end
|
411
419
|
|
412
|
-
|
420
|
+
RepctlCmds.start
|
data/config/config.rb
CHANGED
@@ -5,10 +5,12 @@ module Repctl
|
|
5
5
|
# The location of the local MySQL installation.
|
6
6
|
MYSQL_HOME = "/usr/local/mysql"
|
7
7
|
|
8
|
-
ROOT_PASSWORD = "
|
8
|
+
ROOT_PASSWORD = "graceling"
|
9
9
|
|
10
10
|
SERVER_CONFIG = File.expand_path("../servers.yml", __FILE__)
|
11
11
|
|
12
|
+
GEM_HOME = File.expand_path("../..", __FILE__)
|
13
|
+
|
12
14
|
# Define the directory where subdirectores for data will be created
|
13
15
|
# for each MySQL server instance. This should agree with the
|
14
16
|
# per server'data_dir' property in the servers.yml file, and it should
|
data/lib/repctl/mysql_admin.rb
CHANGED
@@ -68,6 +68,26 @@ module Repctl
|
|
68
68
|
include Repctl::Config
|
69
69
|
include Servers
|
70
70
|
|
71
|
+
#
|
72
|
+
# Public methods are:
|
73
|
+
#
|
74
|
+
# do_secure_accounts
|
75
|
+
# do_start
|
76
|
+
# do_admin
|
77
|
+
# do_config
|
78
|
+
# do_crash
|
79
|
+
# do_change_master
|
80
|
+
# do_dump
|
81
|
+
# do_restore
|
82
|
+
# do_repl_user
|
83
|
+
# do_cluster_user
|
84
|
+
# do_create_widgets
|
85
|
+
# get_coordinates
|
86
|
+
# get_mysqld_pid
|
87
|
+
# get_slave_status
|
88
|
+
# run_mysql_query
|
89
|
+
#
|
90
|
+
|
71
91
|
def do_secure_accounts(instance)
|
72
92
|
client = Client.open(instance, :no_password => true)
|
73
93
|
q1 = "UPDATE mysql.user SET Password = PASSWORD(\'#{ROOT_PASSWORD}\') where User = \'root\'"
|
@@ -147,12 +167,6 @@ module Repctl
|
|
147
167
|
%x( cd #{MYSQL_HOME} && #{cmd} )
|
148
168
|
end
|
149
169
|
|
150
|
-
def do_status(instance)
|
151
|
-
|
152
|
-
status = get_coordinates(instance)
|
153
|
-
puts status
|
154
|
-
end
|
155
|
-
|
156
170
|
#
|
157
171
|
# Treat the instance as a slave and
|
158
172
|
# process the output of "SHOW SLAVE STATUS".
|
@@ -198,39 +212,6 @@ module Repctl
|
|
198
212
|
end
|
199
213
|
end
|
200
214
|
|
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
215
|
#
|
235
216
|
# From http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html:
|
236
217
|
#
|
@@ -282,7 +263,6 @@ EOT
|
|
282
263
|
ensure
|
283
264
|
slave_connection.close if slave_connection
|
284
265
|
end
|
285
|
-
|
286
266
|
end
|
287
267
|
|
288
268
|
def do_dump(instance, dumpfile)
|
@@ -312,38 +292,6 @@ EOT
|
|
312
292
|
run_cmd(cmd, true)
|
313
293
|
end
|
314
294
|
|
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
295
|
# Get the master coordinates from a MySQL instance. Optionally,
|
348
296
|
# run a block while holding the READ LOCK.
|
349
297
|
def get_coordinates(instance)
|
@@ -375,30 +323,6 @@ EOT
|
|
375
323
|
# coordinates
|
376
324
|
end
|
377
325
|
|
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
326
|
def run_mysql_query(instance, cmd)
|
403
327
|
client = Client.open(instance)
|
404
328
|
if client
|
@@ -414,92 +338,6 @@ EOT
|
|
414
338
|
client.close if client
|
415
339
|
end
|
416
340
|
|
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
341
|
def do_repl_user(instance)
|
504
342
|
hostname = "127.0.0.1"
|
505
343
|
client = Client.open(instance)
|
@@ -526,7 +364,6 @@ EOT
|
|
526
364
|
client.close if client
|
527
365
|
end
|
528
366
|
|
529
|
-
|
530
367
|
def do_cluster_user(instance)
|
531
368
|
client = Client.open(instance)
|
532
369
|
|
@@ -571,6 +408,16 @@ EOT
|
|
571
408
|
client.close if client
|
572
409
|
end
|
573
410
|
|
411
|
+
# Return the process ID (pid) for an instance.
|
412
|
+
def get_mysqld_pid(instance)
|
413
|
+
server = server_for_instance(instance)
|
414
|
+
pidfile = server['pid-file']
|
415
|
+
return nil unless File.exist?(pidfile)
|
416
|
+
Integer(File.open(pidfile, &:readline).strip)
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
574
421
|
# This is an example template to create commands to issue queries.
|
575
422
|
def template(instance)
|
576
423
|
client = Client.open(instance)
|
@@ -586,8 +433,64 @@ EOT
|
|
586
433
|
client.close if client
|
587
434
|
end
|
588
435
|
|
589
|
-
|
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
|
590
477
|
|
478
|
+
def is_master?(instance)
|
479
|
+
get_slave_coordinates(instance).empty?
|
480
|
+
end
|
481
|
+
|
482
|
+
def is_slave?(instance)
|
483
|
+
!is_master?(instance)
|
484
|
+
end
|
485
|
+
|
486
|
+
def find_masters()
|
487
|
+
masters = []
|
488
|
+
all_servers.each do |s|
|
489
|
+
masters << s if is_master?(s)
|
490
|
+
end
|
491
|
+
masters
|
492
|
+
end
|
493
|
+
|
591
494
|
def run_cmd(cmd, verbose)
|
592
495
|
puts cmd if verbose
|
593
496
|
cmd += " > /dev/null 2>&1" unless verbose
|
@@ -600,14 +503,129 @@ EOT
|
|
600
503
|
"FAIL: exit code is #{exit_code}"
|
601
504
|
end
|
602
505
|
end
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
506
|
+
|
507
|
+
def stop_slave_io_thread(instance)
|
508
|
+
client = Client.open(instance)
|
509
|
+
if client
|
510
|
+
client.query("STOP SLAVE IO_THREAD")
|
511
|
+
end
|
512
|
+
ensure
|
513
|
+
client.close if client
|
514
|
+
end
|
515
|
+
|
516
|
+
# unused
|
517
|
+
def promote_slave_to_master(instance)
|
518
|
+
client = Client.open(instance)
|
519
|
+
if client
|
520
|
+
client.query("STOP SLAVE")
|
521
|
+
client.query("RESET MASTER")
|
522
|
+
end
|
523
|
+
ensure
|
524
|
+
client.close if client
|
525
|
+
end
|
526
|
+
|
527
|
+
def get_slave_coordinates(instance)
|
528
|
+
client = Client.open(instance)
|
529
|
+
if client
|
530
|
+
results = client.query("SHOW SLAVE STATUS")
|
531
|
+
row = results.first
|
532
|
+
if row
|
533
|
+
{:file => row["Master_Log_File"], :position => row["Read_Master_Log_Pos"]}
|
534
|
+
else
|
535
|
+
{}
|
536
|
+
end
|
537
|
+
end
|
538
|
+
ensure
|
539
|
+
client.close if client
|
540
|
+
end
|
541
|
+
|
542
|
+
# unused
|
543
|
+
def start_slave_io_thread(instance)
|
544
|
+
client = Client.open(instance)
|
545
|
+
if client
|
546
|
+
client.query("START SLAVE IO_THREAD")
|
547
|
+
end
|
548
|
+
ensure
|
549
|
+
client.close if client
|
610
550
|
end
|
551
|
+
|
552
|
+
def drain_relay_log(instance)
|
553
|
+
done = false
|
554
|
+
stop_slave_io_thread(instance)
|
555
|
+
client = Client.open(instance)
|
556
|
+
if client
|
557
|
+
|
558
|
+
# If the slave 'sql_thread' is not running, this will loop forever.
|
559
|
+
while !done
|
560
|
+
results = client.query("SHOW PROCESSLIST")
|
561
|
+
results.each do |row|
|
562
|
+
if row["State"] =~ /Slave has read all relay log/
|
563
|
+
done = true
|
564
|
+
puts "Slave has read all relay log."
|
565
|
+
break
|
566
|
+
end
|
567
|
+
end
|
568
|
+
puts "Waiting for slave to read relay log." unless done
|
569
|
+
end
|
570
|
+
else
|
571
|
+
puts "Could not open connection to instance #{instance}."
|
572
|
+
end
|
573
|
+
ensure
|
574
|
+
client.close if client
|
575
|
+
end
|
576
|
+
|
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
|
+
#
|
596
|
+
# Get the status of replication for the master and all slaves.
|
597
|
+
# Return an array of hashes, each hash has the form:
|
598
|
+
# {:instance => <instance id>, :error => <errrmsg>,
|
599
|
+
# :master_file => <binlog-file-name>, :master_position => <binlog-position>,
|
600
|
+
# :slave_file => <binlog-file-name>, :slave_position => <binlog-position>}
|
601
|
+
#
|
602
|
+
def do_slave_status(instance)
|
603
|
+
instance ||= DEFAULT_MASTER
|
604
|
+
locked = false
|
605
|
+
client = Client.open(instance, :timeout => 5)
|
606
|
+
if client
|
607
|
+
client.query("FLUSH TABLES WITH READ LOCK")
|
608
|
+
locked = true
|
609
|
+
results = client.query("SHOW SLAVE STATUS")
|
610
|
+
if results.first
|
611
|
+
results.first.merge("Instance" => instance, "Error" => "Success")
|
612
|
+
else
|
613
|
+
{"Instance" => instance, "Error" => "MySQL server is not a slave."}
|
614
|
+
end
|
615
|
+
else
|
616
|
+
{"Instance" => instance, "Error" => "Could not connect to MySQL server."}
|
617
|
+
end
|
618
|
+
rescue Mysql2::Error => e
|
619
|
+
{:instance => instance, "Error" => e.message}
|
620
|
+
ensure
|
621
|
+
if client
|
622
|
+
client.query("UNLOCK TABLES") if locked
|
623
|
+
client.close
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
|
628
|
+
|
611
629
|
end
|
612
630
|
end
|
613
631
|
|
data/lib/repctl/version.rb
CHANGED
data/lib/repctl.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# The compiled-in default is not generally very useful.
|
2
|
+
config_dir = ENV["REPCTL_CONFIG_DIR"] ||
|
3
|
+
File.expand_path('../../config', __FILE__)
|
4
|
+
|
5
|
+
require File.join(config_dir, 'config')
|
2
6
|
|
3
7
|
require "repctl/version"
|
4
8
|
require "repctl/servers"
|
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.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-01-26 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &55282760 !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: *55282760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: thor
|
27
|
-
requirement: &
|
27
|
+
requirement: &55282340 !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: *55282340
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mysql2
|
38
|
-
requirement: &
|
38
|
+
requirement: &55281920 !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: *55281920
|
47
47
|
description: Ruby gem with Thor script to manage MySQL and PostgreSQL replication
|
48
48
|
email:
|
49
49
|
- lydianblues@gmail.com
|