ariossw-db2s3 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{db2s3}
5
+ s.version = "0.2.5"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Xavier Shay"]
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@rhnh.net}
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/s3_config.rb
28
+ spec/spec_helper.rb
29
+ tasks
30
+ tasks/tasks.rake
31
+ )
32
+ s.has_rdoc = false
33
+ s.homepage = %q{http://github.com/ariossw/db2s3}
34
+ #s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ #s.rubyforge_project = %q{grit}
37
+ s.rubygems_version = %q{1.3.0}
38
+ s.summary = %q{db2s3 provides rake tasks for backing up and restoring your DB to S3}
39
+
40
+ # TODO: WTF does this do
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 2
44
+ end
45
+
46
+ s.add_dependency(%q<aws-s3>, [">= 0.5.1"])
47
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,117 @@
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 += " #{db_credentials[:database]}"
61
+ end
62
+
63
+ def store
64
+ @store ||= S3Store.new
65
+ end
66
+
67
+ def most_recent_dump_file_name
68
+ "most-recent-dump-#{db_credentials[:database]}.txt"
69
+ end
70
+
71
+ def run(command)
72
+ result = system(command)
73
+ raise("error, process exited with status #{$?.exitstatus}") unless result
74
+ end
75
+
76
+ def db_credentials
77
+ ActiveRecord::Base.connection.instance_eval { @config } # Dodgy!
78
+ end
79
+
80
+ class S3Store
81
+ def initialize
82
+ @connected = false
83
+ end
84
+
85
+ def ensure_connected
86
+ return if @connected
87
+ AWS::S3::Base.establish_connection!(DB2S3::Config::S3.slice(:access_key_id, :secret_access_key).merge(:use_ssl => true))
88
+ AWS::S3::Bucket.create(bucket)
89
+ @connected = true
90
+ end
91
+
92
+ def store(file_name, file)
93
+ ensure_connected
94
+ AWS::S3::S3Object.store(file_name, file, bucket)
95
+ end
96
+
97
+ def fetch(file_name)
98
+ ensure_connected
99
+ AWS::S3::S3Object.find(file_name, bucket)
100
+
101
+ file = Tempfile.new("dump")
102
+ open(file.path, 'w') do |f|
103
+ AWS::S3::S3Object.stream(file_name, bucket) do |chunk|
104
+ f.write chunk
105
+ end
106
+ end
107
+ file
108
+ end
109
+
110
+ private
111
+
112
+ def bucket
113
+ DB2S3::Config::S3[:bucket]
114
+ end
115
+ end
116
+
117
+ 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,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,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)
@@ -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,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ariossw-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-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@rhnh.net
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/s3_config.rb
49
+ - spec/spec_helper.rb
50
+ - tasks
51
+ - tasks/tasks.rake
52
+ has_rdoc: false
53
+ homepage: http://github.com/ariossw/db2s3
54
+ licenses:
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.5
76
+ signing_key:
77
+ specification_version: 2
78
+ summary: db2s3 provides rake tasks for backing up and restoring your DB to S3
79
+ test_files: []
80
+