Class: EY::Backup::MysqlEngine
- Inherits:
-
Engine
show all
- Defined in:
- lib/ey_backup/engines/mysql_engine.rb
Constant Summary
Constants included
from Spawner
Spawner::CHUNK_SIZE
Instance Attribute Summary
Attributes inherited from Engine
#allow_concurrent, #force, #host, #key_id, #log_coordinates, #password, #skip_analyze, #username
Instance Method Summary
collapse
Methods inherited from Engine
#backup_running?, #block_concurrent, descendants, #gpg?, inherited, #initialize, label, lookup, register
Methods included from Spawner
#ioify, #run, #runs?, #spawn
Methods inherited from Base
#logger
Instance Method Details
#capture_create_database_statement(database_name) ⇒ Object
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 160
def capture_create_database_statement(database_name)
query = "show create database `#{database_name}` \\G"
raw = %x(mysql #{username_option} #{host_option} -NB -e #{Shellwords.escape(query)})
capture_status = $?
create_cmd = raw.to_s.split("\n").map(&:strip).reject(&:empty?).last.to_s
if create_cmd.empty? || !capture_status.success?
"CREATE DATABASE `#{database_name}`"
else
create_cmd
end
end
|
#check_if_replica ⇒ Object
48
49
50
51
52
53
54
55
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 48
def check_if_replica
err_msg="ERROR: Target host: '#{host}' is currently: "
err_msg=err_msg + "a replica, " if db_replicating?
err_msg=err_msg + "in read_only mode, " if read_only_on?
err_msg=err_msg + "restores should be processed against the master."
EY::Backup.logger.fatal(%Q{#{err_msg}}) if db_replicating? or read_only_on?
end
|
#cycle_database(database_name) ⇒ Object
149
150
151
152
153
154
155
156
157
158
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 149
def cycle_database(database_name)
create_cmd = capture_create_database_statement(database_name)
unless create_cmd =~ /\ACREATE\s+DATABASE\b/i
raise "Refusing to restore '#{database_name}': could not obtain a valid CREATE DATABASE statement (got #{create_cmd.inspect}). The existing database has NOT been touched."
end
drop_database!(database_name)
recreate_database!(create_cmd, database_name)
end
|
#databases_option(db) ⇒ Object
72
73
74
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 72
def databases_option(db)
"#{db}"
end
|
#db_has_myisam?(database_name) ⇒ Boolean
88
89
90
91
92
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 88
def db_has_myisam?(database_name)
query = "SELECT 1 FROM information_schema.tables WHERE table_schema='#{database_name}' AND engine='MyISAM' LIMIT 1;"
stdout = %x{mysql #{username_option} #{host_option} -N -e"#{query}"}
stdout.strip == '1'
end
|
#db_replicating? ⇒ Boolean
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 94
def db_replicating?
statement = replica_status_statement
stdout = %x(mysql #{username_option} #{host_option} -BN -e #{Shellwords.escape(statement)})
probe_status = $?
unless probe_status.success?
warn("Unable to determine replication status on '#{host}' via #{statement.inspect}: #{stdout.strip}; treating host as a possible replica.")
return true
end
!stdout.strip.empty?
end
|
#drop_database!(database_name) ⇒ Object
180
181
182
183
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 180
def drop_database!(database_name)
%x(mysql #{username_option} #{host_option} -e #{Shellwords.escape("DROP DATABASE IF EXISTS `#{database_name}`")})
raise "Failed to drop database '#{database_name}' prior to restore; the existing database was left intact." unless $?.success?
end
|
#dump(database_name, basename) ⇒ Object
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 8
def dump(database_name, basename)
file = basename + '.sql'
command = "( #{server_id} mysqldump #{username_option} #{host_option} #{single_transaction_option(database_name)} #{routines_option} #{master_data_option} #{databases_option(database_name)} ) "
if gpg?
command << " | " << GPGEncryptor.command_for(key_id)
file << GPGEncryptor.extension
else
command << " | " << GZipper.gzip
file << GZipper.extension
end
command << " > #{file}"
block_concurrent(database_name) unless allow_concurrent
run(command, database_name)
file
end
|
#host_option ⇒ Object
76
77
78
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 76
def host_option
"-h#{host}"
end
|
#load(database_name, file) ⇒ Object
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 30
def load(database_name, file)
command = "cat #{file}"
if file =~ /.gpz$/
abort "\nCannot restore a GPG backup directly; decrypt the file (#{file}) using your key and then load using the mysql client.
To decrypt a backup: https://support.cloud.engineyard.com/hc/en-us/articles/205413948-Use-PGP-Encrypted-Database-Backups-with-Engine-Yard-Cloud#restore
Once decrypted, restore with: `gunzip -f < <filename> | mysql #{username_option} #{host_option} #{database_name}`\n\n"
else
command << " | " << GZipper.gunzip
end
cycle_database(database_name)
command << " | mysql #{username_option} #{host_option} #{database_name} "
run(command, database_name)
end
|
#log_bin_on? ⇒ Boolean
144
145
146
147
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 144
def log_bin_on?
stdout = %x{mysql #{username_option} #{host_option} -BN -e"select @@global.log_bin"}
stdout.to_i == 1
end
|
#master_data_option ⇒ Object
68
69
70
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 68
def master_data_option
"--master-data=2" if log_bin_on? and log_coordinates
end
|
#mysql_8_4_or_newer? ⇒ Boolean
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 119
def mysql_8_4_or_newer?
stdout = %x(mysql #{username_option} #{host_option} -BN -e #{Shellwords.escape('select @@version')})
return false unless $?.success?
version = stdout.strip
return false if version =~ /mariadb/i
numeric = version[/\A\d+\.\d+/]
return false unless numeric
major, minor = numeric.split('.').map(&:to_i)
major > 8 || (major == 8 && minor >= 4)
end
|
#read_only_on? ⇒ Boolean
139
140
141
142
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 139
def read_only_on?
stdout = %x{mysql #{username_option} #{host_option} -BN -e"select @@global.read_only"}
stdout.to_i == 1
end
|
#recreate_database!(create_cmd, database_name) ⇒ Object
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 185
def recreate_database!(create_cmd, database_name)
IO.popen(['mysql', username_option, host_option], 'w') do |io|
io.write(create_cmd)
io.write(";\n")
end
unless $?.success?
raise "Failed to recreate database '#{database_name}' after DROP -- the database has been dropped and NOT recreated. Statement attempted: #{create_cmd.inspect}"
end
end
|
#replica_status_statement ⇒ Object
SHOW SLAVE STATUS was deprecated in MySQL 8.0.22 and removed in
8.4, replaced by SHOW REPLICA STATUS.
115
116
117
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 115
def replica_status_statement
mysql_8_4_or_newer? ? "show replica status" : "show slave status"
end
|
#routines_option ⇒ Object
57
58
59
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 57
def routines_option
"--routines"
end
|
#server_id ⇒ Object
61
62
63
64
65
66
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 61
def server_id
if log_bin_on? and log_coordinates
stdout = %x{mysql #{username_option} #{host_option} -BN -e"select @@global.server_id"}
"echo '-- Server_id: #{stdout.strip}' && "
end
end
|
#single_transaction_option(database_name) ⇒ Object
84
85
86
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 84
def single_transaction_option(database_name)
'--single-transaction' unless db_has_myisam?(database_name)
end
|
#suffix ⇒ Object
203
204
205
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 203
def suffix
/sql\.(gz|gpz)$/
end
|
#username_option ⇒ Object
80
81
82
|
# File 'lib/ey_backup/engines/mysql_engine.rb', line 80
def username_option
"-u#{username}"
end
|