lib/backup/database/mysql.rb in backup-4.0.2 vs lib/backup/database/mysql.rb in backup-4.0.3

- old
+ new

@@ -1,6 +1,7 @@ # encoding: utf-8 +require 'shellwords' module Backup module Database class MySQL < Base class Error < Backup::Error; end @@ -30,32 +31,52 @@ # Tables to dump. This in only valid if `name` is specified. # If none are given, the entire database will be dumped. attr_accessor :only_tables ## - # Additional "mysqldump" options + # Additional "mysqldump" or "innobackupex (backup creation)" options attr_accessor :additional_options + ## + # Additional innobackupex log preparation phase ("apply-logs") options + attr_accessor :prepare_options + + ## + # Default is :mysqldump (which is built in MySQL and generates + # a textual SQL file), but can be changed to :innobackupex, which + # has more feasible restore times for large databases. + # See: http://www.percona.com/doc/percona-xtrabackup/ + attr_accessor :backup_engine + + ## + # If set the backup engine command block is executed as the given user + attr_accessor :sudo_user + + ## + # If set, do not suppress innobackupdb output (useful for debugging) + attr_accessor :verbose + def initialize(model, database_id = nil, &block) super instance_eval(&block) if block_given? @name ||= :all + @backup_engine ||= :mysqldump end ## - # Performs the mysqldump command and outputs the dump file - # in the +dump_path+ using +dump_filename+. + # Performs the mysqldump or innobackupex command and outputs + # the dump file in the +dump_path+ using +dump_filename+. # - # <trigger>/databases/MySQL[-<database_id>].sql[.gz] + # <trigger>/databases/MySQL[-<database_id>].[sql|tar][.gz] def perform! super pipeline = Pipeline.new - dump_ext = 'sql' + dump_ext = sql_backup? ? 'sql' : 'tar' - pipeline << mysqldump + pipeline << sudo_option(sql_backup? ? mysqldump : innobackupex) model.compressor.compress_with do |command, ext| pipeline << command dump_ext << ext end if model.compressor @@ -79,12 +100,12 @@ "#{ tables_to_dump } #{ tables_to_skip }" end def credential_options opts = [] - opts << "--user='#{ username }'" if username - opts << "--password='#{ password }'" if password + opts << "--user=#{ Shellwords.escape(username) }" if username + opts << "--password=#{ Shellwords.escape(password) }" if password opts.join(' ') end def connectivity_options return "--socket='#{ socket }'" if socket @@ -97,10 +118,14 @@ def user_options Array(additional_options).join(' ') end + def user_prepare_options + Array(prepare_options).join(' ') + end + def name_option dump_all? ? '--all-databases' : name end def tables_to_dump @@ -114,9 +139,42 @@ end.join(' ') end def dump_all? name == :all + end + + def sql_backup? + backup_engine.to_sym == :mysqldump + end + + def innobackupex + # Creation phase + "#{ utility(:innobackupex) } #{ credential_options } " + + "#{ connectivity_options } #{ user_options } " + + "--no-timestamp #{ temp_dir } #{ quiet_option } && " + + # Log applying phase (prepare for restore) + "#{ utility(:innobackupex) } --apply-log #{ temp_dir } " + + "#{ user_prepare_options } #{ quiet_option } && " + + # Move files to tar-ed stream on stdout + "#{ utility(:tar) } --remove-files -cf - " + + "-C #{ File.dirname(temp_dir) } #{ File.basename(temp_dir) }" + end + + def sudo_option(command_block) + return command_block unless sudo_user + + "sudo -s -u #{ sudo_user } -- <<END_OF_SUDO\n" + + "#{command_block}\n" + + "END_OF_SUDO\n" + end + + def quiet_option + verbose ? "" : " 2> /dev/null " + end + + def temp_dir + File.join(dump_path, dump_filename + ".bkpdir") end end end end