lib/backup/database/mysql.rb
module Backup
module Database
class MySQL < Base
class Error < Backup::Error; end
##
# Name of the database that needs to get dumped
# To dump all databases, set this to `:all` or leave blank.
attr_accessor :name
##
# Credentials for the specified database
attr_accessor :username, :password
##
# Connectivity options
attr_accessor :host, :port, :socket
##
# Tables to skip while dumping the database
#
# If `name` is set to :all (or not specified), these must include
# a database name. e.g. 'name.table'.
# If `name` is given, these may simply be table names.
attr_accessor :skip_tables
##
# 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" 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 true (which is the default behaviour), the backup will be prepared
# after it has been successfuly created. This option is only valid if
# :backup_engine is set to :innobackupex.
attr_accessor :prepare_backup
##
# 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
@prepare_backup = true if @prepare_backup.nil?
end
##
# Performs the mysqldump or innobackupex command and outputs
# the dump file in the +dump_path+ using +dump_filename+.
#
# <trigger>/databases/MySQL[-<database_id>].[sql|tar][.gz]
def perform!
super
pipeline = Pipeline.new
dump_ext = sql_backup? ? "sql" : "tar"
pipeline << sudo_option(sql_backup? ? mysqldump : innobackupex)
if model.compressor
model.compressor.compress_with do |command, ext|
pipeline << command
dump_ext << ext
end
end
pipeline << "#{utility(:cat)} > " \
"'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
pipeline.run
if pipeline.success?
log!(:finished)
else
raise Error, "Dump Failed!\n#{pipeline.error_messages}"
end
end
private
def mysqldump
"#{utility(:mysqldump)} #{user_options} #{credential_options} " \
"#{connectivity_options} #{name_option} " \
"#{tables_to_dump} #{tables_to_skip}"
end
def credential_options
opts = []
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
opts = []
opts << "--host='#{host}'" if host
opts << "--port='#{port}'" if port
opts.join(" ")
end
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
Array(only_tables).join(" ") unless dump_all?
end
def tables_to_skip
Array(skip_tables).map do |table|
table = dump_all? || table["."] ? table : "#{name}.#{table}"
"--ignore-table='#{table}'"
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} && " +
innobackupex_prepare +
# Move files to tar-ed stream on stdout
"#{utility(:tar)} --remove-files -cf - " \
"-C #{File.dirname(temp_dir)} #{File.basename(temp_dir)}"
end
def innobackupex_prepare
return "" unless @prepare_backup
# Log applying phase (prepare for restore)
"#{utility(:innobackupex)} --apply-log #{temp_dir} " \
"#{user_prepare_options} #{quiet_option} && "
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