ey_cloud_server 1.3.1 → 1.4.5.pre

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.
@@ -0,0 +1,47 @@
1
+ module EY
2
+ module Backup
3
+ class Dumper < Base
4
+ attr_accessor :database
5
+
6
+ def_delegators EY::Backup, :engine, :minder, :base_path
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
15
+ end
16
+ end
17
+
18
+ def initialize(database, options = {})
19
+ super(options)
20
+ @database = database
21
+ end
22
+
23
+ def run
24
+ info "doing database: #{database}"
25
+
26
+ FileUtils.mkdir_p('/mnt/tmp')
27
+
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(/:/, '-')
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,51 @@
1
+ module EY
2
+ module Backup
3
+ class Engine < Base
4
+ include Spawner
5
+
6
+ attr_accessor :username, :password
7
+
8
+ def self.label
9
+ @label
10
+ end
11
+
12
+ def self.register(label)
13
+ @label = label
14
+ end
15
+
16
+ def self.descendants
17
+ @descendants ||= []
18
+ end
19
+
20
+ def self.inherited(descendant)
21
+ descendants << descendant
22
+ end
23
+
24
+ def self.lookup(label)
25
+ descendants.detect {|d| d.label == label } ||
26
+ EY::Backup.logger.fatal("Unknown database engine: #{label}")
27
+ end
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
43
+ end
44
+
45
+ def gpg?
46
+ not EY::Backup.config.key_id.nil? &&
47
+ EY::Backup.config.key_id.blank?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ module EY
2
+ module Backup
3
+ class MysqlEngine < Engine
4
+ register 'mysql'
5
+
6
+ def dump(database, basename)
7
+ file = basename + '.sql'
8
+
9
+ command = "mysqldump #{username_option} #{password_option} #{single_transaction_option(database)} #{database}"
10
+
11
+ if gpg?
12
+ command << " | " << GPGEncryptor.command
13
+ file << GPGEncryptor.extension
14
+ else
15
+ command << " | " << GZipper.gzip
16
+ file << GZipper.extension
17
+ end
18
+
19
+ command << " > #{file}"
20
+
21
+ run(command)
22
+
23
+ Splitter.dump(BackupSet.from(database, file))
24
+ end
25
+
26
+ def load(database, backup)
27
+ backup = Splitter.load(backup)
28
+
29
+ command = "cat #{backup.files.first}"
30
+
31
+ if gpg?
32
+ command << " | " << GPGEncryptor.load_command
33
+ else
34
+ command << " | " << GZipper.gunzip
35
+ end
36
+
37
+ command << " | mysql #{username_option} #{password_option} #{database}"
38
+
39
+ run(command)
40
+ end
41
+
42
+ def password_option
43
+ "-p'#{password}'" unless password.nil? || password.empty?
44
+ end
45
+
46
+ def username_option
47
+ "-u#{username}"
48
+ end
49
+
50
+ def single_transaction_option(database)
51
+ '--single-transaction' unless db_has_myisam?(database)
52
+ end
53
+
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)
57
+ stdout.string.strip == '1'
58
+ end
59
+
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,57 @@
1
+ module EY
2
+ module Backup
3
+ class Postgresql < Engine
4
+ register 'postgresql'
5
+
6
+ def dump(database, basename)
7
+ file = basename + '.pgz'
8
+
9
+ command = "PGPASSWORD='#{password}' pg_dump -h localhost --format=c --no-owner --no-privileges -U#{username} #{database}"
10
+
11
+ if gpg?
12
+ command << " | " << GPGEncryptor.command
13
+ file << GPGEncryptor.extension
14
+ end
15
+
16
+ command << " > #{file}"
17
+
18
+ run(command)
19
+
20
+ Splitter.dump(BackupSet.from(database, file))
21
+ end
22
+
23
+ def load(database, backup)
24
+ backup = Splitter.load(backup)
25
+
26
+ cycle_database(database) if database_exists?(database)
27
+
28
+ command = "cat #{backup.files.first}"
29
+
30
+ if gpg?
31
+ command << " | " << GPGEncryptor.load_command
32
+ end
33
+
34
+ command << " | PGPASSWORD='#{password}' pg_restore -h localhost --format=c --ignore-version -U#{username} -d #{database}"
35
+
36
+ run(command)
37
+ end
38
+
39
+ def database_exists?(database)
40
+ spawn("PGPASSWORD='#{password}' psql -l | grep '#{database}'").success?
41
+ end
42
+
43
+ def drop_database(database)
44
+ spawn("PGPASSWORD='#{password}' dropdb -h localhost -U#{username} #{database}")
45
+ end
46
+
47
+ def create_database(database)
48
+ spawn("PGPASSWORD='#{password}' createdb -U#{username} -h localhost #{database}")
49
+ end
50
+
51
+ def cycle_database(database)
52
+ drop_database(database)
53
+ create_database(database)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ module EY
2
+ module Backup
3
+ class Loader < Base
4
+ include Logging
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
13
+ end
14
+
15
+ def self.download(options = {})
16
+ index, database = options[:index].split(':')
17
+ new(database, index, options).download_raw
18
+ end
19
+
20
+ def initialize(database, index, options = {})
21
+ super(options)
22
+ @database, @index = database, index
23
+ end
24
+
25
+ def run
26
+ backup = download
27
+ info "Restoring #{database}"
28
+ engine.load(database, backup)
29
+ backup.rm!
30
+ end
31
+
32
+ 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
49
+
50
+ def base_path
51
+ "/mnt/tmp"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+ module EY
2
+ module Backup
3
+ class Logger
4
+ extend Forwardable
5
+
6
+ attr_accessor :stdout, :stderr
7
+
8
+ alias_method :fatal, :abort
9
+ public :fatal
10
+
11
+ def_delegator :stderr, :puts, :error
12
+ def_delegator :stderr, :puts, :warn
13
+ def_delegator :stdout, :puts, :info
14
+ def_delegator :stdout, :puts, :puts
15
+
16
+ def self.quiet
17
+ new(StringIO.new)
18
+ end
19
+
20
+ def initialize(stdout = $stdout, stderr = $stderr)
21
+ @stdout, @stderr = stdout, stderr
22
+ end
23
+
24
+ def say(msg, newline = true)
25
+ newline ? info(msg) : stdout.print(msg)
26
+ end
27
+ end
28
+
29
+ module Logging
30
+ extend Forwardable
31
+
32
+ def_delegator EY::Backup, :logger
33
+ def_delegators :logger, :fatal, :error, :warn, :info, :puts, :say
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module EY
2
+ module Backup
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}"
14
+ end
15
+
16
+ def extension
17
+ ".gpz"
18
+ 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
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ module EY
2
+ module Backup
3
+ module GZipper
4
+ extend Spawner
5
+ extend self
6
+
7
+ CHUNK_SIZE = 1024 * 64
8
+
9
+ def gzip
10
+ "gzip -c"
11
+ end
12
+
13
+ def gunzip
14
+ "gunzip -c"
15
+ end
16
+
17
+ def extension
18
+ ".gz"
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
+ end
50
+ end
51
+ end
52
+
53
+
@@ -0,0 +1,96 @@
1
+ module EY
2
+ module Backup
3
+ module Splitter
4
+ extend Spawner
5
+ extend Logging
6
+ extend self
7
+
8
+ CHUNK_SIZE = 1024 * 64
9
+ MAX_FILE_SIZE = (4.5*1024*1024*1024).to_i #4.5GB
10
+
11
+ def load(backup)
12
+ if backup.files.size > 1
13
+ backup.files = join(backup.files)
14
+ end
15
+
16
+ backup
17
+ end
18
+
19
+ def dump(command, files)
20
+ if files.size != 1
21
+ fatal "Can't split multiple files."
22
+ end
23
+
24
+ end
25
+
26
+ def dump(backup)
27
+ output_files = backup.files.map do |file|
28
+ if File.size(file) >= split_size
29
+ split(file)
30
+ else
31
+ file
32
+ end
33
+ end.flatten
34
+
35
+ backup.files = output_files
36
+ backup
37
+ end
38
+
39
+ def join(input_files)
40
+ filename = input_files.first.sub(/\.part\d+$/, '')
41
+
42
+ File.open(filename, 'w') do |output|
43
+ sort(input_files).each do |input|
44
+ File.open(input, 'r') do |i|
45
+ while chunk = i.read(CHUNK_SIZE)
46
+ output << chunk
47
+ end
48
+ end
49
+
50
+ FileUtils.rm(input)
51
+ end
52
+ end
53
+
54
+ [filename]
55
+ end
56
+
57
+ def sort(input_files)
58
+ input_files.sort {|a,b| part_number(a) <=> part_number(b) }
59
+ end
60
+
61
+ def part_number(file)
62
+ file[/\.part(\d+)/, 1].to_i
63
+ end
64
+
65
+ def split(file)
66
+ total_size, part = 0, 0
67
+ files = []
68
+ File.open(file, 'r') do |i|
69
+ until total_size == File.size(file)
70
+ part += 1
71
+ part_size = 0
72
+ filename = "#{file}.part#{part}"
73
+ File.open(filename, 'w') do |o|
74
+ while part_size < split_size && (chunk = i.read([split_size - part_size, CHUNK_SIZE].min))
75
+ part_size += chunk.size
76
+ o << chunk
77
+ end
78
+ end
79
+
80
+ total_size += part_size
81
+
82
+ files << filename
83
+ end
84
+ end
85
+
86
+ FileUtils.rm(file)
87
+
88
+ files
89
+ end
90
+
91
+ def split_size
92
+ EY::Backup.config.split_size || MAX_FILE_SIZE
93
+ end
94
+ end
95
+ end
96
+ end