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 +2 -1
- data/lib/dumper.rb +1 -0
- data/lib/dumper/agent.rb +2 -2
- data/lib/dumper/database/base.rb +10 -3
- data/lib/dumper/database/mongodb.rb +3 -7
- data/lib/dumper/database/mysql.rb +1 -1
- data/lib/dumper/database/redis.rb +37 -0
- data/lib/dumper/job.rb +19 -28
- data/lib/dumper/stack.rb +1 -0
- data/lib/dumper/version.rb +1 -1
- metadata +3 -2
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
|
14
|
+
Support for PostgreSQL and Redis are coming soon.
|
14
15
|
|
15
16
|
## Installation
|
16
17
|
|
data/lib/dumper.rb
CHANGED
data/lib/dumper/agent.rb
CHANGED
@@ -20,7 +20,7 @@ module Dumper
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def initialize(options = {})
|
23
|
-
log '
|
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 })
|
data/lib/dumper/database/base.rb
CHANGED
@@ -3,17 +3,20 @@ module Dumper
|
|
3
3
|
class Base
|
4
4
|
include Dumper::Utility::ObjectFinder
|
5
5
|
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :tmpdir, :filename
|
7
7
|
|
8
|
-
def initialize(stack = nil
|
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
|
-
"#{
|
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='#{
|
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
|
-
"#{
|
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
|
data/lib/dumper/job.rb
CHANGED
@@ -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
|
32
|
+
@database = Dumper::Database::MongoDB.new(@stack)
|
33
|
+
when 'redis'
|
34
|
+
@database = Dumper::Database::Redis.new(@stack)
|
33
35
|
else
|
34
|
-
|
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
|
-
|
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
|
-
|
48
|
-
@database.
|
47
|
+
@database.tmpdir = Dir.mktmpdir
|
48
|
+
@database.filename = json[:backup][:filename]
|
49
49
|
log 'starting backup...'
|
50
|
-
log "
|
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
|
58
|
+
@database.finalize
|
69
59
|
@agent.api_request('backup/fail', :params => { :backup_id => backup_id, :code => 'dump_error', :message => $!.to_s })
|
70
|
-
|
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]
|
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
|
-
|
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
|
80
|
+
def upload_to_s3(url, fields)
|
91
81
|
require 'net/http/post/multipart'
|
92
|
-
fields['file'] = UploadIO.new(
|
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
|
-
|
108
|
-
|
109
|
-
|
97
|
+
def abort_with(text)
|
98
|
+
log text
|
99
|
+
@database.try(:finalize)
|
100
|
+
exit!
|
110
101
|
end
|
111
102
|
end
|
112
103
|
end
|
data/lib/dumper/stack.rb
CHANGED
data/lib/dumper/version.rb
CHANGED
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.
|
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-
|
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
|