class MongoOplogBackup::Restore

Attributes

config[R]

Public Class Methods

new(config) click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 17
def initialize(config)
  @config = config
end

Public Instance Methods

backup_folder() click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 9
def backup_folder
  File.join(config.backup_dir)
end
oplog_restore_folder() click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 13
def oplog_restore_folder
  File.join(backup_folder, 'tmp-restore')
end
perform(mode, options={}) click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 96
def perform(mode, options={})
  if options[:oplogLimit]
    raise ArgumentError, "oplogLimit is not a timestamp: eg. <seconds>[:ordinal]" unless options[:oplogLimit] =~ /\A\d+(?::\d+)?\z/
  end

  if mode == :oplog
    restore_oplogs(backup_folder, options)
  elsif mode == :full
    restore_dump(backup_folder, options)
    restore_oplogs(backup_folder, options)
  end
end
restore_dump(dir, options={}) click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 81
def restore_dump(dir, options={})
  restore_args = ['--stopOnError']
  restore_args << '--noIndexRestore' if !!config.options[:noIndexRestore]
  restore_args << '--gzip' if config.use_compression?
  restore_args << File.join(dir, 'dump')

  MongoOplogBackup.log.debug "Starting full restore..."
  status = config.mongorestore(restore_args).status
  if status != 0
    MongoOplogBackup.log.error("Mongorestore failed.")
    raise 'Full restore failed.'
  end
  MongoOplogBackup.log.debug "Full restore complete."
end
restore_oplogs(dir, options={}) click to toggle source

Given a directory of oplog dumps generated by the backup feature, iteratively mongorestore them. Mongorestore <3.4 expects a file named oplog.bson the directory specified Mongorestore 3.4 adds support for –oplogFile parameter, which simplifies things.

# File lib/mongo_oplog_backup/restore.rb, line 25
def restore_oplogs(dir, options={})
  default_restore_args = ['--stopOnError', '--oplogReplay']
  default_restore_args << '--noIndexRestore' if !!config.options[:noIndexRestore]
  if options[:oplogLimit]
    default_restore_args += ['--oplogLimit', options[:oplogLimit]]
    oplog_limit = BSON::Timestamp.from_string(options[:oplogLimit])
  end

  oplog_start_at = BSON::Timestamp.from_string(config.options[:oplogStartAt]) if config.options[:oplogStartAt]

  source_files = Oplog.find_oplogs(dir)

  validate_continuity!(source_files)

  MongoOplogBackup.log.debug "Starting oplog restore..."
  source_files.each do |filename|
    # TODO: mongorestore 3.4 supports --oplogFile

    unless oplog_start_at.nil?
      timestamps = Oplog.timestamps_from_filename(filename)
      if timestamps[:last] <= oplog_start_at
        MongoOplogBackup.log.debug "Skipping batch: #{filename}. Last op in batch (ts: #{timestamps[:last]}) is before oplogStartAt: #{oplog_start_at}"
        next
      end
    end
    unless oplog_limit.nil?
      timestamps = Oplog.timestamps_from_filename(filename)
      if timestamps[:first] > oplog_limit
        MongoOplogBackup.log.debug "Skipping batch: #{filename}. First op in batch (ts: #{timestamps[:first]}) is after oplogLimit: #{oplog_limit}"
        next
      end
    end

    temp_file_path = create_temp_oplog_dir(dir, filename)
    oplog_dir_path = File.dirname(temp_file_path)

    restore_args = default_restore_args.dup
    restore_args << '--gzip' if Oplog.gzip_fingerprint(filename)
    restore_args << oplog_dir_path
    status = config.mongorestore(restore_args).status
    if status != 0
      MongoOplogBackup.log.error("Mongorestore failed during oplog restore. Aborting. Exit code: #{status}")
      raise 'Oplog restore failed.'
    end

    begin
      File.unlink(temp_file_path)
      Dir.rmdir(oplog_dir_path)
    rescue SystemCallError => e
      MongoOplogBackup.log.error("Clean-up error for '#{temp_file_path}. #{e.message}")
      raise
    end
  end
  MongoOplogBackup.log.debug "Oplog restore complete."
end

Private Instance Methods

create_temp_oplog_dir(dir, filename) click to toggle source
# File lib/mongo_oplog_backup/restore.rb, line 126
def create_temp_oplog_dir dir, filename
  temp_dir = File.join(dir, 'tmp-restore', File.basename(filename))
  temp_file_path = File.join(temp_dir, 'oplog.bson')
  FileUtils.mkdir_p(temp_dir)
  File.link(filename, temp_file_path)
  return temp_file_path
rescue Errno::EEXIST => e
  # Probably, 'new_name' already exists when creating link.
  MongoOplogBackup.log.warn("Temporary oplog.bson link already exists: #{e.message}. Assuming valid.")
  return temp_file_path
rescue SystemCallError => e
  MongoOplogBackup.log.error("Setup error for oplog '#{filename}' in '#{temp_file_path}")
  raise
end
validate_continuity!(source_files) click to toggle source

Check for gaps in timestamps based only on the filename, trusting that the oplog set is complete (ie. the filename timestamps match the actual data)

# File lib/mongo_oplog_backup/restore.rb, line 112
def validate_continuity!(source_files)
  expected_first = nil
  MongoOplogBackup.log.debug "Checking timestamps..."
  source_files.each do |filename|
    timestamps = Oplog.timestamps_from_filename(filename)
    raise("Filename without timestamps found in restore set: #{filename}") if timestamps.nil?
    if expected_first && expected_first != timestamps[:first]
      raise "Missing oplog dump? Expected a filename with first timestamp '#{expected_first}', but got '#{timestamps[:first]}' from filename '#{filename}'."
    end
    expected_first = timestamps[:last]
  end
  true
end