db2s3 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ spec/s3_config.rb
data/HISTORY ADDED
@@ -0,0 +1,4 @@
1
+ 0.2.4 (2 Sep 2009)
2
+ - Fix credentials bug
3
+ 0.2.3
4
+ - Keep old backups around
data/README ADDED
@@ -0,0 +1,37 @@
1
+ DB2S3 - A rails plugin to backup Mysql to Amazon S3
2
+ ---------------------------------------------------
3
+ You're looking at a monthly spend of four cents
4
+ So pony up you cheap bastard, and store your backups on S3
5
+
6
+ Usage:
7
+ # In config/environment.rb
8
+ config.gem "xaviershay-db2s3", :lib => "db2s3", :source => "http://gems.github.com"
9
+
10
+ # In Rakefile
11
+ require 'db2s3/tasks'
12
+
13
+ # In config/initializers/db2s3.rb
14
+ DB2S3::Config.instance_eval do
15
+ S3 = {
16
+ :access_key_id => 'yourkey',
17
+ :secret_access_key => 'yoursecretkey',
18
+ :bucket => 'yourapp-db-backup'
19
+ }
20
+ end
21
+ # DB credentials are read from your rails environment
22
+
23
+ rake gems:install
24
+
25
+ # Add to your crontab or whatever
26
+ rake db2s3:backup:full
27
+ rake db2s3:backup:incremental # Unimplemented
28
+
29
+ # Handy tasks
30
+ rake db2s3:metrics # Estimated costs
31
+ rake db2s3:backup:restore # You should be testing this regularly
32
+
33
+ Caveats:
34
+ Currently does not clean up old back ups
35
+
36
+ Kudos:
37
+ http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ desc "Create test database"
2
+ task :create_test_db do
3
+ `mysqladmin -u root create db2s3_unittest`
4
+ end
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = 'db2s3'
10
+ gemspec.summary = "Summarize your gem"
11
+ gemspec.description = 'db2s3 provides rake tasks for backing up and restoring your DB to S3'
12
+ gemspec.email = 'contact@rhnh.net'
13
+ gemspec.homepage = 'http://github.com/xaviershay/db2s3'
14
+ gemspec.authors = ['Xavier Shay']
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
data/db2s3.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{db2s3}
8
+ s.version = "0.2.5"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Xavier Shay"]
12
+ s.date = %q{2009-11-07}
13
+ s.description = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
14
+ s.email = %q{contact@rhnh.net}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "HISTORY",
21
+ "README",
22
+ "Rakefile",
23
+ "db2s3.gemspec",
24
+ "init.rb",
25
+ "lib/db2s3.rb",
26
+ "lib/db2s3/tasks.rb",
27
+ "rails/init.rb",
28
+ "spec/db2s3_spec.rb",
29
+ "spec/mysql_drop_schema.sql",
30
+ "spec/mysql_schema.sql",
31
+ "spec/s3_config.example.rb",
32
+ "spec/spec_helper.rb",
33
+ "tasks/tasks.rake"
34
+ ]
35
+ s.homepage = %q{http://github.com/xaviershay/db2s3}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.4}
39
+ s.summary = %q{Summarize your gem}
40
+ s.test_files = [
41
+ "spec/db2s3_spec.rb",
42
+ "spec/s3_config.example.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
data/lib/db2s3.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'aws/s3'
2
+ require 'tempfile'
3
+
4
+ class DB2S3
5
+ class Config
6
+ end
7
+
8
+ def initialize
9
+ end
10
+
11
+ def full_backup
12
+ file_name = "dump-#{db_credentials[:database]}-#{Time.now.utc.strftime("%Y%m%d%H%M")}.sql.gz"
13
+ store.store(file_name, open(dump_db.path))
14
+ store.store(most_recent_dump_file_name, file_name)
15
+ end
16
+
17
+ def restore
18
+ dump_file_name = store.fetch(most_recent_dump_file_name).read
19
+ file = store.fetch(dump_file_name)
20
+ run "gunzip -c #{file.path} | mysql #{mysql_options}"
21
+ end
22
+
23
+ def metrics
24
+ dump_file = dump_db
25
+
26
+ storage_dollars_per_byte_per_month = 0.15 / 1024.0 / 1024.0 / 1024.0
27
+ transfer_dollars_per_byte_per_month = 0.10 / 1024.0 / 1024.0 / 1024.0
28
+ full_dumps_per_month = 30
29
+
30
+ storage_cost = (dump_file.size * storage_dollars_per_byte_per_month * 100).ceil / 100.0
31
+ transfer_cost = (dump_file.size * full_dumps_per_month * transfer_dollars_per_byte_per_month * 100).ceil / 100.0
32
+ requests_cost = 0.02 # TODO: Actually calculate this, with incremental backups could be more
33
+
34
+ {
35
+ :db_size => dump_file.size,
36
+ :storage_cost => storage_cost,
37
+ :transfer_cost => transfer_cost,
38
+ :total_cost => storage_cost + transfer_cost + requests_cost,
39
+ :requests_cost => requests_cost,
40
+ :full_backups_per_month => full_dumps_per_month
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def dump_db
47
+ dump_file = Tempfile.new("dump")
48
+
49
+ #cmd = "mysqldump --quick --single-transaction --create-options -u#{db_credentials[:user]} --flush-logs --master-data=2 --delete-master-logs"
50
+ cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
51
+ cmd += " | gzip > #{dump_file.path}"
52
+ run(cmd)
53
+
54
+ dump_file
55
+ end
56
+
57
+ def mysql_options
58
+ cmd = " -u #{db_credentials[:username]} "
59
+ cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
60
+ cmd += " -h '#{db_credentials[:host]}'" unless db_credentials[:host].nil?
61
+ cmd += " #{db_credentials[:database]}"
62
+ end
63
+
64
+ def store
65
+ @store ||= S3Store.new
66
+ end
67
+
68
+ def most_recent_dump_file_name
69
+ "most-recent-dump-#{db_credentials[:database]}.txt"
70
+ end
71
+
72
+ def run(command)
73
+ result = system(command)
74
+ raise("error, process exited with status #{$?.exitstatus}") unless result
75
+ end
76
+
77
+ def db_credentials
78
+ ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
79
+ end
80
+
81
+ class S3Store
82
+ def initialize
83
+ @connected = false
84
+ end
85
+
86
+ def ensure_connected
87
+ return if @connected
88
+ AWS::S3::Base.establish_connection!(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
89
+ AWS::S3::Bucket.create(bucket)
90
+ @connected = true
91
+ end
92
+
93
+ def store(file_name, file)
94
+ ensure_connected
95
+ AWS::S3::S3Object.store(file_name, file, bucket)
96
+ end
97
+
98
+ def fetch(file_name)
99
+ ensure_connected
100
+ AWS::S3::S3Object.find(file_name, bucket)
101
+
102
+ file = Tempfile.new("dump")
103
+ open(file.path, 'w') do |f|
104
+ AWS::S3::S3Object.stream(file_name, bucket) do |chunk|
105
+ f.write chunk
106
+ end
107
+ end
108
+ file
109
+ end
110
+
111
+ private
112
+
113
+ def bucket
114
+ DB2S3::Config::S3[:bucket]
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1 @@
1
+ Dir["#{File.dirname(__FILE__)}/../../tasks/*.rake"].each { |ext| load ext }
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/db2s3')
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'db2s3' do
4
+ def load_schema
5
+ `cat '#{File.dirname(__FILE__) + '/mysql_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
6
+ end
7
+
8
+ def drop_schema
9
+ `cat '#{File.dirname(__FILE__) + '/mysql_drop_schema.sql'}' | mysql -u #{DBConfig[:user]} #{DBConfig[:database]}`
10
+ end
11
+
12
+ class Person < ActiveRecord::Base
13
+ end
14
+
15
+ if DB2S3::Config.const_defined?('S3')
16
+ it 'can save and restore a backup to S3' do
17
+ db2s3 = DB2S3.new
18
+ load_schema
19
+ Person.create!(:name => "Baxter")
20
+ db2s3.full_backup
21
+ drop_schema
22
+ db2s3.restore
23
+ Person.find_by_name("Baxter").should_not be_nil
24
+ end
25
+ end
26
+
27
+ it 'provides estimated metrics' do
28
+ db2s3 = DB2S3.new
29
+ # 1 GB DB
30
+ db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024 * 1024 * 1024))
31
+ metrics = db2s3.metrics
32
+ metrics.should == {
33
+ :storage_cost => 0.15, # 15c/GB-Month rounded up to nearest cent, we're only storing one backup
34
+ :transfer_cost => 3.0, # 10c/GB-Month * 30 backups
35
+ :db_size => 1024 * 1024 * 1024, # 1 GB
36
+ :total_cost => 3.17,
37
+ :requests_cost => 0.02,
38
+ :full_backups_per_month => 30 # Default 1 backup/day
39
+ }
40
+ end
41
+
42
+ it 'rounds transfer cost metric up to nearest cent' do
43
+ db2s3 = DB2S3.new
44
+ # 1 KB DB
45
+ db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024))
46
+ metrics = db2s3.metrics
47
+ metrics[:storage_cost].should == 0.01
48
+ metrics[:transfer_cost].should == 0.01
49
+ end
50
+ end
@@ -0,0 +1 @@
1
+ DROP TABLE IF EXISTS people;
@@ -0,0 +1,4 @@
1
+ DROP TABLE IF EXISTS people;
2
+ CREATE TABLE people (
3
+ name VARCHAR(255) NULL
4
+ );
@@ -0,0 +1,5 @@
1
+ DB2S3::Config:: S3 = {
2
+ :access_key_id => 'yourkey',
3
+ :secret_access_key => 'yoursecretkey',
4
+ :bucket => 'db2s3_test'
5
+ }
@@ -0,0 +1,18 @@
1
+ require 'spec'
2
+ require 'activerecord'
3
+ require File.dirname(__FILE__) + '/../lib/db2s3'
4
+ if File.exists?(File.dirname(__FILE__) + '/s3_config.rb')
5
+ require File.dirname(__FILE__) + '/s3_config.rb'
6
+ else
7
+ puts "s3_config.rb does not exist - not running live tests"
8
+ end
9
+
10
+ DBConfig = {
11
+ :adapter => "mysql",
12
+ :encoding => "utf8",
13
+ :database => 'db2s3_unittest',
14
+ :user => "root"
15
+ }
16
+
17
+ ActiveRecord::Base.configurations = { 'production' => DBConfig }
18
+ ActiveRecord::Base.establish_connection(:production)
data/tasks/tasks.rake ADDED
@@ -0,0 +1,40 @@
1
+ namespace :db2s3 do
2
+ namespace :backup do
3
+ desc "Save a full back to S3"
4
+ task :full => :environment do
5
+ DB2S3.new.full_backup
6
+ end
7
+
8
+ desc "Restore your DB from S3"
9
+ task :restore => :environment do
10
+ DB2S3.new.restore
11
+ end
12
+ end
13
+
14
+ desc "Provide estimated costs for backing up your DB to S3"
15
+ task :metrics => :environment do
16
+ def format_size(size)
17
+ units = %w{B KB MB GB TB}
18
+ e = (Math.log(size)/Math.log(1024)).floor
19
+ s = "%.3f" % (size.to_f / 1024**e)
20
+ s.sub(/\.?0*$/, units[e])
21
+ end
22
+
23
+ def format_cost(cost)
24
+ "%.2f" % [cost]
25
+ end
26
+
27
+ metrics = DB2S3.new.metrics
28
+ puts <<-EOS
29
+ Estimates only, does not take into account metadata overhead
30
+ Code has recently been added that keeps old backups around - this is not taken into account in these estimates
31
+
32
+ DB Size: #{format_size(metrics[:db_size])}
33
+ Full backups/month: #{metrics[:full_backups_per_month]}
34
+ Storage Cost $US: #{format_cost(metrics[:storage_cost])}
35
+ Transfer Cost $US: #{format_cost(metrics[:transfer_cost])}
36
+ Requests Cost $US: #{format_cost(metrics[:requests_cost])}
37
+ Total Cost $US: #{format_cost(metrics[:total_cost])}
38
+ EOS
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db2s3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.5
5
+ platform: ruby
6
+ authors:
7
+ - Xavier Shay
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-07 00:00:00 +11:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: db2s3 provides rake tasks for backing up and restoring your DB to S3
17
+ email: contact@rhnh.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - .gitignore
26
+ - HISTORY
27
+ - README
28
+ - Rakefile
29
+ - db2s3.gemspec
30
+ - init.rb
31
+ - lib/db2s3.rb
32
+ - lib/db2s3/tasks.rb
33
+ - rails/init.rb
34
+ - spec/db2s3_spec.rb
35
+ - spec/mysql_drop_schema.sql
36
+ - spec/mysql_schema.sql
37
+ - spec/s3_config.example.rb
38
+ - spec/spec_helper.rb
39
+ - tasks/tasks.rake
40
+ has_rdoc: true
41
+ homepage: http://github.com/xaviershay/db2s3
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.4
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Summarize your gem
68
+ test_files:
69
+ - spec/db2s3_spec.rb
70
+ - spec/s3_config.example.rb
71
+ - spec/spec_helper.rb