fcoury-db2s3 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 only stores the latest backup
35
+
36
+ Kudos:
37
+ http://github.com/pauldowman/blog_code_examples/tree/master/mysql_s3_backup
@@ -0,0 +1,4 @@
1
+ desc "Create test database"
2
+ task :create_test_db do
3
+ `mysqladmin -u root create db2s3_unittest`
4
+ end
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{db2s3}
5
+ s.version = "0.2.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Felipe Coury"]
9
+ s.date = %q{2009-03-08}
10
+ s.description = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
11
+ s.email = %q{contact@felipecoury.com}
12
+ s.files = %w(
13
+ README
14
+ Rakefile
15
+ db2s3.gemspec
16
+ init.rb
17
+ lib
18
+ lib/db2s3.rb
19
+ lib/db2s3/tasks.rb
20
+ rails
21
+ rails/init.rb
22
+ spec
23
+ spec/db2s3_spec.rb
24
+ spec/mysql_drop_schema.sql
25
+ spec/mysql_schema.sql
26
+ spec/s3_config.example.rb
27
+ spec/spec_helper.rb
28
+ tasks
29
+ tasks/tasks.rake
30
+ )
31
+ s.has_rdoc = false
32
+ s.homepage = %q{http://github.com/fcoury/db2s3}
33
+ #s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ #s.rubyforge_project = %q{grit}
36
+ s.rubygems_version = %q{1.3.1}
37
+ s.summary = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
38
+
39
+ # TODO: WTF does this do
40
+ if s.respond_to? :specification_version then
41
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
42
+ s.specification_version = 2
43
+ end
44
+
45
+ s.add_dependency(%q<aws-s3>, [">= 0.5.1"])
46
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,109 @@
1
+ require 'aws/s3'
2
+
3
+ class DB2S3
4
+ class Config
5
+ end
6
+
7
+ def initialize
8
+ end
9
+
10
+ def full_backup
11
+ store.store("dump-#{db_credentials[:database]}.sql.gz", open(dump_db.path))
12
+ end
13
+
14
+ def restore
15
+ file = store.fetch("dump-#{db_credentials[:database]}.sql.gz")
16
+ run "gunzip -c #{file.path} | mysql #{mysql_options}"
17
+ end
18
+
19
+ def metrics
20
+ dump_file = dump_db
21
+
22
+ storage_dollars_per_byte_per_month = 0.15 / 1024.0 / 1024.0 / 1024.0
23
+ transfer_dollars_per_byte_per_month = 0.10 / 1024.0 / 1024.0 / 1024.0
24
+ full_dumps_per_month = 30
25
+
26
+ storage_cost = (dump_file.size * storage_dollars_per_byte_per_month * 100).ceil / 100.0
27
+ transfer_cost = (dump_file.size * full_dumps_per_month * transfer_dollars_per_byte_per_month * 100).ceil / 100.0
28
+ requests_cost = 0.02 # TODO: Actually calculate this, with incremental backups could be more
29
+
30
+ {
31
+ :db_size => dump_file.size,
32
+ :storage_cost => storage_cost,
33
+ :transfer_cost => transfer_cost,
34
+ :total_cost => storage_cost + transfer_cost + requests_cost,
35
+ :requests_cost => requests_cost,
36
+ :full_backups_per_month => full_dumps_per_month
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def dump_db
43
+ dump_file = Tempfile.new("dump")
44
+
45
+ #cmd = "mysqldump --quick --single-transaction --create-options -u#{db_credentials[:user]} --flush-logs --master-data=2 --delete-master-logs"
46
+ cmd = "mysqldump --quick --single-transaction --create-options #{mysql_options}"
47
+ cmd += " | gzip > #{dump_file.path}"
48
+ run(cmd)
49
+
50
+ dump_file
51
+ end
52
+
53
+ def mysql_options
54
+ cmd = " -u #{db_credentials[:username]} "
55
+ cmd += " -p'#{db_credentials[:password]}'" unless db_credentials[:password].nil?
56
+ cmd += " #{db_credentials[:database]}"
57
+ end
58
+
59
+ def store
60
+ @store ||= S3Store.new
61
+ end
62
+
63
+ def run(command)
64
+ result = system(command)
65
+ raise("error, process exited with status #{$?.exitstatus}") unless result
66
+ end
67
+
68
+ def db_credentials
69
+ ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
70
+ end
71
+
72
+ class S3Store
73
+ def initialize
74
+ @connected = false
75
+ end
76
+
77
+ def ensure_connected
78
+ return if @connected
79
+ AWS::S3::Base.establish_connection!(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
80
+ AWS::S3::Bucket.create(bucket)
81
+ @connected = true
82
+ end
83
+
84
+ def store(file_name, file)
85
+ ensure_connected
86
+ AWS::S3::S3Object.store(file_name, file, bucket)
87
+ end
88
+
89
+ def fetch(file_name)
90
+ ensure_connected
91
+ AWS::S3::S3Object.find(file_name, bucket)
92
+
93
+ file = Tempfile.new("dump")
94
+ open(file.path, 'w') do |f|
95
+ AWS::S3::S3Object.stream(file_name, bucket) do |chunk|
96
+ f.write chunk
97
+ end
98
+ end
99
+ file
100
+ end
101
+
102
+ private
103
+
104
+ def bucket
105
+ DB2S3::Config::S3[:bucket]
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1 @@
1
+ Dir["#{File.dirname(__FILE__)}/../../tasks/*.rake"].each { |ext| load ext }
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/db2s3')
@@ -0,0 +1,48 @@
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
+ it 'can save and restore a backup to S3' do
16
+ db2s3 = DB2S3.new
17
+ load_schema
18
+ Person.create!(:name => "Baxter")
19
+ db2s3.full_backup
20
+ drop_schema
21
+ db2s3.restore
22
+ Person.find_by_name("Baxter").should_not be_nil
23
+ end
24
+
25
+ it 'provides estimated metrics' do
26
+ db2s3 = DB2S3.new
27
+ # 1 GB DB
28
+ db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024 * 1024 * 1024))
29
+ metrics = db2s3.metrics
30
+ metrics.should == {
31
+ :storage_cost => 0.15, # 15c/GB-Month rounded up to nearest cent, we're only storing one backup
32
+ :transfer_cost => 3.0, # 10c/GB-Month * 30 backups
33
+ :db_size => 1024 * 1024 * 1024, # 1 GB
34
+ :total_cost => 3.17,
35
+ :requests_cost => 0.02,
36
+ :full_backups_per_month => 30 # Default 1 backup/day
37
+ }
38
+ end
39
+
40
+ it 'rounds transfer cost metric up to nearest cent' do
41
+ db2s3 = DB2S3.new
42
+ # 1 KB DB
43
+ db2s3.stub!(:dump_db).and_return(stub("dump file", :size => 1024))
44
+ metrics = db2s3.metrics
45
+ metrics[:storage_cost].should == 0.01
46
+ metrics[:transfer_cost].should == 0.01
47
+ end
48
+ 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,7 @@
1
+ DB2S3::Config.instance_eval do
2
+ S3 = {
3
+ :access_key_id => 'yourkey',
4
+ :secret_access_key => 'yoursecretkey',
5
+ :bucket => 'db2s3_test'
6
+ }
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec'
2
+ require 'activerecord'
3
+ require File.dirname(__FILE__) + '/../lib/db2s3'
4
+ require File.dirname(__FILE__) + '/s3_config.rb'
5
+
6
+ DBConfig = {
7
+ :adapter => "mysql",
8
+ :encoding => "utf8",
9
+ :database => 'db2s3_unittest',
10
+ :user => "root"
11
+ }
12
+
13
+ ActiveRecord::Base.configurations = { 'production' => DBConfig }
14
+ ActiveRecord::Base.establish_connection(:production)
@@ -0,0 +1,39 @@
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
+
31
+ DB Size: #{format_size(metrics[:db_size])}
32
+ Full backups/month: #{metrics[:full_backups_per_month]}
33
+ Storage Cost $US: #{format_cost(metrics[:storage_cost])}
34
+ Transfer Cost $US: #{format_cost(metrics[:transfer_cost])}
35
+ Requests Cost $US: #{format_cost(metrics[:requests_cost])}
36
+ Total Cost $US: #{format_cost(metrics[:total_cost])}
37
+ EOS
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fcoury-db2s3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Felipe Coury
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-08 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-s3
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.1
24
+ version:
25
+ description: db2s3 provides rake tasks for backing up and restoring your DB to S3
26
+ email: contact@felipecoury.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - README
35
+ - Rakefile
36
+ - db2s3.gemspec
37
+ - init.rb
38
+ - lib
39
+ - lib/db2s3.rb
40
+ - lib/db2s3/tasks.rb
41
+ - rails
42
+ - rails/init.rb
43
+ - spec
44
+ - spec/db2s3_spec.rb
45
+ - spec/mysql_drop_schema.sql
46
+ - spec/mysql_schema.sql
47
+ - spec/s3_config.example.rb
48
+ - spec/spec_helper.rb
49
+ - tasks
50
+ - tasks/tasks.rake
51
+ has_rdoc: false
52
+ homepage: http://github.com/fcoury/db2s3
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.2.0
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: db2s3 provides rake tasks for backing up and restoring your DB to S3
77
+ test_files: []
78
+