class Backup::Model

Attributes

archives[R]

Array of configured Archive objects.

compressor[R]

The configured Compressor, if any.

databases[R]

Array of configured Database objects.

encryptor[R]

The configured Encryptor, if any.

exception[R]

Exception raised by either a before hook or one of the model’s procedures that caused the model to fail. An exception raised by an after hook would not be stored here. Therefore, it is possible for this to be nil even if exit_status is 2 or 3.

exit_status[R]

Result of this model’s backup process.

0 = Job was successful 1 = Job was successful, but issued warnings 2 = Job failed, additional triggers may be performed 3 = Job failed, additional triggers will not be performed

finished_at[R]

The time when the backup finished (as a Time object)

label[R]

The label (stored as a String) is used for a more friendly user output

notifiers[R]

Array of configured Notifier objects.

package[R]

The final backup Package this model will create.

splitter[R]

The configured Splitter, if any.

started_at[R]

The time when the backup initiated (as a Time object)

storages[R]

Array of configured Storage objects.

syncers[R]

Array of configured Syncer objects.

time[R]

The time when the backup initiated (in format: 2011.02.20.03.29.59)

trigger[R]

The trigger (stored as a String) is used as an identifier for initializing the backup process

Public Class Methods

all() click to toggle source

The Backup::Model.all class method keeps track of all the models that have been instantiated. It returns the @all class variable, which contains an array of all the models

# File lib/backup/model.rb, line 13
def all
  @all ||= []
end
find_by_trigger(trigger) click to toggle source

Return an Array of Models matching the given trigger.

# File lib/backup/model.rb, line 19
def find_by_trigger(trigger)
  trigger = trigger.to_s
  if trigger.include?('*')
    regex = /^#{ trigger.gsub('*', '(.*)') }$/
    all.select {|model| regex =~ model.trigger }
  else
    all.select {|model| trigger == model.trigger }
  end
end
new(trigger, label, &block) click to toggle source
# File lib/backup/model.rb, line 115
def initialize(trigger, label, &block)
  @trigger = trigger.to_s
  @label   = label.to_s
  @package = Package.new(self)

  @databases  = []
  @archives   = []
  @storages   = []
  @notifiers  = []
  @syncers    = []

  instance_eval(&self.class.preconfigure) if self.class.preconfigure
  instance_eval(&block) if block_given?

  # trigger all defined databases to generate their #dump_filename
  # so warnings may be logged if `backup perform --check` is used
  databases.each {|db| db.send(:dump_filename) }

  Model.all << self
end
preconfigure(&block) click to toggle source

Allows users to create preconfigured models.

# File lib/backup/model.rb, line 30
def preconfigure(&block)
  @preconfigure ||= block
end

Private Class Methods

reset!() click to toggle source

used for testing

# File lib/backup/model.rb, line 37
def reset!
  @all = @preconfigure = nil
end

Public Instance Methods

after(&block) click to toggle source

Defines a block of code to run after the model’s procedures.

This code is ensured to run, even if the model failed, unless a before hook raised an exception and aborted the model.

The code block will be passed the model’s current exit_status:

‘0`: Success, no warnings. `1`: Success, but warnings were logged. `2`: Failure, but additional models/triggers will still be processed. `3`: Failure, no additional models/triggers will be processed.

The model’s exit_status may be elevated based on the after hook’s actions, but will never be decreased.

Warnings logged within the after hook may elevate the model’s exit_status to 1 and cause warning notifications to be sent.

Raising an exception may elevate the model’s exit_status and cause failure notifications to be sent. If the exception is a StandardError, the exit_status will be elevated to 2. If the exception is not a StandardError, the exit_status will be elevated to 3.

# File lib/backup/model.rb, line 240
def after(&block)
  @after = block if block
  @after
end
archive(name, &block) click to toggle source

Adds an Archive. Multiple Archives may be added to the model.

# File lib/backup/model.rb, line 138
def archive(name, &block)
  @archives << Archive.new(self, name, &block)
end
before(&block) click to toggle source

Defines a block of code to run before the model’s procedures.

Warnings logged within the before hook will elevate the model’s exit_status to 1 and cause warning notifications to be sent.

Raising an exception will abort the model and cause failure notifications to be sent. If the exception is a StandardError, exit_status will be 2. If the exception is not a StandardError, exit_status will be 3.

If any exception is raised, any defined after hook will be skipped.

# File lib/backup/model.rb, line 212
def before(&block)
  @before = block if block
  @before
end
compress_with(name, &block) click to toggle source

Adds an Compressor. Only one Compressor may be added to the model. This will be used to compress each individual Archive and Database stored within the final backup package.

# File lib/backup/model.rb, line 179
def compress_with(name, &block)
  @compressor = get_class_from_scope(Compressor, name).new(&block)
end
database(name, database_id = nil, &block) click to toggle source

Adds an Database. Multiple Databases may be added to the model.

# File lib/backup/model.rb, line 144
def database(name, database_id = nil, &block)
  @databases << get_class_from_scope(Database, name).
      new(self, database_id, &block)
end
duration() click to toggle source

The duration of the backup process (in format: HH:MM:SS)

# File lib/backup/model.rb, line 292
def duration
  return unless finished_at
  elapsed_time(started_at, finished_at)
end
encrypt_with(name, &block) click to toggle source

Adds an Encryptor. Only one Encryptor may be added to the model. This will be used to encrypt the final backup package.

# File lib/backup/model.rb, line 171
def encrypt_with(name, &block)
  @encryptor = get_class_from_scope(Encryptor, name).new(&block)
end
notify_by(name, &block) click to toggle source

Adds an Notifier. Multiple Notifiers may be added to the model.

# File lib/backup/model.rb, line 164
def notify_by(name, &block)
  @notifiers << get_class_from_scope(Notifier, name).new(self, &block)
end
perform!() click to toggle source

Performs the backup process

Once complete, exit_status will indicate the result of this process.

If any errors occur during the backup process, all temporary files will be left in place. If the error occurs before Packaging, then the temporary folder (tmp_path/trigger) will remain and may contain all or some of the configured Archives and/or Database dumps. If the error occurs after Packaging, but before the Storages complete, then the final packaged files (located in the root of tmp_path) will remain.

*** Important *** If an error occurs and any of the above mentioned temporary files remain, those files *** will be removed *** before the next scheduled backup for the same trigger.

# File lib/backup/model.rb, line 261
def perform!
  @started_at = Time.now.utc
  @time = package.time = started_at.strftime("%Y.%m.%d.%H.%M.%S")

  log!(:started)
  before_hook

  procedures.each do |procedure|
    procedure.is_a?(Proc) ? procedure.call : procedure.each(&:perform!)
  end

  syncers.each(&:perform!)

rescue Interrupt
  @interrupted = true
  raise

rescue Exception => err
  @exception = err

ensure
  unless @interrupted
    set_exit_status
    @finished_at = Time.now.utc
    log!(:finished)
    after_hook
  end
end
split_into_chunks_of(chunk_size, suffix_length = 3) click to toggle source

Adds a Splitter to split the final backup package into multiple files.

chunk_size is specified in MiB and must be given as an Integer. suffix_length controls the number of characters used in the suffix (and the maximum number of chunks possible). ie. 1 (-a, -b), 2 (-aa, -ab), 3 (-aaa, -aab)

# File lib/backup/model.rb, line 190
    def split_into_chunks_of(chunk_size, suffix_length = 3)
      if chunk_size.is_a?(Integer) && suffix_length.is_a?(Integer)
        @splitter = Splitter.new(self, chunk_size, suffix_length)
      else
        raise Error, <<-EOS
          Invalid arguments for #split_into_chunks_of()
          +chunk_size+ (and optional +suffix_length+) must be Integers.
        EOS
      end
    end
store_with(name, storage_id = nil, &block) click to toggle source

Adds an Storage. Multiple Storages may be added to the model.

# File lib/backup/model.rb, line 151
def store_with(name, storage_id = nil, &block)
  @storages << get_class_from_scope(Storage, name).
      new(self, storage_id, &block)
end
sync_with(name, syncer_id = nil, &block) click to toggle source

Adds an Syncer. Multiple Syncers may be added to the model.

# File lib/backup/model.rb, line 158
def sync_with(name, syncer_id = nil, &block)
  @syncers << get_class_from_scope(Syncer, name).new(syncer_id, &block)
end

Private Instance Methods

after_hook() click to toggle source

Runs the after hook. Any exception raised here will be logged only and the model’s exit_status will be elevated if neccessary.

# File lib/backup/model.rb, line 418
def after_hook
  return unless after && !@before_hook_failed

  Logger.info 'After Hook Starting...'
  after.call(exit_status)
  Logger.info 'After Hook Finished.'

  set_exit_status # in case hook logged warnings

rescue Exception => err
  fatal = !err.is_a?(StandardError)
  ex = fatal ? FatalError : Error
  Logger.error ex.wrap(err, 'After Hook Failed!')
  # upgrade exit_status if needed
  (@exit_status = fatal ? 3 : 2) unless exit_status == 3
end
before_hook() click to toggle source

Runs the before hook. Any exception raised will be wrapped and re-raised, where it will be handled by perform the same as an exception raised while performing the model’s procedures. Only difference is that an exception raised here will prevent any after hook from being run.

# File lib/backup/model.rb, line 401
def before_hook
  return unless before

  Logger.info 'Before Hook Starting...'
  before.call
  Logger.info 'Before Hook Finished.'

rescue Exception => err
  @before_hook_failed = true
  ex = err.is_a?(StandardError) ? Error : FatalError
  raise ex.wrap(err, 'Before Hook Failed!')
end
clean!() click to toggle source

Removes the final package file(s) once all configured Storages have run.

# File lib/backup/model.rb, line 355
def clean!
  Cleaner.remove_package(package)
end
elapsed_time(start_time, finish_time) click to toggle source

Returns a string representing the elapsed time in HH:MM:SS.

# File lib/backup/model.rb, line 469
def elapsed_time(start_time, finish_time)
  duration  = finish_time.to_i - start_time.to_i
  hours     = duration / 3600
  remainder = duration - (hours * 3600)
  minutes   = remainder / 60
  seconds   = remainder - (minutes * 60)
  '%02d:%02d:%02d' % [hours, minutes, seconds]
end
get_class_from_scope(scope, name) click to toggle source

Returns the class/model specified by name inside of scope. scope should be a Class/Module. name may be Class/Module or String representation of any namespace which exists under scope.

The ‘Backup::Config::DSL’ namespace is stripped from name, since this is the namespace where we define module namespaces for use with Model’s DSL methods.

Examples:

get_class_from_scope(Backup::Database, 'MySQL')
  returns the class Backup::Database::MySQL

get_class_from_scope(Backup::Syncer, Backup::Config::RSync::Local)
  returns the class Backup::Syncer::RSync::Local
# File lib/backup/model.rb, line 376
def get_class_from_scope(scope, name)
  klass = scope
  name = name.to_s.sub(/^Backup::Config::DSL::/, '')
  name.split('::').each do |chunk|
    klass = klass.const_get(chunk)
  end
  klass
end
log!(action) click to toggle source

Logs messages when the model starts and finishes.

exception will be set here if exit_status is > 1, since log(:finished) is called before the after hook.

# File lib/backup/model.rb, line 440
def log!(action)
  case action
  when :started
    Logger.info "Performing Backup for '#{ label } (#{ trigger })'!\n" +
        "[ backup #{ VERSION } : #{ RUBY_DESCRIPTION } ]"

  when :finished
    if exit_status > 1
      ex = exit_status == 2 ? Error : FatalError
      err = ex.wrap(exception, "Backup for #{ label } (#{ trigger }) Failed!")
      Logger.error err
      Logger.error "\nBacktrace:\n\s\s" + err.backtrace.join("\n\s\s") + "\n\n"

      Cleaner.warnings(self)
    else
      msg = "Backup for '#{ label } (#{ trigger })' "
      if exit_status == 1
        msg << "Completed Successfully (with Warnings) in #{ duration }"
        Logger.warn msg
      else
        msg << "Completed Successfully in #{ duration }"
        Logger.info msg
      end
    end
  end
end
package!() click to toggle source

After all the databases and archives have been dumped and stored, these files will be bundled in to a .tar archive (uncompressed), which may be optionally Encrypted and/or Split into multiple “chunks”. All information about this final archive is stored in the @package. Once complete, the temporary folder used during packaging is removed.

# File lib/backup/model.rb, line 323
def package!
  Packager.package!(self)
  Cleaner.remove_packaging(self)
end
prepare!() click to toggle source

Clean any temporary files and/or package files left over from the last time this model/trigger was performed. Logs warnings if files exist and are cleaned.

# File lib/backup/model.rb, line 313
def prepare!
  Cleaner.prepare(self)
end
procedures() click to toggle source

Returns an array of procedures that will be performed if any Archives or Databases are configured for the model.

# File lib/backup/model.rb, line 302
def procedures
  return [] unless databases.any? || archives.any?

  [lambda { prepare! }, databases, archives,
   lambda { package! }, lambda { store! }, lambda { clean! }]
end
set_exit_status() click to toggle source

Sets or updates the model’s exit_status.

# File lib/backup/model.rb, line 387
def set_exit_status
  @exit_status = if exception
    exception.is_a?(StandardError) ? 2 : 3
  else
    Logger.has_warnings? ? 1 : 0
  end
end
store!() click to toggle source

Attempts to use all configured Storages, even if some of them result in exceptions. Returns true or raises first encountered exception.

# File lib/backup/model.rb, line 331
def store!
  storage_results = storages.map do |storage|
    begin
      storage.perform!
    rescue => ex
      ex
    end
  end

  first_exception, *other_exceptions = storage_results.select { |result| result.is_a? Exception }

  if first_exception
    other_exceptions.each do |exception|
     Logger.error exception.to_s
     Logger.error exception.backtrace.join('\n')
    end
    raise first_exception
  else
    true
  end
end