Class: LL::InnoBackup
- Inherits:
-
Object
show all
- Defined in:
- lib/ll-innobackup.rb
Defined Under Namespace
Classes: NoStateError, StateError
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Constructor Details
#initialize(options = {}) ⇒ InnoBackup
Returns a new instance of InnoBackup.
67
68
69
70
71
72
73
74
|
# File 'lib/ll-innobackup.rb', line 67
def initialize(options = {})
@now = Time.now
@date = @now.to_date
@options = options
@lock_files = {}
@state_files = {}
@type = backup_type
end
|
Instance Attribute Details
#date ⇒ Object
Returns the value of attribute date.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def date
@date
end
|
#lock_files ⇒ Object
Returns the value of attribute lock_files.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def lock_files
@lock_files
end
|
#now ⇒ Object
Returns the value of attribute now.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def now
@now
end
|
#options ⇒ Object
Returns the value of attribute options.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def options
@options
end
|
#state_files ⇒ Object
Returns the value of attribute state_files.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def state_files
@state_files
end
|
#type ⇒ Object
Returns the value of attribute type.
60
61
62
|
# File 'lib/ll-innobackup.rb', line 60
def type
@type
end
|
Class Method Details
.innobackup_log(t) ⇒ Object
55
56
57
|
# File 'lib/ll-innobackup.rb', line 55
def innobackup_log(t)
"/tmp/backup_#{t}_innobackup_log"
end
|
.lock_file(type) ⇒ Object
51
52
53
|
# File 'lib/ll-innobackup.rb', line 51
def lock_file(type)
"/tmp/backup_#{type}.lock"
end
|
.options ⇒ Object
Use this in case the log file is massive
9
10
11
12
13
|
# File 'lib/ll-innobackup.rb', line 9
def options
JSON.parse(File.read('/etc/mysql/innobackupex.json'))
rescue Errno::ENOENT
{}
end
|
.state_file(t) ⇒ Object
47
48
49
|
# File 'lib/ll-innobackup.rb', line 47
def state_file(t)
"/tmp/backup_#{t}_state"
end
|
.tail_file(path, n) ⇒ Object
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
# File 'lib/ll-innobackup.rb', line 15
def tail_file(path, n)
file = File.open(path, 'r')
buffer_s = 512
line_count = 0
file.seek(0, IO::SEEK_END)
offset = file.pos
while line_count <= n && offset > 0
to_read = if (offset - buffer_s) < 0
offset
else
buffer_s
end
file.seek(offset - to_read)
data = file.read(to_read)
data.reverse.each_char do |c|
if line_count > n
offset += 1
break
end
offset -= 1
line_count += 1 if c == "\n|"
end
end
file.seek(offset)
file.read
end
|
Instance Method Details
#aws_backup_file ⇒ Object
311
312
313
314
315
316
|
# File 'lib/ll-innobackup.rb', line 311
def aws_backup_file
return "#{hostname}/#{now.iso8601}/percona_full_backup" if type == 'full'
"#{hostname}/#{Time.parse(state('full')['date']).iso8601}/percona_incremental_#{now.iso8601}"
rescue NoMethodError
raise NoStateError, 'incremental state missing or corrupt'
end
|
#aws_bin ⇒ Object
158
159
160
|
# File 'lib/ll-innobackup.rb', line 158
def aws_bin
@aws_bin = options['aws_bin'] ||= '/usr/local/bin/aws'
end
|
#aws_bucket ⇒ Object
162
163
164
165
|
# File 'lib/ll-innobackup.rb', line 162
def aws_bucket
raise NoStateError, 'aws_bucket not provided' unless options['aws_bucket']
@aws_bucket = options['aws_bucket']
end
|
#aws_command ⇒ Object
220
221
222
223
224
|
# File 'lib/ll-innobackup.rb', line 220
def aws_command
"#{aws_bin} s3 cp #{working_file} s3://#{aws_bucket}/#{aws_backup_file} "\
"#{expected_size} #{expires} "\
"2> #{aws_log} >> #{aws_log}"
end
|
#aws_log ⇒ Object
76
77
78
|
# File 'lib/ll-innobackup.rb', line 76
def aws_log
"/tmp/backup_#{type}_aws_log"
end
|
#backup ⇒ Object
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
# File 'lib/ll-innobackup.rb', line 230
def backup
require 'English'
return unless valid_commands?
`#{innobackup_command}`
@completed_inno = $CHILD_STATUS == 0
raise InnoBackup::StateError, 'Unable to run innobackup correctly' unless @completed_inno
`#{aws_command}`
@completed_aws = $CHILD_STATUS == 0
raise InnoBackup::StateError, 'Unable to run aws upload correctly' unless @completed_aws
return record if success? && completed?
rescue InnoBackup::StateError => e
revert_aws
rescue InnoBackup::NoStateError => e
STDERR.puts e.message
ensure
report
cleanup
end
|
#backup_bin ⇒ Object
130
131
132
|
# File 'lib/ll-innobackup.rb', line 130
def backup_bin
@backup_bin = options['backup_bin'] ||= '/usr/bin/innobackupex'
end
|
#backup_compress_threads ⇒ Object
138
139
140
|
# File 'lib/ll-innobackup.rb', line 138
def backup_compress_threads
@backup_compress_threads = options['backup_compress_threads'] ||= 4
end
|
#backup_parallel ⇒ Object
134
135
136
|
# File 'lib/ll-innobackup.rb', line 134
def backup_parallel
@backup_parallel = options['backup_parallel'] ||= 4
end
|
#backup_type ⇒ Object
124
125
126
127
128
|
# File 'lib/ll-innobackup.rb', line 124
def backup_type
return 'full' unless fully_backed_up_today? || full_backup_running?
return 'incremental' unless incremental_backup_running?
raise 'Unable to backup as backups are running'
end
|
#can_full_backup? ⇒ Boolean
112
113
114
|
# File 'lib/ll-innobackup.rb', line 112
def can_full_backup?
!fully_backed_up_today? && lock?('full')
end
|
#cleanup ⇒ Object
330
331
332
333
334
|
# File 'lib/ll-innobackup.rb', line 330
def cleanup
File.unlink working_file
rescue StandardError => e
STDERR.puts "Caught exception #{e} when trying to cleanup"
end
|
#completed? ⇒ Boolean
318
319
320
|
# File 'lib/ll-innobackup.rb', line 318
def completed?
completed_aws? && completed_inno?
end
|
#completed_aws? ⇒ Boolean
322
323
324
|
# File 'lib/ll-innobackup.rb', line 322
def completed_aws?
@completed_aws == true
end
|
#completed_inno? ⇒ Boolean
326
327
328
|
# File 'lib/ll-innobackup.rb', line 326
def completed_inno?
@completed_inno == true
end
|
#encryption_key ⇒ Object
154
155
156
|
# File 'lib/ll-innobackup.rb', line 154
def encryption_key
@encryption_key ||= options['encryption_key']
end
|
#encryption_threads ⇒ Object
142
143
144
|
# File 'lib/ll-innobackup.rb', line 142
def encryption_threads
@encryption_threads = options['encryption_threads'] ||= 4
end
|
#expected_full_size ⇒ Object
172
173
174
175
176
177
178
|
# File 'lib/ll-innobackup.rb', line 172
def expected_full_size
@expected_full_size ||= -> do
return File.size(working_file) if File.exist?(working_file)
return options['expected_full_size'] if options['expected_full_size']
1_600_000_000
end.call
end
|
#expected_size ⇒ Object
216
217
218
|
# File 'lib/ll-innobackup.rb', line 216
def expected_size
"--expected-size=#{expected_full_size}" if type == 'full'
end
|
#expires ⇒ Object
211
212
213
214
|
# File 'lib/ll-innobackup.rb', line 211
def expires
ed = expires_date
"--expires=#{ed}" if ed
end
|
#expires_date ⇒ Object
199
200
201
202
203
204
205
206
207
208
209
|
# File 'lib/ll-innobackup.rb', line 199
def expires_date
require 'active_support/all'
return (@now + 2.days).iso8601 if type == 'incremental'
return (@now + 6.months).iso8601 if @date.yesterday.month != @date.month
return (@now + 1.month).iso8601 if @date.cwday == 1
(@now + 2.weeks).iso8601
end
|
#full_backup_running? ⇒ Boolean
116
117
118
|
# File 'lib/ll-innobackup.rb', line 116
def full_backup_running?
!lock?('full')
end
|
#fully_backed_up_today? ⇒ Boolean
96
97
98
99
100
101
102
103
104
105
106
|
# File 'lib/ll-innobackup.rb', line 96
def fully_backed_up_today?
require 'active_support/all'
date = state('full')['date']
Time.parse(date).today?
rescue Errno::ENOENT
puts 'unable to obtain last full backup state'
false
rescue NoMethodError
puts 'unable to obtain last backup state'
false
end
|
#hostname ⇒ Object
301
302
303
304
305
|
# File 'lib/ll-innobackup.rb', line 301
def hostname
return options['hostname'] if options['hostname']
require 'socket'
Socket.gethostbyname(Socket.gethostname).first
end
|
#incremental ⇒ Object
275
276
277
278
|
# File 'lib/ll-innobackup.rb', line 275
def incremental
return unless backup_type == 'incremental'
"--incremental --incremental-lsn=#{lsn_from_state}"
end
|
#incremental_backup_running? ⇒ Boolean
120
121
122
|
# File 'lib/ll-innobackup.rb', line 120
def incremental_backup_running?
!lock?('incremental')
end
|
#innobackup_command ⇒ Object
193
194
195
196
197
|
# File 'lib/ll-innobackup.rb', line 193
def innobackup_command
"#{backup_bin} #{sql_authentication} "\
"#{incremental} #{innobackup_options} /tmp/sql "\
"2> #{innobackup_log} > #{working_file}"
end
|
#innobackup_log ⇒ Object
80
81
82
|
# File 'lib/ll-innobackup.rb', line 80
def innobackup_log
"/tmp/backup_#{type}_innobackup_log"
end
|
#innobackup_options ⇒ Object
184
185
186
187
188
189
190
191
|
# File 'lib/ll-innobackup.rb', line 184
def innobackup_options
[
"--parallel=#{backup_parallel}",
"--compress-threads=#{backup_compress_threads}",
("--encrypt=AES256 --encrypt-key=#{encryption_key} --encrypt-threads=#{encryption_threads}" if is_encrypted?),
'--stream=xbstream --compress'
].join(" ")
end
|
#is_encrypted? ⇒ Boolean
108
109
110
|
# File 'lib/ll-innobackup.rb', line 108
def is_encrypted?
!options['encryption_key'].empty?
end
|
#lock?(t = type) ⇒ Boolean
84
85
86
87
|
# File 'lib/ll-innobackup.rb', line 84
def lock?(t = type)
lock_files[t] ||= File.new(InnoBackup.lock_file(t), File::CREAT)
lock_files[t].flock(File::LOCK_NB | File::LOCK_EX).zero?
end
|
#lsn_from_backup_log ⇒ Object
293
294
295
296
297
298
299
|
# File 'lib/ll-innobackup.rb', line 293
def lsn_from_backup_log
matches = InnoBackup.tail_file(
InnoBackup.innobackup_log(type),
30
).match(/The latest check point \(for incremental\): '(\d+)'/)
matches[1] if matches
end
|
#lsn_from_full_backup_state? ⇒ Boolean
280
281
282
283
284
|
# File 'lib/ll-innobackup.rb', line 280
def lsn_from_full_backup_state?
Time.parse(state('full')['date']) > Time.parse(state('incremental')['date'])
rescue Errno::ENOENT
true
end
|
#lsn_from_state ⇒ Object
286
287
288
289
290
291
|
# File 'lib/ll-innobackup.rb', line 286
def lsn_from_state
return state('full')['lsn'] if lsn_from_full_backup_state?
state('incremental')['lsn']
rescue NoMethodError
raise NoStateError, 'no state file for incremental backup'
end
|
#record ⇒ Object
264
265
266
267
268
269
270
271
272
273
|
# File 'lib/ll-innobackup.rb', line 264
def record
File.write(
InnoBackup.state_file(type),
{
date: now,
lsn: lsn_from_backup_log,
file: aws_backup_file
}.to_json
)
end
|
#report ⇒ Object
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
|
# File 'lib/ll-innobackup.rb', line 336
def report
if success? && completed?
STDERR.puts "#{$PROGRAM_NAME}: success: completed #{type} backup"
return
end
STDERR.puts "Unable to run innobackup" unless completed_inno?
STDERR.puts "Unable to run aws s3 command" unless completed_aws?
STDERR.puts "#{$PROGRAM_NAME}: failed"
STDERR.puts 'missing binaries' unless valid_commands?
inno_tail = InnoBackup.tail_file(innobackup_log, 10)
STDERR.puts 'invalid sql user' if inno_tail =~ /Option user requires an argument/
STDERR.puts 'unable to connect to DB' if inno_tail =~ /Access denied for user/
STDERR.puts 'insufficient file access' if inno_tail =~ /Can't change dir to/
aws_tail = InnoBackup.tail_file(aws_log, 10)
STDERR.puts 'bucket incorrect' if aws_tail =~ /The specified bucket does not exist/
STDERR.puts 'invalid AWS key' if aws_tail =~ /The AWS Access Key Id you/
STDERR.puts 'invalid Secret key' if aws_tail =~ /The request signature we calculated/
end
|
#revert_aws ⇒ Object
250
251
252
253
|
# File 'lib/ll-innobackup.rb', line 250
def revert_aws
exc = "#{aws_bin} s3 rm s3://#{aws_bucket}/#{aws_backup_file} > /dev/null 2>/dev/null"
`#{exc}`
end
|
#sql_authentication ⇒ Object
180
181
182
|
# File 'lib/ll-innobackup.rb', line 180
def sql_authentication
"--user=#{sql_backup_user} --password=#{sql_backup_password}"
end
|
#sql_backup_password ⇒ Object
150
151
152
|
# File 'lib/ll-innobackup.rb', line 150
def sql_backup_password
@sql_backup_password ||= options['sql_backup_password']
end
|
#sql_backup_user ⇒ Object
146
147
148
|
# File 'lib/ll-innobackup.rb', line 146
def sql_backup_user
@sql_backup_user ||= options['sql_backup_user']
end
|
#state(t) ⇒ Object
89
90
91
92
93
94
|
# File 'lib/ll-innobackup.rb', line 89
def state(t)
state_files[t] ||= JSON.parse(File.read(InnoBackup.state_file(t)))
rescue JSON::ParserError
puts 'unable to stat state file'
{}
end
|
#success? ⇒ Boolean
255
256
257
258
259
260
261
262
|
# File 'lib/ll-innobackup.rb', line 255
def success?
InnoBackup.tail_file(
innobackup_log,
1
) =~ / completed OK/
rescue Errno::ENOENT
false
end
|
#valid_commands? ⇒ Boolean
226
227
228
|
# File 'lib/ll-innobackup.rb', line 226
def valid_commands?
File.exist?(backup_bin) && File.exist?(aws_bin)
end
|
#working_directory ⇒ Object
167
168
169
170
|
# File 'lib/ll-innobackup.rb', line 167
def working_directory
return options['working_directory'] if options['working_directory']
'/tmp'
end
|
#working_file ⇒ Object
307
308
309
|
# File 'lib/ll-innobackup.rb', line 307
def working_file
@working_file ||= File.join working_directory, "#{now.iso8601}-percona_backup"
end
|