repctl 0.0.2 → 0.0.3
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/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
|