Class: RDSBackup::Job
- Inherits:
-
Object
- Object
- RDSBackup::Job
- Defined in:
- lib/rds_backup_service/model/backup_job.rb
Overview
Backs up the contents of a single RDS database to S3
Instance Attribute Summary collapse
-
#account_name ⇒ Object
readonly
Returns the value of attribute account_name.
-
#backup_id ⇒ Object
readonly
Returns the value of attribute backup_id.
-
#files ⇒ Object
readonly
Returns the value of attribute files.
-
#message ⇒ Object
readonly
Returns the value of attribute message.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#rds_id ⇒ Object
readonly
Returns the value of attribute rds_id.
-
#requested ⇒ Object
readonly
Returns the value of attribute requested.
-
#status ⇒ Object
readonly
Returns the value of attribute status.
-
#status_url ⇒ Object
readonly
Returns the value of attribute status_url.
Class Method Summary collapse
-
.perform(rds_instance_id, options = {}) ⇒ Object
Entry point for the Resque framework.
Instance Method Summary collapse
-
#configure_tmp_rds ⇒ Object
Updates the Master Password and applies the configured RDS Security Group.
-
#create_disconnected_rds(new_rds_name = nil) ⇒ Object
Step 1 of the overall process - create a disconnected copy of the RDS.
-
#create_tmp_rds_from_snapshot ⇒ Object
Creates a new RDS from the snapshot.
-
#delete_disconnected_rds ⇒ Object
Destroys the temporary RDS instance.
-
#destroy_snapshot ⇒ Object
Destroys the snapshot if it exists.
-
#download_data_from_tmp_rds ⇒ Object
Connects to the RDS server, and dumps the database to a temp dir.
-
#initialize(rds_instance_id, optional_params = {}) ⇒ Job
constructor
Constructor.
-
#perform_backup ⇒ Object
Top-level, long-running method for performing the backup.
-
#prepare_backup ⇒ Object
Queries RDS for any pre-existing entities associated with this job.
-
#s3 ⇒ Object
lazily initializes and returns S3 connection.
-
#send_mail ⇒ Object
Sends a status email.
-
#snapshot_original_rds ⇒ Object
Snapshots the original RDS.
-
#to_json ⇒ Object
returns a JSON-format String representation of this backup job.
-
#update_status(message, new_status = nil) ⇒ Object
Writes a new status message to the log, and writes the job info to S3.
-
#upload_output_to_s3 ⇒ Object
Uploads the compressed SQL file to S3.
-
#wait_for_new_parameter_group ⇒ Object
Wait for the new RDS Parameter Group to become ‘in-sync’.
-
#wait_for_new_security_group ⇒ Object
Wait for the new RDS Security Group to become ‘active’.
-
#write_to_s3 ⇒ Object
Writes this job’s JSON representation to S3.
Constructor Details
#initialize(rds_instance_id, optional_params = {}) ⇒ Job
Constructor.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 20 def initialize(rds_instance_id, optional_params = {}) @rds_id, @options = rds_instance_id, optional_params.symbolize_keys @backup_id = [:backup_id] || "%016x" % (rand * 0xffffffffffffffff) @requested = [:requested] ? Time.parse([:requested]) : Time.now @status = 200 @message = "queued" @files = [] @config = RDSBackup.settings @bucket = @config['backup_bucket'] @s3_path = (@config['backup_prefix'] ? "#{@config['backup_prefix']}/" : "")+ "#{requested.strftime("%Y/%m/%d")}/#{rds_id}/#{backup_id}" @snapshot_id = "rds-backup-service-#{rds_id}-#{backup_id}" @new_rds_id = "rds-backup-service-#{backup_id}" @new_password = "#{backup_id}" @account_name = [:account_name] end |
Instance Attribute Details
#account_name ⇒ Object (readonly)
Returns the value of attribute account_name.
10 11 12 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 10 def account_name @account_name end |
#backup_id ⇒ Object (readonly)
Returns the value of attribute backup_id.
10 11 12 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 10 def backup_id @backup_id end |
#files ⇒ Object (readonly)
Returns the value of attribute files.
11 12 13 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 11 def files @files end |
#message ⇒ Object (readonly)
Returns the value of attribute message.
11 12 13 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 11 def @message end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
10 11 12 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 10 def @options end |
#rds_id ⇒ Object (readonly)
Returns the value of attribute rds_id.
10 11 12 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 10 def rds_id @rds_id end |
#requested ⇒ Object (readonly)
Returns the value of attribute requested.
11 12 13 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 11 def requested @requested end |
#status ⇒ Object (readonly)
Returns the value of attribute status.
11 12 13 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 11 def status @status end |
#status_url ⇒ Object (readonly)
Returns the value of attribute status_url.
11 12 13 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 11 def status_url @status_url end |
Class Method Details
.perform(rds_instance_id, options = {}) ⇒ Object
Entry point for the Resque framework. Parameters are the same as for #initialize()
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 62 def self.perform(rds_instance_id, = {}) job = Job.new(rds_instance_id, ) begin job.perform_backup rescue Resque::TermException => e ::Resque.enqueue_to(:backups, Job, rds_instance_id, .merge(backup_id: job.backup_id, requested: job.requested.to_s)) job.update_status "Terminated on interrupt signal - requeued" end end |
Instance Method Details
#configure_tmp_rds ⇒ Object
Updates the Master Password and applies the configured RDS Security Group
147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 147 def configure_tmp_rds update_status "Waiting for instance #{@new_instance.id}..." @new_instance.wait_for { ready? } update_status "Modifying RDS attributes for new RDS #{@new_instance.id}" @rds.modify_db_instance(@new_instance.id, true, { 'DBParameterGroupName' => @original_server.db_parameter_groups. first['DBParameterGroupName'], 'DBSecurityGroups' => [ @config['rds_security_group'] ], 'MasterUserPassword' => @new_password, }) end |
#create_disconnected_rds(new_rds_name = nil) ⇒ Object
Step 1 of the overall process - create a disconnected copy of the RDS
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 91 def create_disconnected_rds(new_rds_name = nil) @new_rds_id = new_rds_name if new_rds_name prepare_backup unless @original_server # in case run as a convenience method snapshot_original_rds create_tmp_rds_from_snapshot configure_tmp_rds wait_for_new_security_group wait_for_new_parameter_group # (reboots as needed) destroy_snapshot end |
#create_tmp_rds_from_snapshot ⇒ Object
Creates a new RDS from the snapshot
127 128 129 130 131 132 133 134 135 136 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 127 def create_tmp_rds_from_snapshot unless @new_instance update_status "Waiting for snapshot #{@snapshot_id}" @snapshot.wait_for { ready? } update_status "Booting new RDS #{@new_rds_id} from snapshot #{@snapshot.id}" @rds.restore_db_instance_from_db_snapshot(@snapshot.id, @new_rds_id, 'DBInstanceClass' => @original_server.flavor_id) @new_instance = @rds.servers.get @new_rds_id end end |
#delete_disconnected_rds ⇒ Object
Destroys the temporary RDS instance
208 209 210 211 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 208 def delete_disconnected_rds update_status "Deleting RDS instance #{@new_instance.id}" @new_instance.destroy end |
#destroy_snapshot ⇒ Object
Destroys the snapshot if it exists
139 140 141 142 143 144 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 139 def destroy_snapshot if (@snapshot = @rds.snapshots.get @snapshot_id) update_status "Deleting snapshot #{@snapshot.id}" @snapshot.destroy end end |
#download_data_from_tmp_rds ⇒ Object
Connects to the RDS server, and dumps the database to a temp dir
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 191 def download_data_from_tmp_rds @new_instance.wait_for { ready? } db_name = @original_server.db_name db_user = @original_server.master_username update_status "Dumping database #{db_name} from #{@new_instance.id}" dump_time = @snapshot ? Time.parse(@snapshot.created_at.to_s) : Time.now date_stamp = dump_time.strftime("%Y-%m-%d-%H%M%S") @sql_file = "#{@config['tmp_dir']}/#{@s3_path}/#{db_name}.#{date_stamp}.sql.gz" hostname = @new_instance.endpoint['Address'] dump_cmd = "mysqldump -u #{db_user} -h #{hostname} "+ "-p#{@new_password} #{db_name} | gzip >#{@sql_file}" FileUtils.mkpath(File.dirname @sql_file) @log.debug "Executing command: #{dump_cmd}" `#{dump_cmd}` end |
#perform_backup ⇒ Object
Top-level, long-running method for performing the backup.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 74 def perform_backup begin prepare_backup update_status "Backing up #{rds_id} from account #{account_name}" create_disconnected_rds download_data_from_tmp_rds # populates @sql_file delete_disconnected_rds upload_output_to_s3 update_status "Backup of #{rds_id} complete" send_mail rescue Exception => e update_status "ERROR: #{e..split("\n").first}", 500 raise e end end |
#prepare_backup ⇒ Object
Queries RDS for any pre-existing entities associated with this job. Also waits for the original RDS to become ready.
104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 104 def prepare_backup unless @original_server = RDSBackup.get_rds(rds_id) names = RDSBackup.rds_accounts.map {|name, account| name } raise "Unable to find RDS #{rds_id} in accounts #{names.join ", "}" end @account_name = @original_server.tracker_account[:name] @rds = ::Fog::AWS::RDS.new( RDSBackup.rds_accounts[@account_name][:credentials]) @snapshot = @rds.snapshots.get @snapshot_id @new_instance = @rds.servers.get @new_rds_id end |
#s3 ⇒ Object
lazily initializes and returns S3 connection
250 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 250 def s3 ; @s3 ||= RDSBackup.s3 end |
#send_mail ⇒ Object
Sends a status email
238 239 240 241 242 243 244 245 246 247 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 238 def send_mail return unless @options[:email] @log.info "Emailing #{@options[:email]}..." begin RDSBackup::Email.new(self).send! @log.info "Email sent to #{@options[:email]} for job #{backup_id}" rescue Exception => e @log.warn "Error sending email: #{e..split("\n").first}" end end |
#snapshot_original_rds ⇒ Object
Snapshots the original RDS
117 118 119 120 121 122 123 124 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 117 def snapshot_original_rds unless @new_instance || @snapshot update_status "Waiting for RDS instance #{@original_server.id}" @original_server.wait_for { ready? } update_status "Creating snapshot #{@snapshot_id} from RDS #{rds_id}" @snapshot = @rds.snapshots.create(id: @snapshot_id, instance_id: rds_id) end end |
#to_json ⇒ Object
returns a JSON-format String representation of this backup job
38 39 40 41 42 43 44 45 46 47 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 38 def to_json JSON.pretty_generate({ rds_instance: rds_id, account_name: account_name, backup_status: status, status_message: , status_url: status_url, files: files, }) end |
#update_status(message, new_status = nil) ⇒ Object
Writes a new status message to the log, and writes the job info to S3
229 230 231 232 233 234 235 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 229 def update_status(, new_status = nil) @log = @options[:logger] || RDSBackup.default_logger(STDOUT) @message = @status = new_status if new_status @status == 200 ? (@log.info ) : (@log.error ) write_to_s3 end |
#upload_output_to_s3 ⇒ Object
Uploads the compressed SQL file to S3
214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 214 def upload_output_to_s3 update_status "Uploading output file #{::File.basename @sql_file}" dump_path = "#{@s3_path}/#{::File.basename @sql_file}" s3.put_object(@bucket, dump_path, File.read(@sql_file)) upload = s3.directories.get(@bucket).files.get dump_path @files = [ { name: ::File.basename(@sql_file), size: upload.content_length, url: upload.url(Time.now + (3600 * 24 * 30)) # 30 days from now } ] @log.info "Deleting tmp directory #{File.dirname @sql_file}" FileUtils.rm_rf(File.dirname @sql_file) end |
#wait_for_new_parameter_group ⇒ Object
Wait for the new RDS Parameter Group to become ‘in-sync’
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 173 def wait_for_new_parameter_group old_name = @original_server.db_parameter_groups.first['DBParameterGroupName'] update_status "Applying parameter group #{old_name} to #{@new_instance.id}" job = self # save local var for closure in wait_for, below @new_instance.wait_for { new_group = (db_parameter_groups.select do |group| group['DBParameterGroupName'] == old_name end).first status = (new_group ? new_group['ParameterApplyStatus'] : 'Unknown') if (status == "pending-reboot") job.update_status "Rebooting RDS #{id} to apply ParameterGroup #{old_name}" reboot and wait_for { ready? } end status == 'in-sync' && ready? } end |
#wait_for_new_security_group ⇒ Object
Wait for the new RDS Security Group to become ‘active’
160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 160 def wait_for_new_security_group old_group_name = @config['rds_security_group'] update_status "Applying security group #{old_group_name}"+ " to #{@new_instance.id}" @new_instance.wait_for { new_group = (db_security_groups.select do |group| group['DBSecurityGroupName'] == old_group_name end).first (new_group ? new_group['Status'] : 'Unknown') == 'active' } end |
#write_to_s3 ⇒ Object
Writes this job’s JSON representation to S3
50 51 52 53 54 55 56 57 58 |
# File 'lib/rds_backup_service/model/backup_job.rb', line 50 def write_to_s3 status_path = "#{@s3_path}/status.json" s3.put_object(@bucket, status_path, "#{to_json}\n") unless @status_url expire_date = Time.now + (3600 * 24) # one day from now @status_url = s3.get_object_http_url(@bucket, status_path, expire_date) s3.put_object(@bucket, status_path, "#{to_json}\n") end end |