dumper 0.1.1 → 0.2.0

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.md CHANGED
@@ -9,8 +9,9 @@ Dumper is a backup management system that offers a whole new way to take daily b
9
9
  * Ruby 1.8.7 , Ruby 1.9.2 or later
10
10
  * Rails 3.0 or later
11
11
  * MySQL with ActiveRecord
12
+ * MongoDB with mongo gem (including Mongoid 2.x)
12
13
 
13
- Support for PostgreSQL, MongoDB and Redis are coming soon.
14
+ Support for PostgreSQL and Redis are coming soon.
14
15
 
15
16
  ## Installation
16
17
 
@@ -13,5 +13,6 @@ module Dumper
13
13
  autoload :Base, 'dumper/database/base'
14
14
  autoload :MySQL, 'dumper/database/mysql'
15
15
  autoload :MongoDB, 'dumper/database/mongodb'
16
+ autoload :Redis, 'dumper/database/redis'
16
17
  end
17
18
  end
@@ -20,7 +20,7 @@ module Dumper
20
20
  end
21
21
 
22
22
  def initialize(options = {})
23
- log '**** Dumper requires :app_key! ****' if options[:app_key].blank?
23
+ log 'app_key is missing' if options[:app_key].blank?
24
24
 
25
25
  @stack = Dumper::Stack.new
26
26
  @api_base = options[:api_base] || 'http://dumper.io'
@@ -51,7 +51,7 @@ module Dumper
51
51
 
52
52
  @token = json[:token]
53
53
  log "agent started as #{@token ? 'primary' : 'secondary'}"
54
- sleep 1.hour unless @token
54
+ sleep 1.hour + rand(10) unless @token
55
55
 
56
56
  loop do
57
57
  json = api_request('agent/poll', :params => { :token => @token })
@@ -3,17 +3,20 @@ module Dumper
3
3
  class Base
4
4
  include Dumper::Utility::ObjectFinder
5
5
 
6
- attr_accessor :tempfile
6
+ attr_accessor :tmpdir, :filename
7
7
 
8
- def initialize(stack = nil, options = {})
8
+ def initialize(stack = nil)
9
9
  @stack = stack
10
- @options = options
11
10
  end
12
11
 
13
12
  def file_ext
14
13
  self.class::FILE_EXT
15
14
  end
16
15
 
16
+ def dump_path
17
+ "#{tmpdir}/#{filename}"
18
+ end
19
+
17
20
  def dump_tool_path
18
21
  tool = self.class::DUMP_TOOL
19
22
  path = `which #{tool}`.chomp
@@ -26,6 +29,10 @@ module Dumper
26
29
  end
27
30
  path
28
31
  end
32
+
33
+ def finalize
34
+ FileUtils.remove_entry_secure tmpdir if File.exist? tmpdir
35
+ end
29
36
  end
30
37
  end
31
38
  end
@@ -5,7 +5,7 @@ module Dumper
5
5
  FILE_EXT = 'tar.gz'
6
6
 
7
7
  def command
8
- "#{@stack.configs[:mongodb][:dump_tool]} #{connection_options} #{additional_options} && cd #{@options[:tmpdir]} && tar -czf #{@tempfile.path} ."
8
+ "cd #{tmpdir} && #{dump_tool_path} #{connection_options} #{additional_options} && tar -czf #{filename} ."
9
9
  end
10
10
 
11
11
  def connection_options
@@ -16,16 +16,12 @@ module Dumper
16
16
  end
17
17
 
18
18
  def additional_options
19
- "--out='#{@options[:tmpdir]}'"
20
- end
21
-
22
- def finalize
23
- FileUtils.remove_entry_secure @options[:tmpdir] if File.exist? @options[:tmpdir]
19
+ "--out='#{tmpdir}'"
24
20
  end
25
21
 
26
22
  def config_for(rails_env=nil)
27
23
  return unless defined?(Mongo::DB) &&
28
- mongo = find_instance_in_object_space(Mongo::DB)
24
+ (mongo = find_instance_in_object_space(Mongo::DB))
29
25
 
30
26
  {
31
27
  :host => mongo.connection.host,
@@ -5,7 +5,7 @@ module Dumper
5
5
  FILE_EXT = 'sql.gz'
6
6
 
7
7
  def command
8
- "#{@stack.configs[:mysql][:dump_tool]} #{connection_options} #{additional_options} #{@stack.configs[:mysql][:database]} | gzip > #{@tempfile.path}"
8
+ "cd #{tmpdir} && #{dump_tool_path} #{connection_options} #{additional_options} #{@stack.configs[:mysql][:database]} | gzip > #{filename}"
9
9
  end
10
10
 
11
11
  def connection_options
@@ -0,0 +1,37 @@
1
+ module Dumper
2
+ module Database
3
+ class Redis < Base
4
+ DUMP_TOOL = 'redis-cli'
5
+ FILE_EXT = 'rdb.gz'
6
+
7
+ def command
8
+ uncompressed = filename.sub('.gz','')
9
+ "cd #{tmpdir} && cp #{@stack.configs[:redis][:dbpath]} #{uncompressed} && gzip #{uncompressed}"
10
+ end
11
+
12
+ def config_for(rails_env=nil)
13
+ return unless defined?(::Redis) &&
14
+ (main_thread_redis = find_instance_in_object_space(::Redis))
15
+
16
+ client = main_thread_redis.client
17
+
18
+ # New connection for the agent thread
19
+ redis = ::Redis.new(:host => client.host, :port => client.port, :password => client.password, :db => client.db)
20
+ dir = redis.config(:get, :dir)['dir']
21
+ dbfilename = redis.config(:get, :dbfilename)['dbfilename']
22
+ dbpath = "#{dir}/#{dbfilename}"
23
+
24
+ return unless File.exist?(dbpath) # Redis must run on the back up node
25
+
26
+ {
27
+ :host => client.host,
28
+ :port => client.port,
29
+ :password => client.password,
30
+ :database => client.db,
31
+ :dump_tool => dump_tool_path,
32
+ :dbpath => dbpath
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -29,45 +29,35 @@ module Dumper
29
29
  when 'mysql'
30
30
  @database = Dumper::Database::MySQL.new(@stack)
31
31
  when 'mongodb'
32
- @database = Dumper::Database::MongoDB.new(@stack, :tmpdir => Dir.mktmpdir)
32
+ @database = Dumper::Database::MongoDB.new(@stack)
33
+ when 'redis'
34
+ @database = Dumper::Database::Redis.new(@stack)
33
35
  else
34
- log "invalid server type: #{server[:type]}"
35
- exit!
36
+ abort_with "invalid server type: #{server[:type]}"
36
37
  end
37
38
 
38
39
  # Prepare
39
40
  json = @agent.api_request('backup/prepare', :params => { :server_id => server[:id], :manual => server[:manual].to_s, :ext => @database.file_ext })
40
- return unless json[:status] == 'ok'
41
+ abort_with('backup/prepare failed') unless json[:status] == 'ok'
41
42
 
42
43
  backup_id = json[:backup][:id]
43
- filename = json[:backup][:filename]
44
44
 
45
45
  # Dump
46
46
  start_at = Time.now
47
- tempfile = ruby19? ? Tempfile.new(filename, encoding: 'ascii-8bit') : Tempfile.new(filename)
48
- @database.tempfile = tempfile
47
+ @database.tmpdir = Dir.mktmpdir
48
+ @database.filename = json[:backup][:filename]
49
49
  log 'starting backup...'
50
- log "tempfile = #{tempfile.path}"
50
+ log "tmpdir = #{@database.tmpdir}, filename = #{@database.filename}"
51
51
  log "command = #{@database.command}"
52
52
 
53
53
  begin
54
54
  pid, stdin, stdout, stderr = popen4(@database.command)
55
55
  stdin.close
56
- # # Reuse buffer: http://www.ruby-forum.com/topic/134164
57
- # buffer_size = 1.megabytes
58
- # buffer = "\x00" * buffer_size # fixed-size malloc optimization
59
- # while stdout.read(buffer_size, buffer)
60
- # tempfile.write buffer
61
- # if tempfile.size > MAX_FILESIZE
62
- # raise 'Max filesize exceeded.'
63
- # end
64
- # end
65
- # tempfile.flush
66
56
  rescue
67
57
  Process.kill(:INT, pid) rescue SystemCallError
68
- @database.finalize if @database.respond_to?(:finalize)
58
+ @database.finalize
69
59
  @agent.api_request('backup/fail', :params => { :backup_id => backup_id, :code => 'dump_error', :message => $!.to_s })
70
- exit!
60
+ abort_with("dump error: #{$!}")
71
61
  ensure
72
62
  [stdin, stdout, stderr].each{|io| io.close unless io.closed? }
73
63
  Process.waitpid(pid)
@@ -75,21 +65,21 @@ module Dumper
75
65
 
76
66
  dump_duration = Time.now - start_at
77
67
  log "dump_duration = #{dump_duration}"
68
+ abort_with('max filesize exceeded') if File.size(@database.dump_path) > MAX_FILESIZE
78
69
 
79
- upload_to_s3(json[:url], json[:fields], tempfile.path, filename)
70
+ upload_to_s3(json[:url], json[:fields])
80
71
 
81
72
  json = @agent.api_request('backup/commit', :params => { :backup_id => backup_id, :dump_duration => dump_duration.to_i })
82
73
  rescue
83
74
  log_last_error
84
75
  ensure
85
- tempfile.close(true)
86
- @database.finalize if @database.respond_to?(:finalize)
76
+ @database.finalize
87
77
  end
88
78
 
89
79
  # Upload
90
- def upload_to_s3(url, fields, local_file, remote_file)
80
+ def upload_to_s3(url, fields)
91
81
  require 'net/http/post/multipart'
92
- fields['file'] = UploadIO.new(local_file, 'application/octet-stream', remote_file)
82
+ fields['file'] = UploadIO.new(@database.dump_path, 'application/octet-stream', @database.filename)
93
83
  uri = URI.parse(url)
94
84
  request = Net::HTTP::Post::Multipart.new uri.path, fields
95
85
  http = Net::HTTP.new(uri.host, uri.port)
@@ -104,9 +94,10 @@ module Dumper
104
94
  log_last_error
105
95
  end
106
96
 
107
- # Helper
108
- def ruby19?
109
- RUBY_VERSION >= '1.9.0'
97
+ def abort_with(text)
98
+ log text
99
+ @database.try(:finalize)
100
+ exit!
110
101
  end
111
102
  end
112
103
  end
@@ -5,6 +5,7 @@ module Dumper
5
5
  DATABASES = {
6
6
  :mysql => Dumper::Database::MySQL,
7
7
  :mongodb => Dumper::Database::MongoDB,
8
+ :redis => Dumper::Database::Redis,
8
9
  }
9
10
 
10
11
  attr_accessor :rails_env, :dispatcher, :framework, :rackup, :configs
@@ -1,3 +1,3 @@
1
1
  module Dumper
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dumper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-31 00:00:00.000000000 Z
12
+ date: 2012-06-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -114,6 +114,7 @@ files:
114
114
  - lib/dumper/database/base.rb
115
115
  - lib/dumper/database/mongodb.rb
116
116
  - lib/dumper/database/mysql.rb
117
+ - lib/dumper/database/redis.rb
117
118
  - lib/dumper/dependency.rb
118
119
  - lib/dumper/job.rb
119
120
  - lib/dumper/stack.rb