db2s3 0.2.5

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/.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