jetpants 0.7.10 → 0.8.0
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.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')
|