ey_cloud_server 1.4.5 → 1.4.26
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/bin/binary_log_purge +10 -2
- data/bin/ey-snapshots +7 -1
- data/bin/eybackup +13 -1
- data/lib/ey-flex.rb +18 -7
- data/lib/ey-flex/big-brother.rb +2 -2
- data/lib/ey-flex/bucket_minder.rb +46 -160
- data/lib/ey-flex/ec2.rb +17 -0
- data/lib/ey-flex/snapshot_minder.rb +93 -171
- data/lib/ey_backup.rb +84 -48
- data/lib/ey_backup/backend.rb +34 -0
- data/lib/ey_backup/backup_set.rb +70 -63
- data/lib/ey_backup/base.rb +0 -5
- data/lib/ey_backup/cli.rb +26 -6
- data/lib/ey_backup/database.rb +48 -0
- data/lib/ey_backup/dumper.rb +15 -31
- data/lib/ey_backup/engine.rb +7 -17
- data/lib/ey_backup/engines/mysql_engine.rb +24 -16
- data/lib/ey_backup/engines/postgresql_engine.rb +26 -20
- data/lib/ey_backup/loader.rb +13 -33
- data/lib/ey_backup/processors/gpg_encryptor.rb +3 -20
- data/lib/ey_backup/processors/gzipper.rb +0 -29
- data/lib/ey_backup/processors/splitter.rb +22 -34
- data/lib/ey_backup/spawner.rb +7 -13
- data/lib/ey_cloud_server.rb +1 -1
- data/lib/{ey-flex → ey_cloud_server}/version.rb +1 -1
- data/spec/big-brother_spec.rb +12 -0
- data/spec/bucket_minder_spec.rb +113 -0
- data/spec/config-example.yml +11 -0
- data/spec/ey_api_spec.rb +63 -0
- data/spec/ey_backup/backend_spec.rb +12 -0
- data/spec/ey_backup/backup_spec.rb +54 -0
- data/spec/ey_backup/cli_spec.rb +35 -0
- data/spec/ey_backup/mysql_backups_spec.rb +208 -0
- data/spec/ey_backup/postgres_backups_spec.rb +106 -0
- data/spec/ey_backup/spec_helper.rb +5 -0
- data/spec/fakefs_hax.rb +50 -0
- data/spec/gpg.public +0 -0
- data/spec/gpg.sekrit +0 -0
- data/spec/helpers.rb +270 -0
- data/spec/snapshot_minder_spec.rb +68 -0
- data/spec/spec_helper.rb +31 -0
- metadata +286 -53
@@ -1,39 +1,35 @@
|
|
1
1
|
module EY
|
2
2
|
module Backup
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
extend self
|
3
|
+
class Splitter
|
4
|
+
include Spawner
|
5
|
+
include Logging
|
7
6
|
|
8
7
|
CHUNK_SIZE = 1024 * 64
|
9
8
|
MAX_FILE_SIZE = (4.5*1024*1024*1024).to_i #4.5GB
|
10
9
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
def self.dump(file, split_size)
|
11
|
+
split_size ||= MAX_FILE_SIZE
|
12
|
+
new.dump(file, split_size)
|
13
|
+
end
|
15
14
|
|
16
|
-
|
15
|
+
def self.load(files)
|
16
|
+
new.load(files)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
if files.size
|
21
|
-
|
19
|
+
def load(files)
|
20
|
+
if files.size > 1
|
21
|
+
join(files)
|
22
|
+
else
|
23
|
+
files.first
|
22
24
|
end
|
23
|
-
|
24
25
|
end
|
25
26
|
|
26
|
-
def dump(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
end.flatten
|
34
|
-
|
35
|
-
backup.files = output_files
|
36
|
-
backup
|
27
|
+
def dump(file, split_size)
|
28
|
+
if File.size(file) >= split_size
|
29
|
+
split(file, split_size)
|
30
|
+
else
|
31
|
+
[file]
|
32
|
+
end
|
37
33
|
end
|
38
34
|
|
39
35
|
def join(input_files)
|
@@ -46,12 +42,10 @@ module EY
|
|
46
42
|
output << chunk
|
47
43
|
end
|
48
44
|
end
|
49
|
-
|
50
|
-
FileUtils.rm(input)
|
51
45
|
end
|
52
46
|
end
|
53
47
|
|
54
|
-
|
48
|
+
filename
|
55
49
|
end
|
56
50
|
|
57
51
|
def sort(input_files)
|
@@ -62,7 +56,7 @@ module EY
|
|
62
56
|
file[/\.part(\d+)/, 1].to_i
|
63
57
|
end
|
64
58
|
|
65
|
-
def split(file)
|
59
|
+
def split(file, split_size)
|
66
60
|
total_size, part = 0, 0
|
67
61
|
files = []
|
68
62
|
File.open(file, 'r') do |i|
|
@@ -83,14 +77,8 @@ module EY
|
|
83
77
|
end
|
84
78
|
end
|
85
79
|
|
86
|
-
FileUtils.rm(file)
|
87
|
-
|
88
80
|
files
|
89
81
|
end
|
90
|
-
|
91
|
-
def split_size
|
92
|
-
EY::Backup.config.split_size || MAX_FILE_SIZE
|
93
|
-
end
|
94
82
|
end
|
95
83
|
end
|
96
84
|
end
|
data/lib/ey_backup/spawner.rb
CHANGED
@@ -19,21 +19,15 @@ module EY
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def run(command)
|
22
|
-
|
23
|
-
|
22
|
+
unless runs?(command)
|
23
|
+
raise "Failed to run #{command.inspect}"
|
24
|
+
end
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
stdin << chunk
|
31
|
-
end
|
32
|
-
|
33
|
-
stdin.flush
|
34
|
-
stdin.close
|
35
|
-
end
|
36
|
-
end
|
27
|
+
def runs?(command)
|
28
|
+
pid, *_ = Open4.popen4(command)
|
29
|
+
pid, status = Process::waitpid2(pid)
|
30
|
+
status.success?
|
37
31
|
end
|
38
32
|
|
39
33
|
def ioify(stdout, stdin, stderr, &block)
|
data/lib/ey_cloud_server.rb
CHANGED
@@ -0,0 +1,113 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "BucketMinder" do
|
4
|
+
def setup_s3(secret_id, secret_key, region)
|
5
|
+
@raw_s3 = Fog::Storage.new(:provider => 'AWS', :region => region, :aws_access_key_id => secret_id, :aws_secret_access_key => secret_key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def setup_bucket(secret_id, secret_key, bucket_name, region = nil)
|
9
|
+
@bucket_minder = EY::BucketMinder.new(secret_id, secret_key, bucket_name, region)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "in us-east-1" do
|
13
|
+
before do
|
14
|
+
@config = spec_config
|
15
|
+
|
16
|
+
@bucket_name = "ey-backup-#{Digest::SHA1.hexdigest(@config['aws_secret_id'])[0..11]}"
|
17
|
+
@region = 'us-east-1'
|
18
|
+
|
19
|
+
setup_s3(@config['aws_secret_id'], @config['aws_secret_key'], @region)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets up the bucket" do
|
23
|
+
setup_bucket(@config['aws_secret_id'], @config['aws_secret_key'], @bucket_name)
|
24
|
+
|
25
|
+
real_bucket = @raw_s3.directories.get(@bucket_name)
|
26
|
+
real_bucket.key.should == @bucket_name
|
27
|
+
end
|
28
|
+
|
29
|
+
it "uses a default name when the bucket name is not provided" do
|
30
|
+
setup_bucket(@config['aws_secret_id'], @config['aws_secret_key'], nil)
|
31
|
+
@bucket_minder.bucket.key.should == @bucket_name
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "with a working bucket" do
|
35
|
+
before :each do
|
36
|
+
setup_bucket(@config['aws_secret_id'], @config['aws_secret_key'], @bucket_name)
|
37
|
+
|
38
|
+
@test_filename = 'somefile.tst'
|
39
|
+
@file_contents = 'i am file contents'
|
40
|
+
lambda { @bucket_minder.remove_object(@test_filename) }
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can put, get and remove a file's contents to and from bucket" do
|
44
|
+
@bucket_minder.list(@test_filename).should be_empty
|
45
|
+
@bucket_minder.put(@test_filename, @file_contents)
|
46
|
+
|
47
|
+
remote_file = @bucket_minder.list(@test_filename).first
|
48
|
+
remote_file[:name].should == @test_filename
|
49
|
+
|
50
|
+
@bucket_minder.stream(@test_filename) do |chunk, remaining_size, total_size|
|
51
|
+
chunk.should == @file_contents
|
52
|
+
end
|
53
|
+
|
54
|
+
@bucket_minder.remove_object(@test_filename)
|
55
|
+
@bucket_minder.list(@test_filename).should be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when deleting backups" do
|
59
|
+
it "does not load the backup files into memory" do
|
60
|
+
@bucket_minder.files.should_not_receive(:get)
|
61
|
+
|
62
|
+
@bucket_minder.put('test_bucket_minder_file_1', 'foo')
|
63
|
+
@bucket_minder.remove_object('test_bucket_minder_file_1')
|
64
|
+
|
65
|
+
@bucket_minder.list('test_bucket_minder_file_1').should be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "bucket minder merge operations" do
|
70
|
+
it "returns the number of files in a bucket" do
|
71
|
+
@bucket_minder.put('test_bucket_minder_file_1', 'foo')
|
72
|
+
@bucket_minder.put('test_bucket_minder_file_2', 'foo')
|
73
|
+
|
74
|
+
bucketfiles = @bucket_minder.list('test_bucket_minder_file')
|
75
|
+
bucketfiles.length.should == 2
|
76
|
+
bucketfiles.detect { |file| file[:name] == 'test_bucket_minder_file_1' }.should_not be_empty
|
77
|
+
bucketfiles.detect { |file| file[:name] == 'test_bucket_minder_file_2' }.should_not be_empty
|
78
|
+
end
|
79
|
+
|
80
|
+
it "collects file parts" do
|
81
|
+
@bucket_minder.put('test_bucket_minder_file_1', 'foo')
|
82
|
+
@bucket_minder.put('test_bucket_minder_file_2', 'foo')
|
83
|
+
@bucket_minder.put('test_bucket_minder_multi_file.part1', 'foo')
|
84
|
+
@bucket_minder.put('test_bucket_minder_multi_file.part2', 'foo')
|
85
|
+
|
86
|
+
bucketfiles = @bucket_minder.list('test_bucket_minder')
|
87
|
+
bucketfiles.length.should == 3
|
88
|
+
bucketfiles.detect { |file| file[:name] == 'test_bucket_minder_file_1' }.should_not be_empty
|
89
|
+
bucketfiles.detect { |file| file[:name] == 'test_bucket_minder_file_2' }.should_not be_empty
|
90
|
+
|
91
|
+
multi_part_file = bucketfiles.detect { |file| file[:name] == 'test_bucket_minder_multi_file' }
|
92
|
+
multi_part_file.should_not be_empty
|
93
|
+
multi_part_file[:keys].should == ["test_bucket_minder_multi_file.part1", "test_bucket_minder_multi_file.part2"]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "outside of us-east-1" do
|
100
|
+
before do
|
101
|
+
@config = spec_config
|
102
|
+
@bucket_name = "ey-backup-non-us-#{Digest::SHA1.hexdigest(@config['aws_secret_id'])[0..11]}"
|
103
|
+
@region = 'ap-northeast-1'
|
104
|
+
|
105
|
+
setup_s3(@config['aws_secret_id'], @config['aws_secret_key'], @region)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "creates the bucket in the proper region" do
|
109
|
+
setup_bucket(@config['aws_secret_id'], @config['aws_secret_key'], @bucket_name, @region)
|
110
|
+
@bucket_minder.bucket.location.should == @region
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/spec/ey_api_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "EyApi" do
|
4
|
+
describe "#get_json" do
|
5
|
+
before(:each) do
|
6
|
+
@api = Object.new
|
7
|
+
@api.instance_variable_set("@rest", RestClient::Resource.new('http://example.org'))
|
8
|
+
@api.instance_variable_set("@env", @mock_environment_name)
|
9
|
+
@api.instance_variable_set("@keys", {})
|
10
|
+
@api.extend(EyApi)
|
11
|
+
|
12
|
+
@api.stub!(:sleep)
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:each) do
|
16
|
+
FakeWeb.clean_registry
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "when the JSON is available" do
|
20
|
+
before(:each) do
|
21
|
+
FakeWeb.register_uri(:post, "http://example.org/api/json_for_instance", :body => JSON.generate({:valid => "json"}))
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should pass through exceptions that it receives" do
|
25
|
+
JSON.stub!(:parse).and_raise(Exception)
|
26
|
+
lambda {
|
27
|
+
@api.get_json("i-decafbad")
|
28
|
+
}.should raise_error
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return a valid JSON structure" do
|
32
|
+
presumably_valid_json = @api.get_json("i-feedface")
|
33
|
+
lambda {
|
34
|
+
JSON[presumably_valid_json]
|
35
|
+
}.should_not raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "when the JSON is not yet available" do
|
40
|
+
|
41
|
+
it "should retry until the 503 goes away" do
|
42
|
+
FakeWeb.register_uri(:post, "http://example.org/api/json_for_instance", [{
|
43
|
+
:status => ["503", "Service Unavailable"],
|
44
|
+
:body => "{}",
|
45
|
+
}, {
|
46
|
+
:status => ["503", "Service Unavailable"],
|
47
|
+
:body => "{}",
|
48
|
+
}, {
|
49
|
+
:status => ["200", "OK"],
|
50
|
+
:body => JSON.generate({:valid => 'json'}),
|
51
|
+
}]
|
52
|
+
)
|
53
|
+
|
54
|
+
lambda {
|
55
|
+
JSON[@api.get_json("i-robot")]
|
56
|
+
}.should_not raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe EY::Backup do
|
4
|
+
before do
|
5
|
+
@backend = EY::Backup::Backend.new( spec_config['aws_secret_id'], spec_config['aws_secret_key'], 'ap-northeast-1', 'test-backup-bucket')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "uses correct region" do
|
9
|
+
@backend.bucket_minder.bucket.location.should == 'ap-northeast-1'
|
10
|
+
@backend.bucket_minder.bucket_name == "test-backup-bucket"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe EY::Backup do
|
4
|
+
before(:each) do
|
5
|
+
@db_name = create_mysql_database('first')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#list" do
|
9
|
+
|
10
|
+
it 'prints the database to be listed first' do
|
11
|
+
EY::Backup.run([ "-c", backup_config_file ])
|
12
|
+
|
13
|
+
reset_logger
|
14
|
+
|
15
|
+
EY::Backup.run(["-c", backup_config_file, "-l", @db_name])
|
16
|
+
|
17
|
+
stdout.should =~ /\AListing database backups for #{@db_name}$/
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'conforms to the /^\d:#{db_name} #{db_name}\.#{timestamp}\.#{ext}$/ line output' do
|
21
|
+
EY::Backup.run([ "-c", backup_config_file ])
|
22
|
+
|
23
|
+
reset_logger
|
24
|
+
|
25
|
+
EY::Backup.run(["-c", backup_config_file, "-l", @db_name])
|
26
|
+
|
27
|
+
stdout.should =~ /^\d+:[a-zA-Z0-9_\-]+\s+[a-zA-Z0-9_\-]+\.[T0-9\-]+\.[.a-zA-Z0-9]+$/
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#list_all" do
|
32
|
+
it 'prints all the database names' do
|
33
|
+
@db_name2 = create_mysql_database('second')
|
34
|
+
|
35
|
+
EY::Backup.run([ "-c", backup_config_file ])
|
36
|
+
|
37
|
+
reset_logger
|
38
|
+
|
39
|
+
EY::Backup.run(["-c", backup_config_file, '-l', ''])
|
40
|
+
|
41
|
+
stdout.should =~ /^\d+:#{@db_name}/
|
42
|
+
stdout.should =~ /^\d+:#{@db_name2}/
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "cleanup" do
|
47
|
+
it "removes the last backup" do
|
48
|
+
set_keep 5
|
49
|
+
6.times do
|
50
|
+
EY::Backup.run([ "-c", backup_config_file ])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "MySQL Backups" do
|
4
|
+
before(:each) do
|
5
|
+
@db_name = create_mysql_database('first')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "--quiet" do
|
9
|
+
it "does not print anything to STDOUT" do
|
10
|
+
capturing_stdio do
|
11
|
+
reset_logger
|
12
|
+
EY::Backup.run(["-c", backup_config_file, '--quiet'])
|
13
|
+
end
|
14
|
+
|
15
|
+
last_stdout.should be_empty
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'does print to STDOUT without the flag' do
|
19
|
+
EY::Backup.logger = nil
|
20
|
+
|
21
|
+
result = capturing_stdio do
|
22
|
+
EY::Backup.run(["-c", backup_config_file ])
|
23
|
+
end
|
24
|
+
|
25
|
+
last_stdout.should_not be_empty
|
26
|
+
EY::Backup.logger = nil
|
27
|
+
|
28
|
+
result = capturing_stdio do
|
29
|
+
EY::Backup.run(["-c", backup_config_file ])
|
30
|
+
end
|
31
|
+
|
32
|
+
last_stdout.should_not be_empty
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|