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.
69
70
71
72
73
74
75
76
77
|
# File 'lib/ll-innobackup.rb', line 69
def initialize(options = {})
@now = Time.now
@date = @now.to_date
@options = options
@lock_files = {}
@state_files = {}
@type = backup_type
@s3 = Aws::S3::Resource.new()
end
|
Instance Attribute Details
#date ⇒ Object
Returns the value of attribute date.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def date
@date
end
|
#lock_files ⇒ Object
Returns the value of attribute lock_files.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def lock_files
@lock_files
end
|
#now ⇒ Object
Returns the value of attribute now.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def now
@now
end
|
#options ⇒ Object
Returns the value of attribute options.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def options
@options
end
|
#s3 ⇒ Object
Returns the value of attribute s3.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def s3
@s3
end
|
#state_files ⇒ Object
Returns the value of attribute state_files.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def state_files
@state_files
end
|
#type ⇒ Object
Returns the value of attribute type.
61
62
63
|
# File 'lib/ll-innobackup.rb', line 61
def type
@type
end
|
Class Method Details
.innobackup_log(t) ⇒ Object
56
57
58
|
# File 'lib/ll-innobackup.rb', line 56
def innobackup_log(t)
"/tmp/backup_#{t}_innobackup_log"
end
|
.lock_file(type) ⇒ Object
52
53
54
|
# File 'lib/ll-innobackup.rb', line 52
def lock_file(type)
"/tmp/backup_#{type}.lock"
end
|
.options ⇒ Object
Use this in case the log file is massive
10
11
12
13
14
|
# File 'lib/ll-innobackup.rb', line 10
def options
JSON.parse(File.read('/etc/mysql/innobackupex.json'))
rescue Errno::ENOENT
{}
end
|
.state_file(t) ⇒ Object
48
49
50
|
# File 'lib/ll-innobackup.rb', line 48
def state_file(t)
"/tmp/backup_#{t}_state"
end
|
.tail_file(path, n) ⇒ Object
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
46
|
# File 'lib/ll-innobackup.rb', line 16
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
317
318
319
320
321
322
|
# File 'lib/ll-innobackup.rb', line 317
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
161
162
163
|
# File 'lib/ll-innobackup.rb', line 161
def aws_bin
@aws_bin = options['aws_bin'] ||= '/usr/local/bin/aws'
end
|
#aws_bucket ⇒ Object
165
166
167
168
|
# File 'lib/ll-innobackup.rb', line 165
def aws_bucket
raise NoStateError, 'aws_bucket not provided' unless options['aws_bucket']
@aws_bucket = options['aws_bucket']
end
|
#aws_log ⇒ Object
79
80
81
|
# File 'lib/ll-innobackup.rb', line 79
def aws_log
"/tmp/backup_#{type}_aws_log"
end
|
#backup ⇒ Object
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
|
# File 'lib/ll-innobackup.rb', line 237
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
@completed_aws = s3object_uploaded?(aws_bucket, aws_backup_file, working_file)
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
133
134
135
|
# File 'lib/ll-innobackup.rb', line 133
def backup_bin
@backup_bin = options['backup_bin'] ||= '/usr/bin/innobackupex'
end
|
#backup_compress_threads ⇒ Object
141
142
143
|
# File 'lib/ll-innobackup.rb', line 141
def backup_compress_threads
@backup_compress_threads = options['backup_compress_threads'] ||= 4
end
|
#backup_parallel ⇒ Object
137
138
139
|
# File 'lib/ll-innobackup.rb', line 137
def backup_parallel
@backup_parallel = options['backup_parallel'] ||= 4
end
|
#backup_type ⇒ Object
127
128
129
130
131
|
# File 'lib/ll-innobackup.rb', line 127
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
115
116
117
|
# File 'lib/ll-innobackup.rb', line 115
def can_full_backup?
!fully_backed_up_today? && lock?('full')
end
|
#cleanup ⇒ Object
336
337
338
339
340
|
# File 'lib/ll-innobackup.rb', line 336
def cleanup
File.unlink working_file
rescue StandardError => e
STDERR.puts "Caught exception #{e} when trying to cleanup"
end
|
#completed? ⇒ Boolean
324
325
326
|
# File 'lib/ll-innobackup.rb', line 324
def completed?
completed_aws? && completed_inno?
end
|
#completed_aws? ⇒ Boolean
328
329
330
|
# File 'lib/ll-innobackup.rb', line 328
def completed_aws?
@completed_aws == true
end
|
#completed_inno? ⇒ Boolean
332
333
334
|
# File 'lib/ll-innobackup.rb', line 332
def completed_inno?
@completed_inno == true
end
|
#encryption_key ⇒ Object
157
158
159
|
# File 'lib/ll-innobackup.rb', line 157
def encryption_key
@encryption_key ||= options['encryption_key']
end
|
#encryption_threads ⇒ Object
145
146
147
|
# File 'lib/ll-innobackup.rb', line 145
def encryption_threads
@encryption_threads = options['encryption_threads'] ||= 4
end
|
#expected_full_size ⇒ Object
175
176
177
178
179
180
181
|
# File 'lib/ll-innobackup.rb', line 175
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
219
220
221
|
# File 'lib/ll-innobackup.rb', line 219
def expected_size
"--expected-size=#{expected_full_size}" if type == 'full'
end
|
#expires ⇒ Object
214
215
216
217
|
# File 'lib/ll-innobackup.rb', line 214
def expires
ed = expires_date
"--expires=#{ed}" if ed
end
|
#expires_date ⇒ Object
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/ll-innobackup.rb', line 202
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
119
120
121
|
# File 'lib/ll-innobackup.rb', line 119
def full_backup_running?
!lock?('full')
end
|
#fully_backed_up_today? ⇒ Boolean
99
100
101
102
103
104
105
106
107
108
109
|
# File 'lib/ll-innobackup.rb', line 99
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
307
308
309
310
311
|
# File 'lib/ll-innobackup.rb', line 307
def hostname
return options['hostname'] if options['hostname']
require 'socket'
Socket.gethostbyname(Socket.gethostname).first
end
|
#incremental ⇒ Object
281
282
283
284
|
# File 'lib/ll-innobackup.rb', line 281
def incremental
return unless backup_type == 'incremental'
"--incremental --incremental-lsn=#{lsn_from_state}"
end
|
#incremental_backup_running? ⇒ Boolean
123
124
125
|
# File 'lib/ll-innobackup.rb', line 123
def incremental_backup_running?
!lock?('incremental')
end
|
#innobackup_command ⇒ Object
196
197
198
199
200
|
# File 'lib/ll-innobackup.rb', line 196
def innobackup_command
"#{backup_bin} #{sql_authentication} "\
"#{incremental} #{innobackup_options} /tmp/sql "\
"2> #{innobackup_log} > #{working_file}"
end
|
#innobackup_log ⇒ Object
83
84
85
|
# File 'lib/ll-innobackup.rb', line 83
def innobackup_log
"/tmp/backup_#{type}_innobackup_log"
end
|
#innobackup_options ⇒ Object
187
188
189
190
191
192
193
194
|
# File 'lib/ll-innobackup.rb', line 187
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
111
112
113
|
# File 'lib/ll-innobackup.rb', line 111
def is_encrypted?
!options['encryption_key'].empty?
end
|
#lock?(t = type) ⇒ Boolean
87
88
89
90
|
# File 'lib/ll-innobackup.rb', line 87
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
299
300
301
302
303
304
305
|
# File 'lib/ll-innobackup.rb', line 299
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
286
287
288
289
290
|
# File 'lib/ll-innobackup.rb', line 286
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
292
293
294
295
296
297
|
# File 'lib/ll-innobackup.rb', line 292
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
270
271
272
273
274
275
276
277
278
279
|
# File 'lib/ll-innobackup.rb', line 270
def record
File.write(
InnoBackup.state_file(type),
{
date: now,
lsn: lsn_from_backup_log,
file: aws_backup_file
}.to_json
)
end
|
#report ⇒ Object
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
|
# File 'lib/ll-innobackup.rb', line 342
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
256
257
258
259
|
# File 'lib/ll-innobackup.rb', line 256
def revert_aws
exc = "#{aws_bin} s3 rm s3://#{aws_bucket}/#{aws_backup_file} > /dev/null 2>/dev/null"
`#{exc}`
end
|
#s3object_uploaded?(bucket_name, object_key, file_path) ⇒ Boolean
223
224
225
226
227
228
229
230
231
|
# File 'lib/ll-innobackup.rb', line 223
def s3object_uploaded?(bucket_name, object_key, file_path)
object = @s3.bucket(bucket_name).object(object_key)
object.upload_file(file_path, {expires: expires_date, thread_count: @options['thread_count']}) do |r|
return true
end
rescue StandardError => e
STDERR.puts "Error uploading object: #{e.message}"
return false
end
|
#sql_authentication ⇒ Object
183
184
185
|
# File 'lib/ll-innobackup.rb', line 183
def sql_authentication
"--user=#{sql_backup_user} --password=#{sql_backup_password}"
end
|
#sql_backup_password ⇒ Object
153
154
155
|
# File 'lib/ll-innobackup.rb', line 153
def sql_backup_password
@sql_backup_password ||= options['sql_backup_password']
end
|
#sql_backup_user ⇒ Object
149
150
151
|
# File 'lib/ll-innobackup.rb', line 149
def sql_backup_user
@sql_backup_user ||= options['sql_backup_user']
end
|
#state(t) ⇒ Object
92
93
94
95
96
97
|
# File 'lib/ll-innobackup.rb', line 92
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
261
262
263
264
265
266
267
268
|
# File 'lib/ll-innobackup.rb', line 261
def success?
InnoBackup.tail_file(
innobackup_log,
1
) =~ / completed OK/
rescue Errno::ENOENT
false
end
|
#valid_commands? ⇒ Boolean
233
234
235
|
# File 'lib/ll-innobackup.rb', line 233
def valid_commands?
File.exist?(backup_bin) && File.exist?(aws_bin)
end
|
#working_directory ⇒ Object
170
171
172
173
|
# File 'lib/ll-innobackup.rb', line 170
def working_directory
return options['working_directory'] if options['working_directory']
'/tmp'
end
|
#working_file ⇒ Object
313
314
315
|
# File 'lib/ll-innobackup.rb', line 313
def working_file
@working_file ||= File.join working_directory, "#{now.iso8601}-percona_backup"
end
|