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.
Files changed (42) hide show
  1. data/bin/binary_log_purge +10 -2
  2. data/bin/ey-snapshots +7 -1
  3. data/bin/eybackup +13 -1
  4. data/lib/ey-flex.rb +18 -7
  5. data/lib/ey-flex/big-brother.rb +2 -2
  6. data/lib/ey-flex/bucket_minder.rb +46 -160
  7. data/lib/ey-flex/ec2.rb +17 -0
  8. data/lib/ey-flex/snapshot_minder.rb +93 -171
  9. data/lib/ey_backup.rb +84 -48
  10. data/lib/ey_backup/backend.rb +34 -0
  11. data/lib/ey_backup/backup_set.rb +70 -63
  12. data/lib/ey_backup/base.rb +0 -5
  13. data/lib/ey_backup/cli.rb +26 -6
  14. data/lib/ey_backup/database.rb +48 -0
  15. data/lib/ey_backup/dumper.rb +15 -31
  16. data/lib/ey_backup/engine.rb +7 -17
  17. data/lib/ey_backup/engines/mysql_engine.rb +24 -16
  18. data/lib/ey_backup/engines/postgresql_engine.rb +26 -20
  19. data/lib/ey_backup/loader.rb +13 -33
  20. data/lib/ey_backup/processors/gpg_encryptor.rb +3 -20
  21. data/lib/ey_backup/processors/gzipper.rb +0 -29
  22. data/lib/ey_backup/processors/splitter.rb +22 -34
  23. data/lib/ey_backup/spawner.rb +7 -13
  24. data/lib/ey_cloud_server.rb +1 -1
  25. data/lib/{ey-flex → ey_cloud_server}/version.rb +1 -1
  26. data/spec/big-brother_spec.rb +12 -0
  27. data/spec/bucket_minder_spec.rb +113 -0
  28. data/spec/config-example.yml +11 -0
  29. data/spec/ey_api_spec.rb +63 -0
  30. data/spec/ey_backup/backend_spec.rb +12 -0
  31. data/spec/ey_backup/backup_spec.rb +54 -0
  32. data/spec/ey_backup/cli_spec.rb +35 -0
  33. data/spec/ey_backup/mysql_backups_spec.rb +208 -0
  34. data/spec/ey_backup/postgres_backups_spec.rb +106 -0
  35. data/spec/ey_backup/spec_helper.rb +5 -0
  36. data/spec/fakefs_hax.rb +50 -0
  37. data/spec/gpg.public +0 -0
  38. data/spec/gpg.sekrit +0 -0
  39. data/spec/helpers.rb +270 -0
  40. data/spec/snapshot_minder_spec.rb +68 -0
  41. data/spec/spec_helper.rb +31 -0
  42. metadata +286 -53
@@ -0,0 +1,48 @@
1
+ module EY
2
+ module Backup
3
+ class Database
4
+ def initialize(engine, base_path, keep, backend, environment, name)
5
+ if name.nil? || name.empty?
6
+ raise ArgumentError, "database name is blank"
7
+ end
8
+ @engine = engine
9
+ @base_path = base_path
10
+ @keep = keep
11
+ @backend = backend
12
+ @environment = environment
13
+ @name = name
14
+ end
15
+ attr_reader :name, :engine, :environment, :keep, :base_path
16
+
17
+ def bucket_minder
18
+ @backend.bucket_minder
19
+ end
20
+
21
+ def backups
22
+ s3_objects = bucket_minder.list("#{@environment}.#{@name}").select { |o| o[:name] =~ @engine.suffix }
23
+ s3_objects.map {|o| BackupSet.new(self, o[:name], nil, o[:keys]) }.sort
24
+ end
25
+
26
+ def dump
27
+ FileUtils.mkdir_p(@base_path)
28
+
29
+ basename = generate_basename
30
+ file = @engine.dump(@name, basename)
31
+
32
+ BackupSet.from(self, basename, file)
33
+ end
34
+
35
+ def start_upload(filenames)
36
+ @backend.start_upload(filenames, @environment, @name)
37
+ end
38
+
39
+ def generate_basename
40
+ "#{@base_path}/#{@name}.#{timestamp}"
41
+ end
42
+
43
+ def timestamp
44
+ Time.now.strftime("%Y-%m-%dT%H-%M-%S")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,47 +1,31 @@
1
1
  module EY
2
2
  module Backup
3
3
  class Dumper < Base
4
- attr_accessor :database
4
+ include Logging
5
5
 
6
- def_delegators EY::Backup, :engine, :minder, :base_path
6
+ attr_reader :database
7
7
 
8
- def self.run(options = {})
9
- if database = options[:db]
10
- new(database, options).run
11
- else
12
- options[:databases].each do |database|
13
- new(database, options).run
14
- end
8
+ def self.run(databases, split_size)
9
+ databases.each do |database|
10
+ new(database).run(split_size)
15
11
  end
16
12
  end
17
13
 
18
- def initialize(database, options = {})
19
- super(options)
20
- @database = database
14
+ def initialize(database)
15
+ @database = database
21
16
  end
22
17
 
23
- def run
24
- info "doing database: #{database}"
18
+ def run(split_size)
19
+ info "Doing database: #{@database.name}"
25
20
 
26
- FileUtils.mkdir_p('/mnt/tmp')
21
+ backup_set = @database.dump
22
+ backup_set.split!(split_size)
23
+ backup_set.upload!
27
24
 
28
- backup = engine.dump(database, base_name)
29
-
30
- backup.upload!(database)
31
-
32
- info "Successful backup: #{database}.#{backup.normalized_name}"
33
- BackupSet.cleanup(database, EY::Backup.config.keep)
34
- end
35
-
36
-
37
- def base_name
38
- "#{base_path}/#{database}.#{timestamp}"
39
- end
40
-
41
- def timestamp
42
- @timestamp ||= Time.now.strftime("%Y-%m-%dT%H:%M:%S").gsub(/:/, '-')
25
+ info "Successful backup: #{@database.name}.#{backup_set.normalized_name}"
26
+ backup_set.cleanup
27
+ backup_set.rm!
43
28
  end
44
-
45
29
  end
46
30
  end
47
31
  end
@@ -3,7 +3,7 @@ module EY
3
3
  class Engine < Base
4
4
  include Spawner
5
5
 
6
- attr_accessor :username, :password
6
+ attr_reader :username, :password, :host
7
7
 
8
8
  def self.label
9
9
  @label
@@ -26,25 +26,15 @@ module EY
26
26
  EY::Backup.logger.fatal("Unknown database engine: #{label}")
27
27
  end
28
28
 
29
- def initialize(username, password)
30
- @username, @password = username, password
31
- end
32
-
33
- def post_dump_process(backup)
34
- processors.inject(backup) do |backup, processor|
35
- processor.dump(backup)
36
- end
37
- end
38
-
39
- def pre_load_process(backup)
40
- processors.reverse.inject(backup) do |backup, processor|
41
- processor.load(backup)
42
- end
29
+ def initialize(username, password, host, key_id)
30
+ @username, @password, @host = username, password, host
31
+ @key_id = key_id
43
32
  end
33
+ attr_reader :key_id
44
34
 
45
35
  def gpg?
46
- not EY::Backup.config.key_id.nil? &&
47
- EY::Backup.config.key_id.blank?
36
+ not @key_id.nil? &&
37
+ @key_id.blank?
48
38
  end
49
39
  end
50
40
  end
@@ -3,13 +3,13 @@ module EY
3
3
  class MysqlEngine < Engine
4
4
  register 'mysql'
5
5
 
6
- def dump(database, basename)
6
+ def dump(database_name, basename)
7
7
  file = basename + '.sql'
8
8
 
9
- command = "mysqldump #{username_option} #{password_option} #{single_transaction_option(database)} #{database}"
9
+ command = "mysqldump #{username_option} #{host_option} #{password_option} #{single_transaction_option(database_name)} #{routines_option} #{database_name}"
10
10
 
11
11
  if gpg?
12
- command << " | " << GPGEncryptor.command
12
+ command << " | " << GPGEncryptor.command_for(key_id)
13
13
  file << GPGEncryptor.extension
14
14
  else
15
15
  command << " | " << GZipper.gzip
@@ -20,44 +20,52 @@ module EY
20
20
 
21
21
  run(command)
22
22
 
23
- Splitter.dump(BackupSet.from(database, file))
23
+ file
24
24
  end
25
25
 
26
- def load(database, backup)
27
- backup = Splitter.load(backup)
28
-
29
- command = "cat #{backup.files.first}"
26
+ def load(database_name, file)
27
+ command = "cat #{file}"
30
28
 
31
29
  if gpg?
32
- command << " | " << GPGEncryptor.load_command
30
+ raise "Cannot load a GPG backup"
33
31
  else
34
32
  command << " | " << GZipper.gunzip
35
33
  end
36
34
 
37
- command << " | mysql #{username_option} #{password_option} #{database}"
35
+ command << " | mysql #{username_option} #{host_option} #{password_option} #{database_name}"
38
36
 
39
37
  run(command)
40
38
  end
41
39
 
40
+ def routines_option
41
+ "--routines"
42
+ end
43
+
42
44
  def password_option
43
45
  "-p'#{password}'" unless password.nil? || password.empty?
44
46
  end
45
47
 
48
+ def host_option
49
+ "-h#{host}"
50
+ end
51
+
46
52
  def username_option
47
53
  "-u#{username}"
48
54
  end
49
55
 
50
- def single_transaction_option(database)
51
- '--single-transaction' unless db_has_myisam?(database)
56
+ def single_transaction_option(database_name)
57
+ '--single-transaction' unless db_has_myisam?(database_name)
52
58
  end
53
59
 
54
- def db_has_myisam?(database)
55
- query, stdout = "SELECT 1 FROM information_schema.tables WHERE table_schema='#{database}' AND engine='MyISAM' LIMIT 1;", StringIO.new
56
- spawn(%Q{mysql #{username_option} #{password_option} -N -e"#{query}"}, stdout)
60
+ def db_has_myisam?(database_name)
61
+ query, stdout = "SELECT 1 FROM information_schema.tables WHERE table_schema='#{database_name}' AND engine='MyISAM' LIMIT 1;", StringIO.new
62
+ spawn(%Q{mysql #{username_option} #{password_option} #{host_option} -N -e"#{query}"}, stdout)
57
63
  stdout.string.strip == '1'
58
64
  end
59
65
 
60
-
66
+ def suffix
67
+ /sql\.(gz|gpz)$/
68
+ end
61
69
  end
62
70
  end
63
71
  end
@@ -3,13 +3,13 @@ module EY
3
3
  class Postgresql < Engine
4
4
  register 'postgresql'
5
5
 
6
- def dump(database, basename)
6
+ def dump(database_name, basename)
7
7
  file = basename + '.pgz'
8
8
 
9
- command = "PGPASSWORD='#{password}' pg_dump -h localhost --format=c --no-owner --no-privileges -U#{username} #{database}"
9
+ command = "PGPASSWORD='#{password}' pg_dump -h #{host} --format=c --no-owner --no-privileges -U#{username} #{database_name}"
10
10
 
11
11
  if gpg?
12
- command << " | " << GPGEncryptor.command
12
+ command << " | " << GPGEncryptor.command_for(key_id)
13
13
  file << GPGEncryptor.extension
14
14
  end
15
15
 
@@ -17,40 +17,46 @@ module EY
17
17
 
18
18
  run(command)
19
19
 
20
- Splitter.dump(BackupSet.from(database, file))
20
+ file
21
21
  end
22
22
 
23
- def load(database, backup)
24
- backup = Splitter.load(backup)
25
-
26
- cycle_database(database) if database_exists?(database)
23
+ def load(database_name, file)
24
+ if database_exists?(database_name)
25
+ cycle_database(database_name)
26
+ else
27
+ create_database(database_name)
28
+ end
27
29
 
28
- command = "cat #{backup.files.first}"
30
+ command = "cat #{file}"
29
31
 
30
32
  if gpg?
31
- command << " | " << GPGEncryptor.load_command
33
+ raise "Cannot load a GPG backup"
32
34
  end
33
35
 
34
- command << " | PGPASSWORD='#{password}' pg_restore -h localhost --format=c --ignore-version -U#{username} -d #{database}"
36
+ command << " | PGPASSWORD='#{password}' pg_restore -h #{host} --format=c --ignore-version -U#{username} -d #{database_name}"
35
37
 
36
38
  run(command)
37
39
  end
38
40
 
39
- def database_exists?(database)
40
- spawn("PGPASSWORD='#{password}' psql -l | grep '#{database}'").success?
41
+ def database_exists?(database_name)
42
+ runs?("PGPASSWORD='#{password}' psql -l -h #{host} | grep '#{database_name}'")
43
+ end
44
+
45
+ def drop_database(database_name)
46
+ spawn("PGPASSWORD='#{password}' dropdb -h #{host} -U#{username} #{database_name}")
41
47
  end
42
48
 
43
- def drop_database(database)
44
- spawn("PGPASSWORD='#{password}' dropdb -h localhost -U#{username} #{database}")
49
+ def create_database(database_name)
50
+ spawn("PGPASSWORD='#{password}' createdb -U#{username} -h #{host} #{database_name}")
45
51
  end
46
52
 
47
- def create_database(database)
48
- spawn("PGPASSWORD='#{password}' createdb -U#{username} -h localhost #{database}")
53
+ def cycle_database(database_name)
54
+ drop_database(database_name)
55
+ create_database(database_name)
49
56
  end
50
57
 
51
- def cycle_database(database)
52
- drop_database(database)
53
- create_database(database)
58
+ def suffix
59
+ /pgz(\.gpz)?$/
54
60
  end
55
61
  end
56
62
  end
@@ -3,52 +3,32 @@ module EY
3
3
  class Loader < Base
4
4
  include Logging
5
5
 
6
- attr_accessor :database, :index
7
-
8
- def_delegators EY::Backup, :engine
9
-
10
- def self.run(options = {})
11
- index, database = options[:index].split(':')
12
- new(database, index, options).run
6
+ def self.run(database, index)
7
+ new(database, index).run
13
8
  end
14
9
 
15
- def self.download(options = {})
16
- index, database = options[:index].split(':')
17
- new(database, index, options).download_raw
10
+ def self.download(database, index)
11
+ new(database, index).download
18
12
  end
19
13
 
20
- def initialize(database, index, options = {})
21
- super(options)
14
+ def initialize(database, index)
22
15
  @database, @index = database, index
23
16
  end
24
17
 
25
18
  def run
19
+ info "Restoring #{@database.name}"
26
20
  backup = download
27
- info "Restoring #{database}"
28
- engine.load(database, backup)
29
- backup.rm!
21
+ backup.load!
22
+ backup.remove_joined_file!
30
23
  end
31
24
 
32
25
  def download
33
- BackupSet.download(database, index)
34
- end
35
-
36
- def download_raw
37
- FileUtils.mkdir_p('/mnt/tmp')
38
-
39
- download
40
- end
41
-
42
- def base_name
43
- "#{base_path}/#{database}.#{timestamp}"
44
- end
45
-
46
- def timestamp
47
- @timestamp ||= Time.now.strftime("%Y-%m-%dT%H:%M:%S").gsub(/:/, '-')
48
- end
26
+ FileUtils.mkdir_p(EY::Backup.tmp_dir)
49
27
 
50
- def base_path
51
- "/mnt/tmp"
28
+ backup = BackupSet.download(@database, @index)
29
+ backup.join!
30
+ backup.remove_split_files!
31
+ backup
52
32
  end
53
33
  end
54
34
  end
@@ -1,30 +1,13 @@
1
1
  module EY
2
2
  module Backup
3
3
  module GPGEncryptor
4
- extend Spawner
5
- extend self
6
-
7
- def dump(backup)
8
- backup.files = backup.files.map {|file| encrypt(file) }
9
- backup
10
- end
11
-
12
- def command
13
- "gpg --trusted-key #{EY::Backup.config.key_id} --encrypt --recipient #{EY::Backup.config.key_id}"
4
+ def self.command_for(key_id)
5
+ "gpg -v --trusted-key #{key_id} --encrypt --recipient #{key_id}"
14
6
  end
15
7
 
16
- def extension
8
+ def self.extension
17
9
  ".gpz"
18
10
  end
19
-
20
- def load(backup)
21
- backup.files = backup.files.map {|file| decrypt(file) }
22
- backup
23
- end
24
-
25
- def decrypt(file)
26
- fatal "Restoring encrypted backups is not supported."
27
- end
28
11
  end
29
12
  end
30
13
  end
@@ -17,35 +17,6 @@ module EY
17
17
  def extension
18
18
  ".gz"
19
19
  end
20
-
21
- def load(backup)
22
- output_files = backup.files.map do |input|
23
- if input =~ /\.gz$/
24
- output = input.sub(/\.gz$/, '')
25
-
26
- File.open(output, 'w') do |o|
27
- File.open(input, 'r') do |i|
28
- gz = Zlib::GzipReader.new(i)
29
-
30
- while chunk = gz.read(CHUNK_SIZE)
31
- o << chunk
32
- end
33
-
34
- gz.close
35
- end
36
- end
37
-
38
- FileUtils.rm(input)
39
-
40
- output
41
- else
42
- input
43
- end
44
- end
45
-
46
- backup.files = output_files
47
- backup
48
- end
49
20
  end
50
21
  end
51
22
  end