diff --git a/app/models/bacula_file.rb b/app/models/bacula_file.rb index abbab57..14a7cd5 100644 --- a/app/models/bacula_file.rb +++ b/app/models/bacula_file.rb @@ -1,19 +1,20 @@ +# ActiveRecord class for Bacula's File resource class BaculaFile < ActiveRecord::Base self.table_name = :File self.primary_key = :FileId alias_attribute :file_index, :FileIndex alias_attribute :job_id, :JobId alias_attribute :path_id, :PathId alias_attribute :filename_id, :FilenameId alias_attribute :delta_seq, :DeltaSeq alias_attribute :mark_id, :MarkId alias_attribute :l_stat, :LStat alias_attribute :md5, :MD5 belongs_to :path, foreign_key: :PathId belongs_to :filename, foreign_key: :FilenameId belongs_to :job, foreign_key: :JobId has_many :base_files, foreign_key: :FileId end diff --git a/app/models/client.rb b/app/models/client.rb index efd0405..e8a9a36 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -1,99 +1,102 @@ +# Bacula Client class. +# All hosts that are getting backed up with Bacula have a Client entry, with +# attributes concerning the Client. class Client < ActiveRecord::Base self.table_name = :Client self.primary_key = :ClientId alias_attribute :name, :Name alias_attribute :uname, :Uname alias_attribute :auto_prune, :AutoPrune alias_attribute :file_retention, :FileRetention alias_attribute :job_retention, :JobRetention has_many :jobs, foreign_key: :ClientId has_one :host, foreign_key: :name, primary_key: :Name scope :for_user, ->(user_id) { joins(host: :users).where(users: { id: user_id }) } DAY_SECS = 60 * 60 * 24 # Fetches the client's job_templates that are already persisted to # Bacula's configuration # # @return [ActiveRecord::Relation] of `JobTemplate` def persisted_jobs host.job_templates.where(baculized: true).includes(:fileset, :schedule) end # Fetches the client's performed jobs in reverse chronological order # # @return [ActiveRecord::Relation] of `Job` def recent_jobs jobs.order(EndTime: :desc).includes(:file_set) end # Helper method. It shows the client's job retention, # (which is expressed in seconds) in days. # # @return [Integer] def job_retention_days job_retention / DAY_SECS end # Helper method. It shows the client's file retention, # (which is expressed in seconds) in days. # # @return [Integer] def file_retention_days file_retention / DAY_SECS end # Helper method for auto_prune # # @return [String] 'yes' or 'no' def auto_prune_human auto_prune == 1 ? 'yes' : 'no' end # Helper method for displayin the last job's datetime in a nice format. def last_job_date_formatted if job_time = jobs.backup_type.last.try(:end_time) I18n.l(job_time, format: :long) end end # Shows if a client has any backup jobs to Bacule config # # @return [Boolean] def is_backed_up? jobs.backup_type.any? end # Shows the total file size of the jobs that run for a specific client # # @return [Integer] Size in Bytes def backup_jobs_size jobs.backup_type.map(&:job_bytes).sum end # Shows the total files' count for the jobs that run for a specific client # # @return [Integer] File count def files_count jobs.map(&:job_files).sum end # Fetches the client's jobs that are running at the moment # # @return [Integer] def running_jobs jobs.running.count end # Displays the bacula config that is generated from the client's # host # # @return [String] def bacula_config return unless host host.baculize_config.join("\n") end end diff --git a/app/models/counter.rb b/app/models/counter.rb index 99d15a7..01a08aa 100644 --- a/app/models/counter.rb +++ b/app/models/counter.rb @@ -1,10 +1,13 @@ +# Bacula Counter table +# +# The Counter table contains one entry for each permanent counter defined by the user. class Counter < ActiveRecord::Base self.table_name = :Counters self.primary_key = :Counter alias_attribute :counter, :Counter alias_attribute :min_value, :MinValue alias_attribute :max_value, :MaxValue alias_attribute :current_value, :CurrentValue alias_attribute :wrap_coounter, :WrapCounter end diff --git a/app/models/file_set.rb b/app/models/file_set.rb index 9896de0..a5b4adf 100644 --- a/app/models/file_set.rb +++ b/app/models/file_set.rb @@ -1,11 +1,19 @@ +# Bacula FileSet table. +# +# The FileSet table contains one entry for each FileSet that is used. +# The MD5 signature is kept to ensure that if the user changes anything inside the FileSet, +# it will be detected and the new FileSet will be used. +# This is particularly important when doing an incremental update. +# If the user deletes a file or adds a file, we need to ensure that a Full backup is done +# prior to the next incremental. class FileSet < ActiveRecord::Base self.table_name = :FileSet self.primary_key = :FileSetId alias_attribute :file_set_id, :FileSetId alias_attribute :file_set, :FileSet alias_attribute :md5, :MD5 alias_attribute :create_time, :CreateTime has_many :jobs, foreign_key: :FileSetId end diff --git a/app/models/filename.rb b/app/models/filename.rb index a612570..63f2590 100644 --- a/app/models/filename.rb +++ b/app/models/filename.rb @@ -1,9 +1,14 @@ +# Bacula Filename +# +# The Filename table contains the name of each file backed up with the path removed. +# If different directories or machines contain the same filename, +# only one copy will be saved in this table. class Filename < ActiveRecord::Base self.table_name = :Filename self.primary_key = :FilenameId alias_attribute :filename_id, :FilenameId alias_attribute :name, :Name has_many :bacula_files, foreign_key: :FilenameId end diff --git a/app/models/job.rb b/app/models/job.rb index 21f404a..10cbaa4 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -1,99 +1,126 @@ +# Bacula Job table. +# +# The Job table contains one record for each Job run by Bacula. +# Thus normally, there will be one per day per machine added to the database. +# Note, the JobId is used to index Job records in the database, and it often is shown to the user +# in the Console program. +# However, care must be taken with its use as it is not unique from database to database. +# For example, the user may have a database for Client data saved on machine Rufus and another +# database for Client data saved on machine Roxie. +# In this case, the two database will each have JobIds that match those in another database. +# For a unique reference to a Job, see Job below. +# +# The Name field of the Job record corresponds to the Name resource record given in the +# Director's configuration file. +# Thus it is a generic name, and it will be normal to find many Jobs (or even all Jobs) +# with the same Name. +# +# The Job field contains a combination of the Name and the schedule time of the Job by the Director. +# Thus for a given Director, even with multiple Catalog databases, the Job will contain a unique +# name that represents the Job. +# +# For a given Storage daemon, the VolSessionId and VolSessionTime form a unique identification +# of the Job. +# +# This will be the case even if multiple Directors are using the same Storage daemon. +# +# The Job Type (or simply Type) can have one of the following values: class Job < ActiveRecord::Base self.table_name = :Job self.primary_key = :JobId alias_attribute :job_id, :JobId alias_attribute :job, :Job alias_attribute :name, :Name alias_attribute :type, :Type alias_attribute :level, :Level alias_attribute :client_id, :ClientId alias_attribute :job_status, :JobStatus alias_attribute :sched_time, :SchedTime alias_attribute :start_time, :StartTime alias_attribute :end_time, :EndTime alias_attribute :real_end_time, :RealEndTime alias_attribute :job_t_date, :JobTDate alias_attribute :vol_session_id, :VolSessionId alias_attribute :vol_session_time, :VolSessionTime alias_attribute :job_files, :JobFiles alias_attribute :job_bytes, :JobBytes alias_attribute :read_bytes, :ReadBytes alias_attribute :job_errors, :JobErrors alias_attribute :job_missing_files, :JobMissingFiles alias_attribute :pool_id, :PoolId alias_attribute :file_set_id, :FileSetId alias_attribute :prior_job_id, :PriorJobId alias_attribute :purged_files, :PurgedFiles alias_attribute :has_base, :HasBase alias_attribute :has_cache, :HasCache alias_attribute :reviewed, :Reviewed alias_attribute :comment, :Comment belongs_to :pool, foreign_key: :PoolId belongs_to :file_set, foreign_key: :FileSetId belongs_to :client, foreign_key: :ClientId has_many :bacula_files, foreign_key: :JobId has_many :base_files, foreign_key: :BaseJobId has_many :job_media, foreign_key: :JobId has_many :logs, foreign_key: :JobId scope :running, -> { where(job_status: 'R') } scope :backup_type, -> { where(type: 'B') } scope :restore_type, -> { where(type: 'R') } HUMAN_STATUS = { 'A' => 'Canceled by user', 'B' => 'Blocked', 'C' => 'Created, not yet running', 'D' => 'Verify found differences', 'E' => 'Terminated with errors', 'F' => 'Waiting for Client', 'M' => 'Waiting for media mount', 'R' => 'Running', 'S' => 'Waiting for Storage daemon', 'T' => 'Completed successfully', 'a' => 'SD despooling attributes', 'c' => 'Waiting for client resource', 'd' => 'Waiting on maximum jobs', 'e' => 'Non-fatal error', 'f' => 'Fatal error', 'i' => 'Doing batch insert file records', 'j' => 'Waiting for job resource', 'm' => 'Waiting for new media', 'p' => 'Waiting on higher priority jobs', 's' => 'Waiting for storage resource', 't' => 'Waiting on start time' } paginates_per 20 def level_human { 'F' => 'Full', 'D' => 'Differential', 'I' => 'Incremental' }[level] end def status_human HUMAN_STATUS[job_status] end def fileset file_set.try(:file_set) || '-' end def start_time_formatted if start_time I18n.l(start_time, format: :long) end end def end_time_formatted if end_time I18n.l(end_time, format: :long) end end end diff --git a/app/models/job_histo.rb b/app/models/job_histo.rb index a74a775..af72743 100644 --- a/app/models/job_histo.rb +++ b/app/models/job_histo.rb @@ -1,35 +1,39 @@ +# Bacula JobHisto table. +# +# The bf JobHisto table is the same as the Job table, +# but it keeps long term statistics (i.e. it is not pruned with the Job). class JobHisto < ActiveRecord::Base self.table_name = :JobHisto alias_attribute :job_id, :JobId alias_attribute :job, :Job alias_attribute :name, :Name alias_attribute :type, :Type alias_attribute :level, :Level alias_attribute :client_id, :ClientId alias_attribute :job_status, :JobStatus alias_attribute :sched_time, :SchedTime alias_attribute :start_time, :StartTime alias_attribute :end_time, :EndTime alias_attribute :real_end_time, :RealEndTime alias_attribute :job_t_date, :JobTDate alias_attribute :vol_session_id, :VolSessionId alias_attribute :vol_session_time, :VolSessionTime alias_attribute :job_files, :JobFiles alias_attribute :job_bytes, :JobBytes alias_attribute :read_bytes, :ReadBytes alias_attribute :job_errors, :JobErrors alias_attribute :job_missing_files, :JobMissingFiles alias_attribute :pool_id, :PoolId alias_attribute :file_set_id, :FileSetId alias_attribute :prior_job_id, :PriorJobId alias_attribute :purged_files, :PurgedFiles alias_attribute :has_base, :HasBase alias_attribute :has_cache, :HasCache alias_attribute :reviewed, :Reviewed alias_attribute :comment, :Comment belongs_to :client, foreign_key: :ClientId belongs_to :pool, foreign_key: :PoolId belongs_to :file_set, foreign_key: :FileSetId end diff --git a/app/models/job_media.rb b/app/models/job_media.rb index 8f07497..7480f77 100644 --- a/app/models/job_media.rb +++ b/app/models/job_media.rb @@ -1,18 +1,35 @@ +# Bacula JobMedia table. +# +# The JobMedia table contains one entry at the following: +# +# * start of the job, +# * start of each new tape file, +# * start of each new tape, +# * end of the job. +# +# Since by default, a new tape file is written every 2GB, in general, you will have more +# than 2 JobMedia records per Job. +# The number can be varied by changing the "Maximum File Size" specified in the Device resource. +# This record allows Bacula to efficiently position close to (within 2GB) any given file in a backup. +# For restoring a full Job, these records are not very important, but if you want to retrieve a +# single file that was written near the end of a 100GB backup, the JobMedia records can speed it +# up by orders of magnitude by permitting forward spacing files and blocks rather than reading +# the whole 100GB backup. class JobMedia < ActiveRecord::Base self.table_name = :JobMedia self.primary_key = :JobMediaId alias_attribute :job_media_id, :JobMediaId alias_attribute :job_id, :JobId alias_attribute :media_id, :MediaId alias_attribute :first_index, :FirstIndex alias_attribute :last_index, :LastIndex alias_attribute :start_file, :StartFile alias_attribute :end_file, :EndFile alias_attribute :start_block, :StartBlock alias_attribute :end_block, :EndBlock alias_attribute :vol_index, :VolIndex belongs_to :Job, foreign_key: :JobId belongs_to :Media, foreign_key: :MediaId end diff --git a/app/models/location.rb b/app/models/location.rb index 4411af5..15abd8b 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -1,12 +1,15 @@ +# Bacula Location table. +# +# The Location table defines where a Volume is physically. class Location < ActiveRecord::Base self.table_name = :Location self.primary_key = :LocationId alias_attribute :location_id, :LocationId alias_attribute :location, :Location alias_attribute :cost, :Cost alias_attribute :enabled, :Enabled has_many :media, foreign_key: :LocationId has_many :location_logs, foreign_key: :LocationId end diff --git a/app/models/location_log.rb b/app/models/location_log.rb index c82ddd2..57119c8 100644 --- a/app/models/location_log.rb +++ b/app/models/location_log.rb @@ -1,15 +1,16 @@ +# Bacula LocationLog table class LocationLog < ActiveRecord::Base self.table_name = :LocationLog self.primary_key = :LocLogId alias_attribute :loc_log_id, :LocLogId alias_attribute :date, :Date alias_attribute :comment, :Comment alias_attribute :media_id, :MediaId alias_attribute :location_id, :LocationId alias_attribute :new_vol_status, :NewVolStatus alias_attribute :new_enabled, :NewEnabled belongs_to :media, foreign_key: :MediaId belongs_to :location, foreign_key: :LocationId end diff --git a/app/models/log.rb b/app/models/log.rb index 2a67b3e..672d966 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -1,19 +1,22 @@ +# Bacula Log table. +# +# The Log table contains a log of all Job output. class Log < ActiveRecord::Base self.table_name = :Log self.primary_key = :LogId alias_attribute :log_id, :LogId alias_attribute :job_id, :JobId alias_attribute :time, :Time alias_attribute :log_text, :LogText belongs_to :job, foreign_key: :JobId paginates_per 20 def time_formatted if time I18n.l(time, format: :long) end end end diff --git a/app/models/media.rb b/app/models/media.rb index 339d4b7..7938be1 100644 --- a/app/models/media.rb +++ b/app/models/media.rb @@ -1,55 +1,61 @@ +# Bacula Media table (Volume) +# +# Media table contains one entry for each volume, that is each tape, cassette (8mm, DLT, DAT, ...), +# or file on which information is or was backed up. +# There is one Volume record created for each of the NumVols specified in the +# Pool resource record. class Media < ActiveRecord::Base self.table_name = :Media self.primary_key = :MediaId alias_attribute :media_id, :MediaId alias_attribute :volume_name, :VolumeName alias_attribute :slot, :Slot alias_attribute :pool_id, :PoolId alias_attribute :media_type, :MediaType alias_attribute :media_type_id, :MediaTypeId alias_attribute :label_type, :LabelType alias_attribute :first_written, :FirstWritten alias_attribute :last_written, :LastWritten alias_attribute :label_date, :LabelDate alias_attribute :vol_jobs, :VolJobs alias_attribute :vol_files, :VolFiles alias_attribute :vol_blocks, :VolBlocks alias_attribute :vol_mounts, :VolMounts alias_attribute :vol_bytes, :VolBytes alias_attribute :vol_parts, :VolParts alias_attribute :vol_errors, :VolErrors alias_attribute :vol_writes, :VolWrites alias_attribute :vol_capacity_bytes, :VolCapacityBytes alias_attribute :vol_status, :VolStatus alias_attribute :enabled, :Enabled alias_attribute :recycle, :Recycle alias_attribute :action_on_purge, :ActionOnPurge alias_attribute :vol_retention, :VolRetention alias_attribute :vol_use_duration, :VolUseDuration alias_attribute :max_vol_jobs, :MaxVolJobs alias_attribute :max_vol_files, :MaxVolFiles alias_attribute :max_vol_bytes, :MaxVolBytes alias_attribute :in_changer, :InChanger alias_attribute :storage_id, :StorageId alias_attribute :device_id, :DeviceId alias_attribute :media_addressing, :MediaAddressing alias_attribute :vol_read_time, :VolReadTime alias_attribute :vol_write_time, :VolWriteTime alias_attribute :end_file, :EndFile alias_attribute :end_block, :EndBlock alias_attribute :location_id, :LocationId alias_attribute :recycle_count, :RecycleCount alias_attribute :initial_write, :InitialWrite alias_attribute :scratch_pool_id, :ScratchPoolId alias_attribute :recycle_pool_id, :RecyclePoolId alias_attribute :comment, :Comment belongs_to :pool, foreign_key: :PoolId belongs_to :storage, foreign_key: :StorageId belongs_to :device, foreign_key: :DeviceId belongs_to :location, foreign_key: :LocationId has_many :job_media, foreign_key: :MediaId has_many :location_logs, foreign_key: :MediaId end diff --git a/app/models/path.rb b/app/models/path.rb index 847b32d..dd6eacd 100644 --- a/app/models/path.rb +++ b/app/models/path.rb @@ -1,9 +1,16 @@ +# Bacula Path table. +# +# The Path table contains shown above the path or directory names of all directories on +# the system or systems. +# As with the filename, only one copy of each directory name is kept regardless of how +# many machines or drives have the same directory. +# These path names should be stored in Unix path name format. class Path < ActiveRecord::Base self.table_name = :Path self.primary_key = :PathId alias_attribute :path_id, :PathId alias_attribute :path, :Path has_many :bacula_files, foreign_key: :PathId end diff --git a/app/models/pool.rb b/app/models/pool.rb index 301263a..8061ea9 100644 --- a/app/models/pool.rb +++ b/app/models/pool.rb @@ -1,37 +1,45 @@ +# Bacula Pool +# +# The Pool table contains one entry for each media pool controlled by Bacula in +# this database. One media record exists for each of the NumVols contained in the Pool. +# The PoolType is a Bacula defined keyword. +# The MediaType is defined by the administrator, and corresponds to the MediaType +# specified in the Director's Storage definition record. +# The CurrentVol is the sequence number of the Media record for the current volume. class Pool < ActiveRecord::Base self.table_name = :Pool self.primary_key = :PoolId alias_attribute :pool_id, :PoolId alias_attribute :name, :Name alias_attribute :num_vols, :NumVols alias_attribute :max_vols, :MaxVols alias_attribute :use_once, :UseOnce alias_attribute :use_catalog, :UseCatalog alias_attribute :accept_any_volume, :AcceptAnyVolume alias_attribute :vol_retention, :VolRetention alias_attribute :vol_use_duration, :VolUseDuration alias_attribute :max_vol_jobs, :MaxVolJobs alias_attribute :max_vol_files, :MaxVolFiles alias_attribute :max_vol_bytes, :MaxVolBytes alias_attribute :auto_prune, :AutoPrune alias_attribute :recycle, :Recycle alias_attribute :action_on_purge, :ActionOnPurge alias_attribute :pool_type, :PoolType alias_attribute :label_type, :LabelType alias_attribute :label_format, :LabelFormat alias_attribute :enabled, :Enabled alias_attribute :scratch_pool_id, :ScratchPoolId alias_attribute :recycle_pool_id, :RecyclePoolId alias_attribute :next_pool_id, :NextPoolId alias_attribute :migration_high_bytes, :MigrationHighBytes alias_attribute :migration_low_bytes, :MigrationLowBytes alias_attribute :migration_time, :MigrationTime has_many :jobs, foreign_key: :PoolId has_many :media, foreign_key: :PoolId def self.available_options pluck(:Name) end end diff --git a/app/models/status.rb b/app/models/status.rb index 9ced15a..c6667ed 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -1,8 +1,14 @@ +# Bacula Status table. +# +# Status table is a lookup table linking: +# +# * jobs' status codes and +# * status codes' messages class Status < ActiveRecord::Base self.table_name = :Status self.primary_key = :JobStatus alias_attribute :job_status, :JobStatus alias_attribute :job_status_long, :JobStatusLong alias_attribute :severity, :Severity end diff --git a/app/models/storage.rb b/app/models/storage.rb index f99950d..016e9f6 100644 --- a/app/models/storage.rb +++ b/app/models/storage.rb @@ -1,14 +1,17 @@ +# Bacula Storage table +# +# The Storage table contains one entry for each Storage used. class Storage < ActiveRecord::Base self.table_name = :Storage self.primary_key = :StorageId alias_attribute :storage_id, :StorageId alias_attribute :name, :Name alias_attribute :auto_changer, :AutoChanger has_many :media, foreign_key: :StorageId def self.available_options pluck(:Name) end end diff --git a/app/models/unsaved_file.rb b/app/models/unsaved_file.rb index 56b4866..72501c4 100644 --- a/app/models/unsaved_file.rb +++ b/app/models/unsaved_file.rb @@ -1,9 +1,10 @@ +# Bacula UnsavedFile table class UnsavedFile < ActiveRecord::Base self.table_name = :UnsavedFiles self.primary_key = :UnsavedId alias_attribute :unsaved_id, :UnsavedId alias_attribute :job_id, :JobId alias_attribute :path_id, :PathId alias_attribute :filename_id, :FilenameId end diff --git a/app/models/version.rb b/app/models/version.rb index 1e17cc4..e823f49 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -1,5 +1,10 @@ +# Bacula Version table +# +# The Version table defines the Bacula database version number. +# Bacula checks this number before reading the database to ensure that it is +# compatible with the Bacula binary file. class Version < ActiveRecord::Base self.table_name = :Version alias_attribute :version_id, :VersionId end