sqlup 0.0.1

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 (39) hide show
  1. data/History.txt +5 -0
  2. data/Manifest.txt +38 -0
  3. data/README.txt +122 -0
  4. data/Rakefile +18 -0
  5. data/bin/sqlup +107 -0
  6. data/bin/sqlup_control +25 -0
  7. data/config/environment.rb +0 -0
  8. data/lib/mysql_backup/entity/files/innodb.rb +77 -0
  9. data/lib/mysql_backup/entity/files/myisam.rb +62 -0
  10. data/lib/mysql_backup/entity/files.rb +95 -0
  11. data/lib/mysql_backup/entity/identifier.rb +179 -0
  12. data/lib/mysql_backup/entity/logs.rb +44 -0
  13. data/lib/mysql_backup/entity/mysqldump.rb +57 -0
  14. data/lib/mysql_backup/entity.rb +38 -0
  15. data/lib/mysql_backup/librarian/backup.rb +134 -0
  16. data/lib/mysql_backup/librarian/backup_collection.rb +63 -0
  17. data/lib/mysql_backup/librarian.rb +176 -0
  18. data/lib/mysql_backup/server.rb +237 -0
  19. data/lib/mysql_backup/storage/s3.rb +110 -0
  20. data/lib/mysql_backup/storage.rb +13 -0
  21. data/lib/mysql_backup/utilities/factory_create_method.rb +51 -0
  22. data/lib/mysql_backup.rb +8 -0
  23. data/lib/sqlup.rb +2 -0
  24. data/test/unit/mysql_backup/entity/files/files_test.rb +45 -0
  25. data/test/unit/mysql_backup/entity/files/innodb_test.rb +50 -0
  26. data/test/unit/mysql_backup/entity/files/myisam_test.rb +42 -0
  27. data/test/unit/mysql_backup/entity/identifier_test.rb +51 -0
  28. data/test/unit/mysql_backup/entity/logs_test.rb +13 -0
  29. data/test/unit/mysql_backup/entity/mysqldump_test.rb +64 -0
  30. data/test/unit/mysql_backup/librarian/backup_collection_test.rb +52 -0
  31. data/test/unit/mysql_backup/librarian/backup_test.rb +25 -0
  32. data/test/unit/mysql_backup/librarian/librarian_test.rb +103 -0
  33. data/test/unit/mysql_backup/server_test.rb +63 -0
  34. data/test/unit/mysql_backup/storage/s3_test.rb +69 -0
  35. data/test/unit/mysql_backup/storage/test_helper.rb +1 -0
  36. data/test/unit/mysql_backup/test_helper.rb +1 -0
  37. data/test/unit/mysql_backup/utilities/test_helper.rb +1 -0
  38. data/test/unit/test_helper.rb +10 -0
  39. metadata +116 -0
@@ -0,0 +1,179 @@
1
+ require 'rubygems'
2
+ require 'named_arguments'
3
+ require 'mysql_backup/utilities/factory_create_method'
4
+
5
+ module MysqlBackup; end
6
+ class MysqlBackup::Entity; end;
7
+
8
+ # There are four different kinds of things you store for MySQL backups.
9
+ #
10
+ # - Completed logs.
11
+ # - The current log (the log file that MySQL is writing to)
12
+ # - mysqldump files (output from the mysqldump command)
13
+ # - binary files (tar | gzip | split of the files in the mysql data directory)
14
+ #
15
+ # They're stored using the following name schemes:
16
+ #
17
+ # :log:type_complete:thelog.000005
18
+ # A completed log file.
19
+ #
20
+ # :log:type_current:log_file_thelog.0000000006:log_position_0000000311:n_parts_0000000001:part_number_0000000000
21
+ # A current log file. The position is 311 (that's where mysql will write the next statement).
22
+ # There's only one part, and this is it. (Part numbers start with 0.)
23
+ # In this release, log files can only have one part - no attempt is made to split them
24
+ # into chunks small enough to fit in S3. Don't let your log files grow larger than 5G.
25
+ # :full:type_mysqldump:log_file_thelog.0000000006:log_position_0000000382:n_parts_0000000001:part_number_0000000000
26
+ # A full mysqldump file. The current log file is thelog.0000000006, and the position in
27
+ # that log is 382. Since we don't flush logs, anything written after 382 might not be
28
+ # complete.
29
+ # :full:type_binary:log_file_thelog.0000000006:log_position_0000000311:n_parts_0000000001:part_number_0000000000
30
+ # A full copy of the MySQL data files, created by tarring up all the data files,
31
+ # then passing them through gzip and split to make sure they'll fit in S3 objects that
32
+ # can only hold 5G.
33
+ class MysqlBackup::Entity::Identifier
34
+ include NamedArguments
35
+ include FactoryCreateMethod
36
+
37
+ # category is one of
38
+ # :full => a full backup of the mysql files
39
+ # :log_current => the mysql binary log file that's being written to
40
+ # :log_complete => mysql binary log files that are complete
41
+ attr_accessor :category
42
+
43
+ # type depends on the category.
44
+ #
45
+ # for category +full+, type is one of
46
+ # :binary, :mysqldump
47
+ attr_accessor :type
48
+
49
+ # The name of the log file
50
+ attr_accessor :log_file
51
+
52
+ # The numeric position in the log file
53
+ attr_accessor :log_position
54
+
55
+ # For multipart storage units, the part number
56
+ attr_accessor :part_number
57
+
58
+ # For multipart storage units, the total number of parts
59
+ attr_accessor :n_parts
60
+
61
+ # Time the object was created
62
+ attr_accessor :timestamp
63
+
64
+ def initialize args
65
+ super
66
+
67
+ part_number ||= 0
68
+ part_number = sprintf "%06d", part_number.to_i
69
+
70
+ n_parts ||= 1
71
+ n_parts = sprintf "%06d", n_parts.to_i
72
+
73
+ throw :bad if log_file == 'foo'
74
+ end
75
+
76
+ def merge new_values_hash
77
+ to_hash.merge new_values_hash
78
+ end
79
+
80
+ def to_hash
81
+ result = {}
82
+ [:category, :type, :log_file, :log_position, :n_parts, :part_number].each do |i|
83
+ result[i] = send i
84
+ end
85
+ result
86
+ end
87
+
88
+ def to_s
89
+ pos = n_digits log_position
90
+ np = n_digits n_parts
91
+ pn = n_digits part_number
92
+ return "#{category}:type_#{@type}:log_file_#{log_file}:log_position_#{pos}:n_parts_#{np}:part_number_#{pn}"
93
+ end
94
+
95
+ def n_digits x
96
+ sprintf "%010d", x
97
+ end
98
+
99
+ # Get the sequence number from the log file name.
100
+ #
101
+ # mylogfile.000034 => 34
102
+ def log_file_number
103
+ log_file[/\.(\d+)$/, 1].to_i
104
+ end
105
+
106
+ # Examples of the four identifiers:
107
+ #
108
+ # log/type_complete/thelog.000001
109
+ # log/type_current/log_file_thelog.0000000006/log_position_0000000311/n_parts_0000000001/part_number_0000000000
110
+ # full/type_mysqldump/log_file_thelog.0000000006/log_position_0000000382/n_parts_0000000001/part_number_0000000000
111
+ # full/type_binary/log_file_thelog.0000000006/log_position_0000000311/n_parts_0000000001/part_number_0000000000
112
+ def self.string_to_args s
113
+ parts = s.split(':')
114
+ args = {}
115
+ args[:category] = parts.shift.to_sym
116
+ args[:type] = (parts.shift)[/^type_(.*)/, 1].to_sym
117
+ args[:log_file] = (parts.shift)[/log_file_(.*)/, 1]
118
+ unless parts.empty?
119
+ args[:log_position] = (parts.shift)[/log_position_(.*)/, 1].to_i
120
+ end
121
+ unless parts.empty?
122
+ args[:n_parts] = (parts.shift)[/n_parts_(.*)/, 1].to_i
123
+ args[:part_number] = (parts.shift)[/part_number_(.*)/, 1].to_i
124
+ end
125
+ args
126
+ end
127
+
128
+ def [] x
129
+ send x
130
+ end
131
+
132
+ def <=> rhs
133
+ category <=> rhs.category || self[:type] <=> rhs[:type] || log_file <=> rhs.log_file || log_position <=> rhs.log_position
134
+ end
135
+
136
+ append_factory_method do |args|
137
+ result = args[:string] && create_object(string_to_args(args[:string]))
138
+ if result
139
+ result.timestamp = args[:timestamp]
140
+ end
141
+ result
142
+ end
143
+ end
144
+
145
+ class MysqlBackup::Entity::Identifier::Full < MysqlBackup::Entity::Identifier
146
+ end
147
+
148
+ class MysqlBackup::Entity::Identifier::Full::Binary < MysqlBackup::Entity::Identifier::Full
149
+ append_factory_method {|args| args[:category] == :full && args[:type] == :binary && new(args)}
150
+ end
151
+
152
+ class MysqlBackup::Entity::Identifier::Full::Mysqldump < MysqlBackup::Entity::Identifier::Full
153
+ append_factory_method {|args| args[:category] == :full && args[:type] == :mysqldump && new(args)}
154
+ end
155
+
156
+ class MysqlBackup::Entity::Identifier::Log < MysqlBackup::Entity::Identifier
157
+ end
158
+
159
+ class MysqlBackup::Entity::Identifier::Log::Current < MysqlBackup::Entity::Identifier::Log
160
+ def to_s
161
+ pos = n_digits log_position
162
+ return "#{category}:type_#{@type}:log_file_#{log_file}:log_position_#{pos}"
163
+ end
164
+
165
+ append_factory_method {|args| args[:category] == :log && args[:type] == :current && new(args)}
166
+ end
167
+
168
+ class MysqlBackup::Entity::Identifier::Log::Complete < MysqlBackup::Entity::Identifier::Log
169
+ def to_s
170
+ return "#{category}:type_#{@type}:log_file_#{log_file}"
171
+ end
172
+ append_factory_method {|args| args[:category] == :log && args[:type] == :complete && new(args)}
173
+ end
174
+
175
+ class Pathname
176
+ def path
177
+ to_s
178
+ end
179
+ end
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'named_arguments'
3
+ require 'mysql_backup/entity/files'
4
+
5
+ class MysqlBackup::Entity::Logs < MysqlBackup::Entity::Files
6
+ include NamedArguments
7
+
8
+ attr_accessor :log_bin_dir
9
+ attr_accessor :completed_logs
10
+ attr_accessor :log_position
11
+ attr_accessor :log_file
12
+
13
+ # Takes the following arguments:
14
+ #
15
+ # :log_bin_dir => The prefix of the log files
16
+ # :completed_logs => All the logs before the one being written
17
+ # :log_file => The current log
18
+ # :log_position => The position in the log file
19
+ def initialize args = {}
20
+ super
21
+ @log_bin_dir = Pathname.new(args[:log_bin_dir])
22
+ end
23
+
24
+ def completed_logs_paths
25
+ completed_logs.map {|l| Pathname.new(log_bin_dir) + l}
26
+ end
27
+
28
+ def log_file_path
29
+ Pathname.new(log_bin_dir) + log_file
30
+ end
31
+
32
+ def save args = {}
33
+ # Do the complete logs
34
+ completed_logs_paths.each do |p|
35
+ i = MysqlBackup::Entity::Identifier.create_object :category => :log, :type => :complete, :log_file => p.basename.to_s, :n_parts => 1, :part_number => 0
36
+ files = self.class.tar_files [p.to_s]
37
+ yield :identifier => i, :file => files.first
38
+ end
39
+
40
+ # Do the current log
41
+ i = MysqlBackup::Entity::Identifier.create_object :category => :log, :type => :current, :log_file => log_file, :log_position => log_position, :n_parts => 1, :part_number => 0
42
+ yield :identifier => i, :file => log_file_path
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ require 'mysql_backup/entity'
2
+
3
+ # Mysqldump files are stored gzipped and split
4
+ class MysqlBackup::Entity::Mysqldump
5
+ include NamedArguments
6
+
7
+ attr_accessor :log
8
+ attr_accessor :log_file
9
+ attr_accessor :log_position
10
+
11
+ # Create a mysqldump file and yield
12
+ # a hash with a MysqlBackup::Entity::Identifier.
13
+ def create
14
+ Tempfile.open 'mysqldump' do |f|
15
+ cmd = "mysqldump --opt --all-databases --single-transaction --master-data=2 > #{f.path}"
16
+ log && log.info("running #{cmd}")
17
+ system cmd or raise RuntimeError, "failed to run command: #{cmd}"
18
+ f.flush
19
+ get_log_position f or raise RuntimeError, "could not get log position"
20
+ f.seek 0
21
+ compressed_files = compress_and_split f
22
+ identifier = MysqlBackup::Entity::Identifier.create_object :category => :full, :type => :mysqldump, :n_parts => compressed_files.length, :log_position => log_position, :log_file => log_file
23
+ compressed_files.each_with_index do |cf, n|
24
+ i = identifier.dup
25
+ i.part_number = n
26
+ yield :identifier => i, :file => cf
27
+ end
28
+ end
29
+ end
30
+
31
+ # Search through a mysqldump file looking for a line like
32
+ # CHANGE MASTER TO MASTER_LOG_FILE='thelog.000005', MASTER_LOG_POS=1163;
33
+ #
34
+ # Set log_position and log_file to the corresponding values.
35
+ def get_log_position file_obj
36
+ result = {}
37
+ n = 0
38
+ file_obj.each_line do |l|
39
+ if l =~ /CHANGE MASTER TO MASTER_LOG_FILE='(.*)', MASTER_LOG_POS=(\d+)/i
40
+ @log_file = $1
41
+ @log_position = $2.to_i
42
+ return true
43
+ end
44
+ n += 1
45
+ break if n > 100
46
+ end
47
+ false
48
+ end
49
+
50
+ def compress_and_split file_obj
51
+ destination_name = "#{file_obj.path}xxx"
52
+ cmd = "cat #{file_obj.path} | gzip | split --suffix-length=4 --bytes=10240000 --numeric-suffixes - #{destination_name}"
53
+ log && log.info("running " + cmd)
54
+ system cmd
55
+ Pathname.glob destination_name + "*"
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+ require 'tempfile'
4
+
5
+ require 'mysql_backup/entity/identifier'
6
+
7
+ module MysqlBackup; end
8
+
9
+ class MysqlBackup::Entity
10
+ attr_accessor :log
11
+
12
+ # Create a set of tarred, gziped, and split files
13
+ # for all the files given by required_path_strings.
14
+ #
15
+ # Return an array of Pathname objects for the
16
+ # split files.
17
+ #
18
+ # The caller is responsible for removing the files returned
19
+ # from tar_files.
20
+ def self.tar_files path_strings #:nodoc:
21
+ result = []
22
+ # Creating a tempfile to get a guaranteed unique filename.
23
+ # We don't actually write to this tempfile, we just
24
+ # use its name as the base name for the split.
25
+ Tempfile.open 'mysqltarball' do |f|
26
+ # We should never see existing files, but just in
27
+ # case we'll need to remove any.
28
+ Pathname.glob(f.path.to_s + 'xxx*').each {|e| e.unlink}
29
+
30
+ paths = path_strings.map {|ps| ps.slice(1..-1)}.join(' ')
31
+ five_gig = 1024 * 1024 * 1024 * 5
32
+ tar_cmd = "( cd / ; tar cf - #{paths} | gzip | split --suffix-length=4 --bytes=#{five_gig} --numeric-suffixes - #{f.path}xxx )"
33
+ do_tar :cmd => tar_cmd
34
+ result = Pathname.glob(f.path.to_s + 'xxx*')
35
+ end
36
+ result
37
+ end
38
+ end
@@ -0,0 +1,134 @@
1
+ require 'rubygems'
2
+ require 'named_arguments'
3
+ require 'mysql_backup/entity/identifier'
4
+
5
+ module MysqlBackup; end
6
+ class MysqlBackup::Librarian; end
7
+
8
+ # A backup is a complete set of identifiers for each run of backup type (full or log).
9
+ # Each backup has multiple identifiers because a single backup can have multiple files.
10
+ # The mysql binary logs are tarred up and then split into smaller parts to fit on S3.
11
+ # The same for mysqldump files; they can be greater than the size you can store in one
12
+ # S3 bucket.
13
+ class MysqlBackup::Librarian::Backup
14
+ include Comparable
15
+ include FactoryCreateMethod
16
+ include NamedArguments
17
+
18
+ # Returns an array of MysqlBackup::Entity::Identifier objects
19
+ # that are the members of this group
20
+ attr_accessor :identifiers
21
+ attribute_defaults :identifiers => []
22
+
23
+ create_class_settings_method :identifier_class
24
+
25
+ def initialize args = {}
26
+ super
27
+ args[:identifier] or raise RuntimeError, "Must provide :identifier"
28
+ identifiers << args[:identifier]
29
+ end
30
+
31
+ # True if this backup is the most recent of its type
32
+ def most_recent?
33
+ end
34
+
35
+
36
+ # Write the files for this backup, untarring and unzipping
37
+ # as required.
38
+ def write_files directory
39
+ end
40
+
41
+ # Write the files for this backup without untarring and unzipping.
42
+ def write_raw_files directory
43
+ end
44
+
45
+ # Returns true if the identifier was added to this group
46
+ def add_identifier_to_group_if_the_identifier_should_be_in_this_group identifier
47
+ is_part = is_part_of_this_group? identifier
48
+ identifiers << identifier if is_part
49
+ is_part
50
+ end
51
+
52
+ def is_part_of_this_group? identifier
53
+ return false unless identifier_class?.first === identifier
54
+ log_file == identifier.log_file && log_position == identifier.log_position
55
+ end
56
+
57
+ def log_file
58
+ identifiers.first.log_file
59
+ end
60
+
61
+ def log_position
62
+ identifiers.first.log_position
63
+ end
64
+
65
+ def each_identifier
66
+ identifiers.each {|i| yield i}
67
+ end
68
+
69
+ def <=> rhs
70
+ identifiers.first.log_file_number <=> rhs.log_file_number || identifiers.first.log_position <=> rhs.log_position
71
+ end
72
+
73
+ # The id string for a backup doesn't include the number of parts and the
74
+ # part itself.
75
+ #
76
+ # (Split on :, ignore the last two elements)
77
+ def to_s
78
+ identifiers.first.to_s.split(':').slice(0..-3).join(':')
79
+ end
80
+
81
+ def name_match rhs
82
+ to_s == rhs
83
+ end
84
+
85
+ def self.new_if_class_match klass
86
+ new_if_class klass, :identifier
87
+ end
88
+ end
89
+
90
+ module MysqlBackup
91
+ class Librarian::Backup::Full < Librarian::Backup
92
+ end
93
+
94
+ class Librarian::Backup::Full::Binary < Librarian::Backup::Full
95
+ new_if_class_match Entity::Identifier::Full::Binary
96
+ identifier_class Entity::Identifier::Full::Binary
97
+ end
98
+
99
+ class Librarian::Backup::Full::Mysqldump < Librarian::Backup::Full
100
+ new_if_class_match Entity::Identifier::Full::Mysqldump
101
+ identifier_class Entity::Identifier::Full::Mysqldump
102
+ end
103
+
104
+ class Librarian::Backup::Log < Librarian::Backup
105
+ def to_s
106
+ identifiers.first.to_s
107
+ end
108
+ end
109
+
110
+ class Librarian::Backup::Log::Current < Librarian::Backup::Log
111
+ new_if_class_match Entity::Identifier::Log::Current
112
+ identifier_class Entity::Identifier::Log::Current
113
+ end
114
+
115
+ class Librarian::Backup::Log::Complete < Librarian::Backup::Log
116
+ new_if_class_match Entity::Identifier::Log::Complete
117
+ identifier_class Entity::Identifier::Log::Complete
118
+
119
+ def is_part_of_this_group? identifier
120
+ return false unless identifier_class?.first === identifier
121
+ log_file == identifier.log_file
122
+ end
123
+
124
+ def log_position
125
+ 0
126
+ end
127
+ end
128
+ end
129
+
130
+ class Symbol
131
+ def to_proc
132
+ Proc.new { |obj, *args| obj.send(self, *args) }
133
+ end
134
+ end
@@ -0,0 +1,63 @@
1
+ require 'named_arguments'
2
+ require 'mysql_backup/librarian/backup'
3
+
4
+ # A backup group collection is a list of all the backup groups held by
5
+ # a MysqlBackup::Storage object.
6
+ #
7
+ # Backup group collections:: A collection of backup groups. Usually contains all of the backup groups in an S3 bucket.
8
+ # Backup group:: A collection of identifiers for S3 buckets. These identifiers point to a a single backup.
9
+ module MysqlBackup; end
10
+ class MysqlBackup::Librarian; end
11
+
12
+ class MysqlBackup::Librarian::BackupCollection
13
+ include NamedArguments
14
+
15
+ attr_accessor :groups
16
+ attribute_defaults :groups => []
17
+
18
+ def add_backup_group g
19
+ groups << g
20
+ end
21
+
22
+ def add_identifier identifier
23
+ result = nil
24
+ result = groups.find do |g|
25
+ g.add_identifier_to_group_if_the_identifier_should_be_in_this_group identifier
26
+ end
27
+ unless result
28
+ result = MysqlBackup::Librarian::Backup.create_object :identifier => identifier
29
+ groups << result
30
+ end
31
+ result
32
+ end
33
+
34
+ def groups_matching_type t
35
+ each_group t
36
+ end
37
+
38
+ def each_log
39
+ each_group MysqlBackup::Librarian::Backup::Log do |g|
40
+ yield g
41
+ end
42
+ end
43
+
44
+ def each_group matching_type = Object
45
+ result = []
46
+ groups.select {|g| matching_type === g}.each do |gg|
47
+ yield gg if block_given?
48
+ result << gg
49
+ end
50
+ result
51
+ end
52
+
53
+ def find_group group_name
54
+ each_group do |g|
55
+ return g if group_name == g.to_s
56
+ end
57
+ nil
58
+ end
59
+
60
+ def types
61
+ groups.map(&:class).uniq
62
+ end
63
+ end
@@ -0,0 +1,176 @@
1
+ require 'rubygems'
2
+ require 'named_arguments'
3
+
4
+ require 'mysql_backup/server'
5
+ require 'mysql_backup/storage/s3'
6
+ require 'mysql_backup/entity/files'
7
+ require 'mysql_backup/entity/files/innodb'
8
+ require 'mysql_backup/entity/files/myisam'
9
+ require 'mysql_backup/entity/logs'
10
+ require 'mysql_backup/entity/mysqldump'
11
+ require 'mysql_backup/librarian/backup_collection'
12
+
13
+ # backup_data_files:: Save the binary backups
14
+ # backup_mysqldump:: Send the mysqldump to s3
15
+ # backup_binary_logs:: Send the logs to S3
16
+ class MysqlBackup::Librarian
17
+ include NamedArguments
18
+
19
+ # MySQL connection parameters
20
+ attr_accessor :host, :user, :password, :db, :port, :sock, :flag
21
+
22
+ # The logger. Default is to log via STDERR.
23
+ attr_accessor :log
24
+
25
+ # A MysqlBackup::Storage object.
26
+ attr_accessor :storage
27
+
28
+ # The connection to MySQL
29
+ attr_accessor :connection
30
+
31
+ # The MysqlBackup::Server object used to talk to MySQL.
32
+ attr_writer :mysql_server
33
+
34
+ # S3 parameters
35
+ attr_accessor :access_key_id, :secret_access_key, :bucket
36
+
37
+ # Takes a required argument to specify the location of the log files:
38
+ #
39
+ # :log_bin_dir => '/var/lib/mysql'
40
+ #
41
+ # Takes required arguments for S3:
42
+ #
43
+ # :access_key_id => 'abc',
44
+ # :secret_access_key => '123'
45
+ # :bucket => 'name_of_the_backup_bucket'
46
+ #
47
+ # Takes these arguments for the connection to MySQL:
48
+ #
49
+ # :host
50
+ # :user
51
+ # :password
52
+ # :db
53
+ # :port
54
+ # :sock
55
+ # :flag
56
+ #
57
+ # Many installations just need to specify <tt>:host</tt> and <tt>:user</tt>.
58
+ def initialize args = {}
59
+ super
60
+ end
61
+
62
+ def backup_data_files
63
+ MysqlBackup::Entity::Files.create_tar_files :log => log, :mysql_server => mysql_server, :mysql_files => [innodb_files, myisam_files] do |args|
64
+ storage.conditional_save args
65
+ end
66
+ end
67
+
68
+ def backup_mysqldump
69
+ m = MysqlBackup::Entity::Mysqldump.new :log => log
70
+ m.create do |args|
71
+ storage.conditional_save args
72
+ end
73
+ end
74
+
75
+ def backup_binary_logs
76
+ logs_obj = mysql_server.create_logs_obj
77
+ logs_obj.save do |args|
78
+ storage.conditional_save args
79
+ end
80
+ end
81
+
82
+ def innodb_files
83
+ @innodb_files ||= mysql_server.create_innodb_files_obj
84
+ end
85
+
86
+ def myisam_files
87
+ @myisam_files ||= mysql_server.create_myisam_files_obj
88
+ end
89
+
90
+ def log_files
91
+ @log_files ||= mysql_server.create_logs_obj
92
+ end
93
+
94
+ def mysql_server
95
+ @mysql_server ||= MysqlBackup::Server.new :connection => create_connection, :log => log
96
+ end
97
+
98
+ def create_backup_group_collection_from_storage
99
+ unless @backups
100
+ c = MysqlBackup::Librarian::BackupCollection.new
101
+ storage.yield_identifiers do |i|
102
+ c.add_identifier i
103
+ end
104
+ @backups = c
105
+ end
106
+ @backups
107
+ end
108
+
109
+ def ls klass = Object
110
+ result = []
111
+ create_backup_group_collection_from_storage.each_group(klass) do |g|
112
+ result << g.to_s
113
+ end
114
+ result
115
+ end
116
+
117
+ def rm backup_name
118
+ g = find_group backup_name
119
+ g.each_identifier do |i|
120
+ storage.rm i
121
+ end
122
+ end
123
+
124
+ def get f, directory
125
+ case f
126
+ when /^full:type_binary/
127
+ get_full_binary f, directory
128
+ when /^full:type_mysqldump/
129
+ get_mysqldump f, directory
130
+ end
131
+ end
132
+
133
+ def get_full_binary f, destination_dir = '/tmp'
134
+ get_backup f do |tempfile|
135
+ run_cmd "( cd #{destination_dir}; zcat #{tempfile.path} | tar xf - )"
136
+ end
137
+ end
138
+
139
+ def get_mysqldump f, destination_dir = '/tmp'
140
+ get_backup f do |tempfile|
141
+ run_cmd "( cd #{destination_dir}; zcat #{tempfile.path} > #{f})"
142
+ end
143
+ end
144
+
145
+ def get_logs destination_dir = '/tmp'
146
+ create_backup_group_collection_from_storage.each_log do |g|
147
+ log && log.info("Looking at backup #{g.to_s}")
148
+ storage.retrieve_backup_and_then_yield_file g do |tempfile|
149
+ run_cmd "( cd #{destination_dir}; zcat #{tempfile.path} | tar xf - )"
150
+ end
151
+ end
152
+ end
153
+
154
+ def run_cmd cmd
155
+ log && log.debug("Run command: #{cmd}")
156
+ system cmd
157
+ end
158
+
159
+ # Always call this with a block that will do something
160
+ # with the raw data retrieved from S3.
161
+ def get_backup f, &block
162
+ b = find_group f
163
+ raise RuntimeError, "Failed to find backup for #{f}" unless b
164
+ if b
165
+ storage.retrieve_backup_and_then_yield_file(b, &block)
166
+ end
167
+ end
168
+
169
+ def find_group f
170
+ create_backup_group_collection_from_storage.find_group f
171
+ end
172
+
173
+ def create_connection
174
+ @connection ||= Mysql.connect(@host, @user, @pass, @db, @port, @sock, @flag)
175
+ end
176
+ end