Class: LL::InnoBackup
- Inherits:
-
Object
- Object
- LL::InnoBackup
- Defined in:
- lib/ll-innobackup.rb
Defined Under Namespace
Classes: NoStateError
Instance Attribute Summary collapse
-
#date ⇒ Object
readonly
Returns the value of attribute date.
-
#lock_files ⇒ Object
readonly
Returns the value of attribute lock_files.
-
#now ⇒ Object
readonly
Returns the value of attribute now.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#state_files ⇒ Object
readonly
Returns the value of attribute state_files.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
Class Method Summary collapse
- .innobackup_log(t) ⇒ Object
- .lock_file(type) ⇒ Object
-
.options ⇒ Object
Use this in case the log file is massive.
- .state_file(t) ⇒ Object
- .tail_file(path, n) ⇒ Object
Instance Method Summary collapse
- #aws_backup_file ⇒ Object
- #aws_bin ⇒ Object
- #aws_bucket ⇒ Object
- #aws_command ⇒ Object
- #aws_log ⇒ Object
- #backup ⇒ Object
- #backup_bin ⇒ Object
- #backup_compress_threads ⇒ Object
- #backup_parallel ⇒ Object
- #backup_type ⇒ Object
- #can_full_backup? ⇒ Boolean
- #cleanup ⇒ Object
- #completed? ⇒ Boolean
- #completed_aws? ⇒ Boolean
- #completed_inno? ⇒ Boolean
- #encryption_key ⇒ Object
- #expected_full_size ⇒ Object
- #expected_size ⇒ Object
- #expires ⇒ Object
- #expires_date ⇒ Object
- #full_backup_running? ⇒ Boolean
- #fully_backed_up_today? ⇒ Boolean
- #hostname ⇒ Object
- #incremental ⇒ Object
- #incremental_backup_running? ⇒ Boolean
-
#initialize(options = {}) ⇒ InnoBackup
constructor
A new instance of InnoBackup.
- #innobackup_command ⇒ Object
- #innobackup_log ⇒ Object
- #innobackup_options ⇒ Object
- #is_encrypted? ⇒ Boolean
- #lock?(t = type) ⇒ Boolean
- #lsn_from_backup_log ⇒ Object
- #lsn_from_full_backup_state? ⇒ Boolean
- #lsn_from_state ⇒ Object
- #record ⇒ Object
- #report ⇒ Object
- #revert_aws ⇒ Object
- #sql_authentication ⇒ Object
- #sql_backup_password ⇒ Object
- #sql_backup_user ⇒ Object
- #state(t) ⇒ Object
- #success? ⇒ Boolean
- #valid_commands? ⇒ Boolean
- #working_directory ⇒ Object
- #working_file ⇒ Object
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( = {}) @now = Time.now @date = @now.to_date = @lock_files = {} @state_files = {} @type = backup_type end |
Instance Attribute Details
#date ⇒ Object (readonly)
Returns the value of attribute date.
60 61 62 |
# File 'lib/ll-innobackup.rb', line 60 def date @date end |
#lock_files ⇒ Object (readonly)
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 (readonly)
Returns the value of attribute now.
60 61 62 |
# File 'lib/ll-innobackup.rb', line 60 def now @now end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
60 61 62 |
# File 'lib/ll-innobackup.rb', line 60 def end |
#state_files ⇒ Object (readonly)
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 (readonly)
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 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 # we start at the end 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
307 308 309 310 311 312 |
# File 'lib/ll-innobackup.rb', line 307 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
154 155 156 |
# File 'lib/ll-innobackup.rb', line 154 def aws_bin @aws_bin = ['aws_bin'] ||= '/usr/local/bin/aws' end |
#aws_bucket ⇒ Object
158 159 160 161 |
# File 'lib/ll-innobackup.rb', line 158 def aws_bucket raise NoStateError, 'aws_bucket not provided' unless ['aws_bucket'] @aws_bucket = ['aws_bucket'] end |
#aws_command ⇒ Object
216 217 218 219 220 |
# File 'lib/ll-innobackup.rb', line 216 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
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/ll-innobackup.rb', line 226 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. ensure report cleanup end |
#backup_bin ⇒ Object
130 131 132 |
# File 'lib/ll-innobackup.rb', line 130 def backup_bin @backup_bin = ['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 = ['backup_compress_threads'] ||= 4 end |
#backup_parallel ⇒ Object
134 135 136 |
# File 'lib/ll-innobackup.rb', line 134 def backup_parallel @backup_parallel = ['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
326 327 328 329 330 |
# File 'lib/ll-innobackup.rb', line 326 def cleanup File.unlink working_file rescue StandardError => e STDERR.puts "Caught exception #{e} when trying to cleanup" end |
#completed? ⇒ Boolean
314 315 316 |
# File 'lib/ll-innobackup.rb', line 314 def completed? completed_aws? && completed_inno? end |
#completed_aws? ⇒ Boolean
318 319 320 |
# File 'lib/ll-innobackup.rb', line 318 def completed_aws? @completed_aws == true end |
#completed_inno? ⇒ Boolean
322 323 324 |
# File 'lib/ll-innobackup.rb', line 322 def completed_inno? @completed_inno == true end |
#encryption_key ⇒ Object
150 151 152 |
# File 'lib/ll-innobackup.rb', line 150 def encryption_key @encryption_key ||= ['encryption_key'] end |
#expected_full_size ⇒ Object
168 169 170 171 172 173 174 |
# File 'lib/ll-innobackup.rb', line 168 def expected_full_size @expected_full_size ||= -> do return File.size(working_file) if File.exist?(working_file) return ['expected_full_size'] if ['expected_full_size'] 1_600_000_000 end.call end |
#expected_size ⇒ Object
212 213 214 |
# File 'lib/ll-innobackup.rb', line 212 def expected_size "--expected-size=#{expected_full_size}" if type == 'full' end |
#expires ⇒ Object
207 208 209 210 |
# File 'lib/ll-innobackup.rb', line 207 def expires ed = expires_date "--expires=#{ed}" if ed end |
#expires_date ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/ll-innobackup.rb', line 195 def expires_date require 'active_support/all' # Keep incrementals for 2 days return (@now + 2.days).iso8601 if type == 'incremental' # Keep first backup of month for 180 days return (@now + 6.months).iso8601 if @date.yesterday.month != @date.month # Keep first backup of week for 31 days (monday) return (@now + 1.month).iso8601 if @date.cwday == 1 # Keep daily backups for 14 days (@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
297 298 299 300 301 |
# File 'lib/ll-innobackup.rb', line 297 def hostname return ['hostname'] if ['hostname'] require 'socket' Socket.gethostbyname(Socket.gethostname).first end |
#incremental ⇒ Object
271 272 273 274 |
# File 'lib/ll-innobackup.rb', line 271 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
189 190 191 192 193 |
# File 'lib/ll-innobackup.rb', line 189 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
180 181 182 183 184 185 186 187 |
# File 'lib/ll-innobackup.rb', line 180 def [ "--parallel=#{backup_parallel}", "--compress-threads=#{backup_compress_threads}", ("--encrypt=AES256 --encrypt-key=#{encryption_key}" if is_encrypted?), '--stream=xbstream --compress' ].join(" ") end |
#is_encrypted? ⇒ Boolean
108 109 110 |
# File 'lib/ll-innobackup.rb', line 108 def is_encrypted? !['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
289 290 291 292 293 294 295 |
# File 'lib/ll-innobackup.rb', line 289 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
276 277 278 279 280 |
# File 'lib/ll-innobackup.rb', line 276 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
282 283 284 285 286 287 |
# File 'lib/ll-innobackup.rb', line 282 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
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/ll-innobackup.rb', line 260 def record File.write( InnoBackup.state_file(type), { date: now, lsn: lsn_from_backup_log, file: aws_backup_file }.to_json ) end |
#report ⇒ Object
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/ll-innobackup.rb', line 332 def report # Eventually Tell Zabbix 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
246 247 248 249 |
# File 'lib/ll-innobackup.rb', line 246 def revert_aws exc = "#{aws_bin} s3 rm s3://#{aws_bucket}/#{aws_backup_file} > /dev/null 2>/dev/null" `#{exc}` end |
#sql_authentication ⇒ Object
176 177 178 |
# File 'lib/ll-innobackup.rb', line 176 def sql_authentication "--user=#{sql_backup_user} --password=#{sql_backup_password}" end |
#sql_backup_password ⇒ Object
146 147 148 |
# File 'lib/ll-innobackup.rb', line 146 def sql_backup_password @sql_backup_password ||= ['sql_backup_password'] end |
#sql_backup_user ⇒ Object
142 143 144 |
# File 'lib/ll-innobackup.rb', line 142 def sql_backup_user @sql_backup_user ||= ['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
251 252 253 254 255 256 257 258 |
# File 'lib/ll-innobackup.rb', line 251 def success? InnoBackup.tail_file( innobackup_log, 1 ) =~ / completed OK/ rescue Errno::ENOENT false end |
#valid_commands? ⇒ Boolean
222 223 224 |
# File 'lib/ll-innobackup.rb', line 222 def valid_commands? File.exist?(backup_bin) && File.exist?(aws_bin) end |
#working_directory ⇒ Object
163 164 165 166 |
# File 'lib/ll-innobackup.rb', line 163 def working_directory return ['working_directory'] if ['working_directory'] '/tmp' end |
#working_file ⇒ Object
303 304 305 |
# File 'lib/ll-innobackup.rb', line 303 def working_file @working_file ||= File.join working_directory, "#{now.iso8601}-percona_backup" end |