class Backup::Model
Attributes
Array of configured Archive objects.
The configured Compressor, if any.
Array of configured Database objects.
The configured Encryptor, if any.
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.
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
The time when the backup finished (as a Time object)
The label (stored as a String) is used for a more friendly user output
Array of configured Notifier objects.
The final backup Package this model will create.
The configured Splitter, if any.
The time when the backup initiated (as a Time object)
Array of configured Storage objects.
Array of configured Syncer objects.
The time when the backup initiated (in format: 2011.02.20.03.29.59)
The trigger (stored as a String) is used as an identifier for initializing the backup process
Public Class Methods
The ::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 12 def all @all ||= [] end
Return an Array of Models matching the given trigger
.
# File lib/backup/model.rb, line 18 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
# File lib/backup/model.rb, line 114 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
Allows users to create preconfigured models.
# File lib/backup/model.rb, line 29 def preconfigure(&block) @preconfigure ||= block end
Private Class Methods
used for testing
# File lib/backup/model.rb, line 36 def reset! @all = @preconfigure = nil end
Public Instance Methods
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 239 def after(&block) @after = block if block @after end
Adds an Archive. Multiple Archives may be added to the model.
# File lib/backup/model.rb, line 137 def archive(name, &block) @archives << Archive.new(self, name, &block) end
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 211 def before(&block) @before = block if block @before end
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 178 def compress_with(name, &block) @compressor = get_class_from_scope(Compressor, name).new(&block) end
Adds an Database. Multiple Databases may be added to the model.
# File lib/backup/model.rb, line 143 def database(name, database_id = nil, &block) @databases << get_class_from_scope(Database, name). new(self, database_id, &block) end
The duration of the backup process (in format: HH:MM:SS)
# File lib/backup/model.rb, line 291 def duration return unless finished_at elapsed_time(started_at, finished_at) end
Adds an Notifier. Multiple Notifiers may be added to the model.
# File lib/backup/model.rb, line 163 def notify_by(name, &block) @notifiers << get_class_from_scope(Notifier, name).new(self, &block) end
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 260 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
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 189 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
Adds an Storage. Multiple Storages may be added to the model.
# File lib/backup/model.rb, line 150 def store_with(name, storage_id = nil, &block) @storages << get_class_from_scope(Storage, name). new(self, storage_id, &block) end
Adds an Syncer. Multiple Syncers may be added to the model.
# File lib/backup/model.rb, line 157 def sync_with(name, syncer_id = nil, &block) @syncers << get_class_from_scope(Syncer, name).new(syncer_id, &block) end
Private Instance Methods
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 417 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
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 400 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
Removes the final package file(s) once all configured Storages have run.
# File lib/backup/model.rb, line 354 def clean! Cleaner.remove_package(package) end
Returns a string representing the elapsed time in HH:MM:SS.
# File lib/backup/model.rb, line 468 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
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 375 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
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 439 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
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 322 def package! Packager.package!(self) Cleaner.remove_packaging(self) end
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 312 def prepare! Cleaner.prepare(self) end
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 301 def procedures return [] unless databases.any? || archives.any? [lambda { prepare! }, databases, archives, lambda { package! }, lambda { store! }, lambda { clean! }] end
Sets or updates the model's exit_status.
# File lib/backup/model.rb, line 386 def set_exit_status @exit_status = if exception exception.is_a?(StandardError) ? 2 : 3 else Logger.has_warnings? ? 1 : 0 end end
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 330 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