jetpants 0.7.10 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +7 -3
- data/bin/jetpants +67 -33
- data/doc/commands.rdoc +2 -0
- data/doc/configuration.rdoc +3 -2
- data/doc/jetpants_collins.rdoc +1 -1
- data/doc/plugins.rdoc +4 -0
- data/doc/requirements.rdoc +1 -1
- data/lib/jetpants.rb +21 -4
- data/lib/jetpants/db.rb +4 -0
- data/lib/jetpants/db/client.rb +10 -4
- data/lib/jetpants/db/import_export.rb +28 -11
- data/lib/jetpants/db/privileges.rb +59 -10
- data/lib/jetpants/db/replication.rb +69 -20
- data/lib/jetpants/db/server.rb +41 -19
- data/lib/jetpants/db/state.rb +99 -8
- data/lib/jetpants/host.rb +33 -35
- data/lib/jetpants/monkeypatch.rb +9 -0
- data/lib/jetpants/pool.rb +14 -7
- data/lib/jetpants/shard.rb +10 -12
- data/lib/jetpants/table.rb +4 -0
- data/lib/jetpants/topology.rb +28 -9
- data/plugins/jetpants_collins/db.rb +12 -0
- data/plugins/jetpants_collins/jetpants_collins.rb +34 -7
- data/plugins/jetpants_collins/pool.rb +6 -2
- data/plugins/jetpants_collins/topology.rb +44 -6
- data/plugins/simple_tracker/topology.rb +1 -0
- metadata +33 -29
@@ -7,7 +7,8 @@ module Jetpants
|
|
7
7
|
class DB
|
8
8
|
# Create a MySQL user. If you omit parameters, the defaults from Jetpants'
|
9
9
|
# configuration will be used instead. Does not automatically grant any
|
10
|
-
# privileges; use DB#grant_privileges for that.
|
10
|
+
# privileges; use DB#grant_privileges for that. Intentionally cannot
|
11
|
+
# create a passwordless user.
|
11
12
|
def create_user(username=false, password=false, skip_binlog=false)
|
12
13
|
username ||= app_credentials[:user]
|
13
14
|
password ||= app_credentials[:pass]
|
@@ -19,6 +20,11 @@ module Jetpants
|
|
19
20
|
commands << "FLUSH PRIVILEGES"
|
20
21
|
commands = commands.join '; '
|
21
22
|
mysql_root_cmd commands, schema: true
|
23
|
+
Jetpants.mysql_grant_ips.each do |ip|
|
24
|
+
message = "Created user '#{username}'@'#{ip}'"
|
25
|
+
message += ' (only on this node - skipping binlog!)' if skip_binlog
|
26
|
+
output message
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
# Drops a user. Can optionally make this statement skip replication, if you
|
@@ -33,6 +39,11 @@ module Jetpants
|
|
33
39
|
commands << "FLUSH PRIVILEGES"
|
34
40
|
commands = commands.join '; '
|
35
41
|
mysql_root_cmd commands, schema: true
|
42
|
+
Jetpants.mysql_grant_ips.each do |ip|
|
43
|
+
message = "Dropped user '#{username}'@'#{ip}'"
|
44
|
+
message += ' (only on this node - skipping binlog!)' if skip_binlog
|
45
|
+
output message
|
46
|
+
end
|
36
47
|
end
|
37
48
|
|
38
49
|
# Grants privileges to the given username for the specified database.
|
@@ -64,30 +75,68 @@ module Jetpants
|
|
64
75
|
commands << "FLUSH PRIVILEGES"
|
65
76
|
commands = commands.join '; '
|
66
77
|
mysql_root_cmd commands, schema: true
|
78
|
+
Jetpants.mysql_grant_ips.each do |ip|
|
79
|
+
verb = (statement.downcase == 'revoke' ? 'Revoking' : 'Granting')
|
80
|
+
target_db = (database == '*' ? 'globally' : "on #{database}.*")
|
81
|
+
output "#{verb} privileges #{preposition.downcase} '#{username}'@'#{ip}' #{target_db}: #{privileges.downcase}"
|
82
|
+
end
|
67
83
|
end
|
68
84
|
|
69
85
|
# Disables access to a DB by the application user, and sets the DB to
|
70
86
|
# read-only. Useful when decommissioning instances from a shard that's
|
71
|
-
# been split
|
87
|
+
# been split, or a former slave that's been permanently removed from the pool
|
72
88
|
def revoke_all_access!
|
73
89
|
user_name = app_credentials[:user]
|
74
90
|
enable_read_only!
|
75
|
-
|
76
|
-
output(drop_user(user_name, true)) # drop the user without replicating the drop statement to slaves
|
91
|
+
drop_user(user_name, true) # drop the user without replicating the drop statement to slaves
|
77
92
|
end
|
78
93
|
|
79
94
|
# Enables global read-only mode on the database.
|
80
95
|
def enable_read_only!
|
81
|
-
|
82
|
-
|
83
|
-
|
96
|
+
if read_only?
|
97
|
+
output "Node already has read_only mode enabled"
|
98
|
+
true
|
99
|
+
else
|
100
|
+
output "Enabling read_only mode"
|
101
|
+
mysql_root_cmd 'SET GLOBAL read_only = 1'
|
102
|
+
read_only?
|
103
|
+
end
|
84
104
|
end
|
85
105
|
|
86
106
|
# Disables global read-only mode on the database.
|
87
107
|
def disable_read_only!
|
88
|
-
|
89
|
-
|
90
|
-
|
108
|
+
if read_only?
|
109
|
+
output "Disabling read_only mode"
|
110
|
+
mysql_root_cmd 'SET GLOBAL read_only = 0'
|
111
|
+
not read_only?
|
112
|
+
else
|
113
|
+
output "Confirmed that read_only mode is already disabled"
|
114
|
+
true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generate and return a random string consisting of uppercase
|
119
|
+
# letters, lowercase letters, and digits.
|
120
|
+
def self.random_password(length=50)
|
121
|
+
chars = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
|
122
|
+
(1..length).map{ chars[rand(chars.length)] }.join
|
123
|
+
end
|
124
|
+
|
125
|
+
# override Jetpants.mysql_grant_ips temporarily before executing a block
|
126
|
+
# then set Jetpants.mysql_grant_ips back to the original values
|
127
|
+
# eg. master.override_mysql_grant_ips(['10.10.10.10']) do
|
128
|
+
# #something
|
129
|
+
# end
|
130
|
+
def override_mysql_grant_ips(ips)
|
131
|
+
ip_holder = Jetpants.mysql_grant_ips
|
132
|
+
Jetpants.mysql_grant_ips = ips
|
133
|
+
begin
|
134
|
+
yield
|
135
|
+
rescue StandardError, Interrupt, IOError
|
136
|
+
Jetpants.mysql_grant_ips = ip_holder
|
137
|
+
raise
|
138
|
+
end
|
139
|
+
Jetpants.mysql_grant_ips = ip_holder
|
91
140
|
end
|
92
141
|
|
93
142
|
end
|
@@ -48,44 +48,67 @@ module Jetpants
|
|
48
48
|
# Pauses replication
|
49
49
|
def pause_replication
|
50
50
|
raise "This DB object has no master" unless master
|
51
|
-
return if @repl_paused
|
52
51
|
output "Pausing replication from #{@master}."
|
53
|
-
|
54
|
-
|
52
|
+
if @repl_paused
|
53
|
+
output "Replication was already paused."
|
54
|
+
repl_binlog_coordinates(true)
|
55
|
+
else
|
56
|
+
output mysql_root_cmd "STOP SLAVE"
|
57
|
+
repl_binlog_coordinates(true)
|
58
|
+
@repl_paused = true
|
59
|
+
end
|
55
60
|
end
|
56
61
|
alias stop_replication pause_replication
|
57
62
|
|
58
63
|
# Starts replication, or restarts replication after a pause
|
59
64
|
def resume_replication
|
60
65
|
raise "This DB object has no master" unless master
|
66
|
+
repl_binlog_coordinates(true)
|
61
67
|
output "Resuming replication from #{@master}."
|
62
68
|
output mysql_root_cmd "START SLAVE"
|
63
69
|
@repl_paused = false
|
64
70
|
end
|
65
71
|
alias start_replication resume_replication
|
66
72
|
|
73
|
+
# Stops replication at the same coordinates on two nodes
|
74
|
+
def pause_replication_with(sibling)
|
75
|
+
[self, sibling].each &:pause_replication
|
76
|
+
|
77
|
+
# self and sibling at same coordinates: all done
|
78
|
+
return true if repl_binlog_coordinates == sibling.repl_binlog_coordinates
|
79
|
+
|
80
|
+
# self ahead of sibling: handle via recursion with roles swapped
|
81
|
+
return sibling.pause_replication_with(self) if ahead_of? sibling
|
82
|
+
|
83
|
+
# sibling ahead of self: catch up to sibling
|
84
|
+
sibling_coords = sibling.repl_binlog_coordinates
|
85
|
+
output "Resuming replication from #{@master} until (#{sibling_coords[0]}, #{sibling_coords[1]})."
|
86
|
+
output(mysql_root_cmd "START SLAVE UNTIL MASTER_LOG_FILE = '#{sibling_coords[0]}', MASTER_LOG_POS = #{sibling_coords[1]}")
|
87
|
+
sleep 1 while repl_binlog_coordinates != sibling_coords
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
67
91
|
# Permanently disables replication. Clears out the SHOW SLAVE STATUS output
|
68
92
|
# entirely in MySQL versions that permit this.
|
69
93
|
def disable_replication!
|
70
|
-
|
94
|
+
stop_replication
|
71
95
|
output "Disabling replication; this db is no longer a slave."
|
72
|
-
|
73
96
|
ver = version_tuple
|
74
|
-
|
97
|
+
|
75
98
|
# MySQL < 5.5: allows master_host='', which clears out SHOW SLAVE STATUS
|
76
99
|
if ver[0] == 5 && ver[1] < 5
|
77
|
-
output mysql_root_cmd "
|
78
|
-
|
100
|
+
output mysql_root_cmd "CHANGE MASTER TO master_host=''; RESET SLAVE"
|
101
|
+
|
79
102
|
# MySQL 5.5.16+: allows RESET SLAVE ALL, which clears out SHOW SLAVE STATUS
|
80
103
|
elsif ver[0] >= 5 && (ver[0] > 5 || ver[1] >= 5) && (ver[0] > 5 || ver[1] > 5 || ver[2] >= 16)
|
81
|
-
output mysql_root_cmd "
|
82
|
-
|
104
|
+
output mysql_root_cmd "CHANGE MASTER TO master_user='test'; RESET SLAVE ALL"
|
105
|
+
|
83
106
|
# Other versions: no safe way to clear out SHOW SLAVE STATUS. Still set master_user to 'test'
|
84
107
|
# so that we know to ignore the slave status output.
|
85
108
|
else
|
86
|
-
output mysql_root_cmd "
|
109
|
+
output mysql_root_cmd "CHANGE MASTER TO master_user='test'; RESET SLAVE"
|
87
110
|
end
|
88
|
-
|
111
|
+
|
89
112
|
@master.slaves.delete(self) rescue nil
|
90
113
|
@master = nil
|
91
114
|
@repl_paused = nil
|
@@ -112,6 +135,7 @@ module Jetpants
|
|
112
135
|
log_pos: pos,
|
113
136
|
user: repl_user,
|
114
137
|
password: repl_pass )
|
138
|
+
t.enable_read_only!
|
115
139
|
end
|
116
140
|
resume_replication if @master # should already have happened from the clone_to! restart anyway, but just to be explicit
|
117
141
|
enable_monitoring
|
@@ -133,6 +157,7 @@ module Jetpants
|
|
133
157
|
log_pos: pos,
|
134
158
|
user: replication_credentials[:user],
|
135
159
|
password: replication_credentials[:pass] )
|
160
|
+
t.enable_read_only!
|
136
161
|
end
|
137
162
|
resume_replication # should already have happened from the clone_to! restart anyway, but just to be explicit
|
138
163
|
catch_up_to_master
|
@@ -240,18 +265,42 @@ module Jetpants
|
|
240
265
|
user && pass ? {user: user, pass: pass} : Jetpants.replication_credentials
|
241
266
|
end
|
242
267
|
|
243
|
-
#
|
244
|
-
#
|
268
|
+
# This method is no longer supported. It used to manipulate /etc/my.cnf directly, which was too brittle.
|
269
|
+
# You can achieve the same effect by passing parameters to DB#restart_mysql.
|
245
270
|
def disable_binary_logging
|
246
|
-
|
247
|
-
comment_out_ini(mysql_config_file, 'log-bin', 'log-slave-updates')
|
271
|
+
raise "DB#disable_binary_logging is no longer supported, please use DB#restart_mysql('--skip-log-bin', '--skip-log-slave-updates') instead"
|
248
272
|
end
|
249
273
|
|
250
|
-
#
|
251
|
-
#
|
274
|
+
# This method is no longer supported. It used to manipulate /etc/my.cnf directly, which was too brittle.
|
275
|
+
# You can achieve the same effect by passing (or NOT passing) parameters to DB#restart_mysql.
|
252
276
|
def enable_binary_logging
|
253
|
-
|
254
|
-
|
277
|
+
raise "DB#enable_binary_logging is no longer supported, please use DB#restart_mysql() instead"
|
278
|
+
end
|
279
|
+
|
280
|
+
# Return true if this node's replication progress is ahead of the provided
|
281
|
+
# node, or false otherwise. The nodes must be in the same pool for coordinates
|
282
|
+
# to be comparable. Does not work in hierarchical replication scenarios!
|
283
|
+
def ahead_of?(node)
|
284
|
+
my_pool = pool(true)
|
285
|
+
raise "Node #{node} is not in the same pool as #{self}" unless node.pool(true) == my_pool
|
286
|
+
|
287
|
+
my_coords = (my_pool.master == self ? binlog_coordinates : repl_binlog_coordinates)
|
288
|
+
node_coords = (my_pool.master == node ? node.binlog_coordinates : node.repl_binlog_coordinates)
|
289
|
+
|
290
|
+
# Same coordinates
|
291
|
+
if my_coords == node_coords
|
292
|
+
false
|
293
|
+
|
294
|
+
# Same logfile: simply compare position
|
295
|
+
elsif my_coords[0] == node_coords[0]
|
296
|
+
my_coords[1] > node_coords[1]
|
297
|
+
|
298
|
+
# Different logfile
|
299
|
+
else
|
300
|
+
my_logfile_num = my_coords[0].match(/^[a-zA-Z.0]+(\d+)$/)[1].to_i
|
301
|
+
node_logfile_num = node_coords[0].match(/^[a-zA-Z.0]+(\d+)$/)[1].to_i
|
302
|
+
my_logfile_num > node_logfile_num
|
303
|
+
end
|
255
304
|
end
|
256
305
|
|
257
306
|
end
|
data/lib/jetpants/db/server.rb
CHANGED
@@ -9,28 +9,44 @@ module Jetpants
|
|
9
9
|
# OK to use this if MySQL is already stopped; it's a no-op then.
|
10
10
|
def stop_mysql
|
11
11
|
output "Attempting to shutdown MySQL"
|
12
|
+
disconnect if @db
|
12
13
|
output service(:stop, 'mysql')
|
13
14
|
running = ssh_cmd "netstat -ln | grep #{@port} | wc -l"
|
14
15
|
raise "[#{@ip}] Failed to shut down MySQL: Something is still listening on port #{@port}" unless running.chomp == '0'
|
16
|
+
@options = []
|
15
17
|
@running = false
|
16
18
|
end
|
17
19
|
|
18
20
|
# Starts MySQL, and confirms that something is now listening on the port.
|
19
21
|
# Raises an exception if MySQL is already running or if something else is
|
20
22
|
# already running on its port.
|
21
|
-
|
22
|
-
|
23
|
+
# Options should be supplied as positional method args, for example:
|
24
|
+
# start_mysql '--skip-networking', '--skip-grant-tables'
|
25
|
+
def start_mysql(*options)
|
26
|
+
if @master
|
27
|
+
@repl_paused = options.include?('--skip-slave-start')
|
28
|
+
end
|
23
29
|
running = ssh_cmd "netstat -ln | grep #{@port} | wc -l"
|
24
30
|
raise "[#{@ip}] Failed to start MySQL: Something is already listening on port #{@port}" unless running.chomp == '0'
|
25
|
-
|
26
|
-
|
31
|
+
if options.size == 0
|
32
|
+
output "Attempting to start MySQL, no option overrides supplied"
|
33
|
+
else
|
34
|
+
output "Attempting to start MySQL with options #{options.join(' ')}"
|
35
|
+
end
|
36
|
+
output service(:start, 'mysql', options.join(' '))
|
37
|
+
@options = options
|
27
38
|
confirm_listening
|
28
39
|
@running = true
|
40
|
+
if role == :master && ! @options.include?('--skip-networking')
|
41
|
+
disable_read_only!
|
42
|
+
end
|
29
43
|
end
|
30
44
|
|
31
45
|
# Restarts MySQL.
|
32
|
-
def restart_mysql
|
33
|
-
|
46
|
+
def restart_mysql(*options)
|
47
|
+
if @master
|
48
|
+
@repl_paused = options.include?('--skip-slave-start')
|
49
|
+
end
|
34
50
|
|
35
51
|
# Disconnect if we were previously connected
|
36
52
|
user, schema = false, false
|
@@ -39,13 +55,21 @@ module Jetpants
|
|
39
55
|
disconnect
|
40
56
|
end
|
41
57
|
|
42
|
-
|
43
|
-
|
58
|
+
if options.size == 0
|
59
|
+
output "Attempting to restart MySQL, no option overrides supplied"
|
60
|
+
else
|
61
|
+
output "Attempting to restart MySQL with options #{options.join(' ')}"
|
62
|
+
end
|
63
|
+
output service(:restart, 'mysql', options.join(' '))
|
64
|
+
@options = options
|
44
65
|
confirm_listening
|
45
66
|
@running = true
|
46
|
-
|
47
|
-
|
48
|
-
|
67
|
+
unless @options.include?('--skip-networking')
|
68
|
+
disable_read_only! if role == :master
|
69
|
+
|
70
|
+
# Reconnect if we were previously connected
|
71
|
+
connect(user: user, schema: schema) if user || schema
|
72
|
+
end
|
49
73
|
end
|
50
74
|
|
51
75
|
# Has no built-in effect. Plugins can override it, and/or implement
|
@@ -60,7 +84,12 @@ module Jetpants
|
|
60
84
|
|
61
85
|
# Confirms that a process is listening on the DB's port
|
62
86
|
def confirm_listening(timeout=10)
|
63
|
-
|
87
|
+
if @options.include? '--skip-networking'
|
88
|
+
output 'Unable to confirm mysqld listening because server started with --skip-networking'
|
89
|
+
false
|
90
|
+
else
|
91
|
+
confirm_listening_on_port(@port, timeout)
|
92
|
+
end
|
64
93
|
end
|
65
94
|
|
66
95
|
# Returns the MySQL data directory for this instance. A plugin can override this
|
@@ -69,13 +98,6 @@ module Jetpants
|
|
69
98
|
'/var/lib/mysql'
|
70
99
|
end
|
71
100
|
|
72
|
-
# Returns the MySQL server configuration file for this instance. A plugin can
|
73
|
-
# override this if needed, especially if running multiple MySQL instances on
|
74
|
-
# the same host.
|
75
|
-
def mysql_config_file
|
76
|
-
'/etc/my.cnf'
|
77
|
-
end
|
78
|
-
|
79
101
|
# Has no built-in effect. Plugins can override it, and/or implement
|
80
102
|
# before_enable_monitoring and after_enable_monitoring callbacks.
|
81
103
|
def enable_monitoring(*services)
|
data/lib/jetpants/db/state.rb
CHANGED
@@ -105,6 +105,28 @@ module Jetpants
|
|
105
105
|
sleep(interval)
|
106
106
|
global_status[:Connections].to_i - conn_counter > threshold
|
107
107
|
end
|
108
|
+
|
109
|
+
# Gets the max theads connected over a time period
|
110
|
+
def max_threads_running(tries=8, interval=1.0)
|
111
|
+
poll_status_value(:Threads_running,:max, tries, interval)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Gets the max or avg for a mysql value
|
115
|
+
def poll_status_value(field, type=:max, tries=8, interval=1.0)
|
116
|
+
max = 0
|
117
|
+
sum = 0
|
118
|
+
tries.times do
|
119
|
+
value = global_status[field].to_i
|
120
|
+
max = value unless max > value
|
121
|
+
sum += value
|
122
|
+
sleep(interval)
|
123
|
+
end
|
124
|
+
if type == :max
|
125
|
+
max
|
126
|
+
elsif type == :avg
|
127
|
+
sum.to_f/tries.to_f
|
128
|
+
end
|
129
|
+
end
|
108
130
|
|
109
131
|
# Confirms the binlog of this node has not moved during a duration
|
110
132
|
# of [interval] seconds.
|
@@ -134,6 +156,27 @@ module Jetpants
|
|
134
156
|
@host.hostname.start_with? 'backup'
|
135
157
|
end
|
136
158
|
|
159
|
+
# Returns true if the node can be promoted to be the master of its pool,
|
160
|
+
# false otherwise (also false if node is ALREADY the master)
|
161
|
+
# Don't use this in hierarchical replication scenarios, result may be
|
162
|
+
# unexpected.
|
163
|
+
def promotable_to_master?(detect_version_mismatches=true)
|
164
|
+
# backup_slaves are non-promotable
|
165
|
+
return false if for_backups?
|
166
|
+
|
167
|
+
# already the master
|
168
|
+
p = pool(true)
|
169
|
+
return false if p.master == self
|
170
|
+
|
171
|
+
# ordinarily, cannot promote a slave that's running a higher version of
|
172
|
+
# MySQL than any other node in the pool.
|
173
|
+
if detect_version_mismatches
|
174
|
+
p.nodes.all? {|db| db == self || !db.available? || db.version_cmp(self) >= 0}
|
175
|
+
else
|
176
|
+
true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
137
180
|
# Returns a hash mapping global MySQL variables (as symbols)
|
138
181
|
# to their values (as strings).
|
139
182
|
def global_variables
|
@@ -155,15 +198,51 @@ module Jetpants
|
|
155
198
|
# Returns an array of integers representing the version of the MySQL server.
|
156
199
|
# For example, Percona Server 5.5.27-rel28.1-log would return [5, 5, 27]
|
157
200
|
def version_tuple
|
158
|
-
|
159
|
-
|
201
|
+
result = nil
|
202
|
+
if running?
|
203
|
+
# If the server is running, we can just query it
|
204
|
+
result = global_variables[:version].split('.', 3).map(&:to_i) rescue nil
|
205
|
+
end
|
206
|
+
if result.nil?
|
207
|
+
# Otherwise we need to parse the output of mysqld --version
|
208
|
+
output = ssh_cmd 'mysqld --version'
|
209
|
+
matches = output.downcase.match('ver\s*(\d+)\.(\d+)\.(\d+)')
|
210
|
+
raise "Unable to determine version for #{self}" unless matches
|
211
|
+
result = matches[1, 3].map(&:to_i)
|
212
|
+
end
|
213
|
+
result
|
214
|
+
end
|
215
|
+
|
216
|
+
# Return a string representing the version. The precision indicates how
|
217
|
+
# many major/minor version numbers to return.
|
218
|
+
# ie, on 5.5.29, normalized_version(3) returns '5.5.29',
|
219
|
+
# normalized_version(2) returns '5.5', and normalized_version(1) returns '5'
|
220
|
+
def normalized_version(precision=2)
|
221
|
+
raise "Invalid precision #{precision}" if precision < 1 || precision > 3
|
222
|
+
version_tuple[0, precision].join('.')
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns -1 if self is running a lower version than db; 1 if self is running
|
226
|
+
# a higher version; and 0 if running same version.
|
227
|
+
def version_cmp(db, precision=2)
|
228
|
+
raise "Invalid precision #{precision}" if precision < 1 || precision > 3
|
229
|
+
my_tuple = version_tuple[0, precision]
|
230
|
+
other_tuple = db.version_tuple[0, precision]
|
231
|
+
my_tuple.each_with_index do |subver, i|
|
232
|
+
return -1 if subver < other_tuple[i]
|
233
|
+
return 1 if subver > other_tuple[i]
|
234
|
+
end
|
235
|
+
0
|
160
236
|
end
|
161
237
|
|
162
238
|
# Returns the Jetpants::Pool that this instance belongs to, if any.
|
163
239
|
# Can optionally create an anonymous pool if no pool was found. This anonymous
|
164
240
|
# pool intentionally has a blank sync_configuration implementation.
|
165
241
|
def pool(create_if_missing=false)
|
166
|
-
result = Jetpants.topology.pool(self)
|
242
|
+
result = Jetpants.topology.pool(self)
|
243
|
+
if !result && master
|
244
|
+
result ||= Jetpants.topology.pool(master)
|
245
|
+
end
|
167
246
|
if !result && create_if_missing
|
168
247
|
pool_master = master || self
|
169
248
|
result = Pool.new('anon_pool_' + pool_master.ip.tr('.', ''), pool_master)
|
@@ -178,10 +257,15 @@ module Jetpants
|
|
178
257
|
# Note that we consider a node with no master and no slaves to be
|
179
258
|
# a :master, since we can't determine if it had slaves but they're
|
180
259
|
# just offline/dead, vs it being an orphaned machine.
|
260
|
+
#
|
261
|
+
# In hierarchical replication scenarios (such as the child shard
|
262
|
+
# masters in the middle of a shard split), we return :master if
|
263
|
+
# Jetpants.topology considers the node to be the master for a pool.
|
181
264
|
def role
|
182
265
|
p = pool
|
183
266
|
case
|
184
|
-
when !@master then :master
|
267
|
+
when !@master then :master # nodes that aren't slaves (including orphans)
|
268
|
+
when p.master == self then :master # nodes that the topology thinks are masters
|
185
269
|
when for_backups? then :backup_slave
|
186
270
|
when p && p.active_slave_weights[self] then :active_slave # if pool in topology, determine based on expected/ideal state
|
187
271
|
when !p && !is_standby? then :active_slave # if pool missing from topology, determine based on actual state
|
@@ -194,10 +278,15 @@ module Jetpants
|
|
194
278
|
# a metric gigabyte. This puts it on the same scale as the output to tools like
|
195
279
|
# "du -h" and "df -h".
|
196
280
|
def data_set_size(in_gb=false)
|
197
|
-
bytes = dir_size("#{mysql_directory}/#{app_schema}")
|
281
|
+
bytes = dir_size("#{mysql_directory}/#{app_schema}") + dir_size("#{mysql_directory}/ibdata1")
|
198
282
|
in_gb ? (bytes / 1073741824.0).round : bytes
|
199
283
|
end
|
200
284
|
|
285
|
+
def mount_stats(mount=false)
|
286
|
+
mount ||= mysql_directory
|
287
|
+
|
288
|
+
host.mount_stats(mount)
|
289
|
+
end
|
201
290
|
|
202
291
|
###### Private methods #####################################################
|
203
292
|
|
@@ -230,9 +319,11 @@ module Jetpants
|
|
230
319
|
else
|
231
320
|
@master = self.class.new(status[:master_host], status[:master_port])
|
232
321
|
if status[:slave_io_running] != status[:slave_sql_running]
|
233
|
-
|
234
|
-
|
235
|
-
|
322
|
+
output "One replication thread is stopped and the other is not."
|
323
|
+
if Jetpants.verify_replication
|
324
|
+
output "You must repair this node manually, OR remove it from its pool permanently if it is unrecoverable."
|
325
|
+
raise "Fatal replication problem on #{self}"
|
326
|
+
end
|
236
327
|
pause_replication
|
237
328
|
else
|
238
329
|
@repl_paused = (status[:slave_io_running].downcase == 'no')
|