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.
- data/bin/binary_log_purge +253 -0
- data/bin/clear_binlogs_from_slave +7 -0
- data/bin/ey-snapshots +6 -1
- data/bin/eybackup +3 -2
- data/lib/ey-flex.rb +11 -3
- data/lib/ey-flex/bucket_minder.rb +91 -16
- data/lib/ey-flex/snapshot_minder.rb +39 -31
- data/lib/ey-flex/version.rb +1 -1
- data/lib/ey_backup.rb +96 -0
- data/lib/ey_backup/backup_set.rb +128 -0
- data/lib/ey_backup/base.rb +18 -0
- data/lib/ey_backup/cli.rb +89 -0
- data/lib/ey_backup/dumper.rb +47 -0
- data/lib/ey_backup/engine.rb +51 -0
- data/lib/ey_backup/engines/mysql_engine.rb +63 -0
- data/lib/ey_backup/engines/postgresql_engine.rb +57 -0
- data/lib/ey_backup/loader.rb +55 -0
- data/lib/ey_backup/logger.rb +36 -0
- data/lib/ey_backup/processors/gpg_encryptor.rb +30 -0
- data/lib/ey_backup/processors/gzipper.rb +53 -0
- data/lib/ey_backup/processors/splitter.rb +96 -0
- data/lib/ey_backup/spawner.rb +54 -0
- data/lib/ey_cloud_server/mysql_start.rb +21 -14
- metadata +71 -39
- data/lib/ey-flex/backups.rb +0 -233
- data/lib/ey-flex/mysql_database.rb +0 -23
- data/lib/ey-flex/postgresql_database.rb +0 -14
@@ -13,20 +13,20 @@ module EY
|
|
13
13
|
opts.banner = "Usage: ey-snapshots [-flag] [argument]"
|
14
14
|
opts.define_head "ey-snapshots: managing your snapshots..."
|
15
15
|
opts.separator '*'*80
|
16
|
-
|
17
|
-
opts.on("-l", "--list-snapshots", "list snapshots") do
|
16
|
+
|
17
|
+
opts.on("-l", "--list-snapshots", "list snapshots") do
|
18
18
|
options[:command] = :list_snapshots
|
19
19
|
end
|
20
20
|
|
21
21
|
opts.on("-c", "--config CONFIG", "Use config file.") do |config|
|
22
22
|
options[:config] = config
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
opts.on("-i", "--instance-id ID", "specify the instance id to work with(only needed if you are running this from ourside of ec2)") do |iid|
|
26
26
|
options[:instance_id] = iid
|
27
27
|
end
|
28
|
-
|
29
|
-
|
28
|
+
|
29
|
+
|
30
30
|
opts.on("--snapshot", "take snapshots of both of your volumes(only runs on your ec2 instance)") do
|
31
31
|
options[:command] = :snapshot_volumes
|
32
32
|
end
|
@@ -34,7 +34,7 @@ module EY
|
|
34
34
|
opts.on("-q", "--quiet", "Supress output to STDOUT") do
|
35
35
|
options[:quiet] = true
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
end
|
39
39
|
|
40
40
|
opts.parse!(args)
|
@@ -58,7 +58,7 @@ module EY
|
|
58
58
|
get_instance_id
|
59
59
|
silence_stream($stderr) { find_volume_ids }
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
def find_volume_ids
|
63
63
|
@volume_ids = {}
|
64
64
|
@ec2.describe_volumes.each do |volume|
|
@@ -67,26 +67,26 @@ module EY
|
|
67
67
|
@volume_ids[:data] = volume[:aws_id]
|
68
68
|
elsif volume[:aws_device] == "/dev/sdz2"
|
69
69
|
@volume_ids[:db] = volume[:aws_id]
|
70
|
-
end
|
70
|
+
end
|
71
71
|
end
|
72
72
|
end
|
73
73
|
say "Volume IDs are #{@volume_ids.inspect}"
|
74
74
|
@volume_ids
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
def list_snapshots
|
78
78
|
@snapshot_ids = {}
|
79
79
|
@ec2.describe_snapshots.sort { |a,b| b[:aws_started_at] <=> a[:aws_started_at] }.each do |snapshot|
|
80
80
|
@volume_ids.each do |mnt, vol|
|
81
81
|
if snapshot[:aws_volume_id] == vol
|
82
82
|
(@snapshot_ids[mnt] ||= []) << snapshot[:aws_id]
|
83
|
-
end
|
83
|
+
end
|
84
84
|
end
|
85
85
|
end
|
86
86
|
say "Snapshots #{@snapshot_ids.inspect}"
|
87
87
|
@snapshot_ids
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def clean_snapshots(keep=5)
|
91
91
|
list_snapshots
|
92
92
|
@snapshot_ids.each do |mnt, ids|
|
@@ -101,7 +101,7 @@ module EY
|
|
101
101
|
end
|
102
102
|
list_snapshots
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def snapshot_volumes
|
106
106
|
snaps = []
|
107
107
|
@volume_ids.each do |vol, vid|
|
@@ -120,10 +120,10 @@ module EY
|
|
120
120
|
end
|
121
121
|
snaps
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
def get_instance_id
|
125
125
|
return @instance_id if @instance_id
|
126
|
-
|
126
|
+
|
127
127
|
open('http://169.254.169.254/latest/meta-data/instance-id') do |f|
|
128
128
|
@instance_id = f.gets
|
129
129
|
end
|
@@ -131,14 +131,22 @@ module EY
|
|
131
131
|
say "Instance ID is #{@instance_id}"
|
132
132
|
@instance_id
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
def sync_filesystem_buffers
|
136
136
|
sync_cmd = "sync && sync && sync"
|
137
137
|
system(sync_cmd)
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
def create_snapshot(volume_id)
|
141
|
-
|
141
|
+
retries = 0
|
142
|
+
begin
|
143
|
+
snap = @ec2.create_snapshot(volume_id)
|
144
|
+
rescue RightAws::AwsError
|
145
|
+
retries += 1
|
146
|
+
raise if retries > 10
|
147
|
+
sleep retries * retries
|
148
|
+
retry
|
149
|
+
end
|
142
150
|
say "Created snapshot of #{volume_id} as #{snap[:aws_id]}"
|
143
151
|
snap
|
144
152
|
end
|
@@ -158,11 +166,11 @@ module EY
|
|
158
166
|
stream.reopen(old_stream)
|
159
167
|
end
|
160
168
|
end
|
161
|
-
|
169
|
+
|
162
170
|
class Mysql
|
163
|
-
|
171
|
+
|
164
172
|
attr_accessor :dbh
|
165
|
-
|
173
|
+
|
166
174
|
def initialize(username, password, lock_wait_timeout)
|
167
175
|
@username = username
|
168
176
|
@password = password
|
@@ -170,36 +178,36 @@ module EY
|
|
170
178
|
@lock_wait_timeout = lock_wait_timeout.nil? ? 5 : lock_wait_timeout
|
171
179
|
@master_status_file = "/db/mysql/.snapshot_backup_master_status.txt"
|
172
180
|
end
|
173
|
-
|
181
|
+
|
174
182
|
def waiting_read_lock_thread
|
175
|
-
thread_cmd = "mysql -p#{@password} -u #{@username} -N -e 'show full processlist;' | grep 'flush tables with read lock' | awk '{print $1}'"
|
183
|
+
thread_cmd = "mysql -p#{@password} -u #{@username} -N -e 'show full processlist;' | grep 'flush tables with read lock' | awk '{print $1}'"
|
176
184
|
%x{#{thread_cmd}}
|
177
185
|
end
|
178
|
-
|
186
|
+
|
179
187
|
def write_master_status
|
180
188
|
master_status_cmd = "mysql -p#{@password} -u #{@username} -e'SHOW MASTER STATUS\\G' > #{@master_status_file}"
|
181
189
|
system(master_status_cmd)
|
182
190
|
end
|
183
|
-
|
191
|
+
|
184
192
|
def flush_tables_with_read_lock
|
185
193
|
pipe = IO.popen("mysql -u #{@username} -p#{@password}", 'w')
|
186
194
|
@read_lock_pid = pipe.pid
|
187
|
-
|
195
|
+
|
188
196
|
pipe.puts('flush tables with read lock;')
|
189
197
|
sleep(@lock_wait_timeout)
|
190
|
-
|
198
|
+
|
191
199
|
if (thread_id = waiting_read_lock_thread) != ''
|
192
200
|
Process.kill('TERM', @read_lock_pid)
|
193
|
-
|
201
|
+
|
194
202
|
# after killing the process the mysql thread is still hanging out, need to kill it directly
|
195
|
-
kill_thread_cmd = "mysql -u #{@username} -p#{@password} -e'kill #{thread_id};'"
|
203
|
+
kill_thread_cmd = "mysql -u #{@username} -p#{@password} -e'kill #{thread_id};'"
|
196
204
|
system(kill_thread_cmd)
|
197
205
|
abort "Read lock not acquired after #{@lock_wait_timeout} second timeout. Killed request and aborting backup."
|
198
206
|
end
|
199
|
-
|
207
|
+
|
200
208
|
true
|
201
209
|
end
|
202
|
-
|
210
|
+
|
203
211
|
def unlock_tables
|
204
212
|
# technically we don't actually have to do anything here since the spawned
|
205
213
|
# process that has the read lock will die with this one but it doesn't hurt
|
@@ -207,7 +215,7 @@ module EY
|
|
207
215
|
Process.kill('TERM', @read_lock_pid)
|
208
216
|
true
|
209
217
|
end
|
210
|
-
|
218
|
+
|
211
219
|
def disconnect
|
212
220
|
@dbh.disconnect
|
213
221
|
end
|
data/lib/ey-flex/version.rb
CHANGED
data/lib/ey_backup.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path(__FILE__ + '/../ey-flex')
|
2
|
+
require 'logger'
|
3
|
+
require 'forwardable'
|
4
|
+
require 'stringio'
|
5
|
+
require 'open4'
|
6
|
+
require 'zlib'
|
7
|
+
require 'ostruct'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
module EY
|
11
|
+
module Backup
|
12
|
+
extend self
|
13
|
+
attr_accessor :logger, :bucket_minder, :engine, :config
|
14
|
+
|
15
|
+
def run(argv = ARGV)
|
16
|
+
options = CLI.run(argv)
|
17
|
+
setup(options)
|
18
|
+
dispatch(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_backup(options = {})
|
22
|
+
Dumper.run(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def list(options = {})
|
26
|
+
if options[:db].nil? || options[:db].empty?
|
27
|
+
BackupSet.list_all
|
28
|
+
else
|
29
|
+
#list a single database's backups
|
30
|
+
BackupSet.list(options[:db])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def restore(options = {})
|
35
|
+
Loader.run(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def download(options = {})
|
39
|
+
Loader.download(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup(options)
|
43
|
+
setup_config(options)
|
44
|
+
setup_logger(options)
|
45
|
+
setup_minder(options)
|
46
|
+
setup_engine(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_config(options)
|
50
|
+
@config = OpenStruct.new(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def setup_logger(options)
|
54
|
+
if options[:quiet]
|
55
|
+
@logger ||= Logger.quiet
|
56
|
+
else
|
57
|
+
@logger ||= Logger.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_minder(options)
|
62
|
+
@bucket = "ey-backup-#{Digest::SHA1.hexdigest(options[:aws_secret_id])[0..11]}"
|
63
|
+
@bucket_minder = EY::BucketMinder.new(options.merge(:bucket => @bucket))
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_engine(options)
|
67
|
+
@engine = Engine.lookup(options[:engine]).new(options[:dbuser], options[:dbpass])
|
68
|
+
end
|
69
|
+
|
70
|
+
def dispatch(options)
|
71
|
+
send(options[:command], options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def base_path
|
75
|
+
"/mnt/tmp"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
lib_dir = File.expand_path(__FILE__ + '/../ey_backup')
|
82
|
+
require lib_dir + '/spawner'
|
83
|
+
require lib_dir + '/logger'
|
84
|
+
require lib_dir + '/base'
|
85
|
+
require lib_dir + '/backup_set'
|
86
|
+
require lib_dir + '/cli'
|
87
|
+
require lib_dir + '/dumper'
|
88
|
+
require lib_dir + '/engine'
|
89
|
+
require lib_dir + '/loader'
|
90
|
+
|
91
|
+
require lib_dir + '/engines/mysql_engine'
|
92
|
+
require lib_dir + '/engines/postgresql_engine'
|
93
|
+
|
94
|
+
require lib_dir + '/processors/gzipper'
|
95
|
+
require lib_dir + '/processors/splitter'
|
96
|
+
require lib_dir + '/processors/gpg_encryptor'
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module EY
|
2
|
+
module Backup
|
3
|
+
class BackupSet < Struct.new(:name, :keys)
|
4
|
+
include EY::Backup::Logging
|
5
|
+
extend EY::Backup::Logging
|
6
|
+
|
7
|
+
attr_accessor :files
|
8
|
+
|
9
|
+
MULTIPART_EXTENSION = /\.part\d+/
|
10
|
+
|
11
|
+
def self.parse(objects)
|
12
|
+
objects.map {|o| new(o[:name], o[:keys]) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from(database, file)
|
16
|
+
backup = new(File.basename(file), [])
|
17
|
+
backup.files = [file]
|
18
|
+
backup
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.cleanup(database, keep)
|
22
|
+
say "Cleanup for #{database}"
|
23
|
+
backups_for(database)[0...(-keep)].each do |backup|
|
24
|
+
say "deleting: #{backup.name}"
|
25
|
+
backup.delete!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.list_all
|
30
|
+
list('all', backups)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.backups
|
34
|
+
EY::Backup.config.databases.map {|db| backups_for(db) }.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.list(database, backups = backups_for(database))
|
38
|
+
info "Listing database backups for #{database}"
|
39
|
+
|
40
|
+
info "#{backups.size} backup(s) found"
|
41
|
+
|
42
|
+
backups.each_with_index do |db, i|
|
43
|
+
info "#{i}:#{database} #{db.normalized_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
backups
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.backups_for(database_name)
|
50
|
+
s3_objects = EY::Backup.bucket_minder.list(:prefix => "#{EY::Backup.config.environment}.#{database_name}", :print => false)
|
51
|
+
parse(s3_objects).sort
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.upload(database, file)
|
55
|
+
EY::Backup.bucket_minder.name = "#{EY::Backup.config.environment}.#{database}/#{File.basename(file)}"
|
56
|
+
EY::Backup.bucket_minder.upload_object(file, false)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.download(database, index)
|
60
|
+
fatal "You didn't specify a database name: e.g. 1:rails_production" if database.empty? || index.empty?
|
61
|
+
|
62
|
+
backup = backups_for(database)[index.to_i]
|
63
|
+
|
64
|
+
fatal "No backup found for database #{database}: requested index: #{index}" unless backup
|
65
|
+
|
66
|
+
backup.download!
|
67
|
+
|
68
|
+
backup
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(*)
|
72
|
+
super
|
73
|
+
|
74
|
+
@files = []
|
75
|
+
end
|
76
|
+
|
77
|
+
def parts
|
78
|
+
[keys.size, files.size].max
|
79
|
+
end
|
80
|
+
|
81
|
+
def normalized_name
|
82
|
+
normalize(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def upload!(database)
|
86
|
+
files.each do |file|
|
87
|
+
EY::Backup.bucket_minder.name = "#{EY::Backup.config.environment}.#{database}/#{File.basename(file)}"
|
88
|
+
EY::Backup.bucket_minder.upload_object(file, false)
|
89
|
+
info "successful upload: #{file}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def download!
|
94
|
+
keys.each do |key|
|
95
|
+
info "Downloading #{key}"
|
96
|
+
filename = File.join(EY::Backup.base_path, normalize(key))
|
97
|
+
File.open(filename, 'wb') do |f|
|
98
|
+
EY::Backup.bucket_minder.stream(key) do |chunk|
|
99
|
+
f << chunk
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
files << filename
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete!
|
108
|
+
keys.each do |key|
|
109
|
+
EY::Backup.bucket_minder.remove_object(key)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def rm!
|
114
|
+
files.each do |file|
|
115
|
+
FileUtils.rm(file)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def normalize(o)
|
120
|
+
o.gsub(/^.*?\//, '')
|
121
|
+
end
|
122
|
+
|
123
|
+
def <=>(o)
|
124
|
+
self.name <=> o.name
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EY
|
2
|
+
module Backup
|
3
|
+
class Base
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :logger, :fatal, :error, :warn, :info, :debug, :say
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@environment = options[:env]
|
10
|
+
@engine_name = options[:engine]
|
11
|
+
end
|
12
|
+
|
13
|
+
def logger
|
14
|
+
EY::Backup.logger
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module EY
|
2
|
+
module Backup
|
3
|
+
module CLI
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def run(argv)
|
7
|
+
options = default_options.merge(opt_parse(argv))
|
8
|
+
|
9
|
+
config_path = options[:config] || "/etc/.#{options[:engine]}.backups.yml"
|
10
|
+
|
11
|
+
config_for(config_path).merge(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_options
|
15
|
+
{
|
16
|
+
:command => :new_backup,
|
17
|
+
:format => "gzip",
|
18
|
+
:engine => 'mysql',
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def opt_parse(argv)
|
23
|
+
# Build a parser for the command line arguments
|
24
|
+
options = {}
|
25
|
+
|
26
|
+
opts = OptionParser.new do |opts|
|
27
|
+
opts.version = EY::CloudServer::VERSION
|
28
|
+
|
29
|
+
opts.banner = "Usage: eybackup [-flag] [argument]"
|
30
|
+
opts.define_head "eybackup: backing up your shit since way back when..."
|
31
|
+
opts.separator '*'*80
|
32
|
+
|
33
|
+
opts.on("-l", "--list-backup DATABASE", "List mysql backups for DATABASE") do |db|
|
34
|
+
options[:db] = (db || 'all')
|
35
|
+
options[:command] = :list
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-n", "--new-backup", "Create new mysql backup") do
|
39
|
+
options[:command] = :new_backup
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-c", "--config CONFIG", "Use config file.") do |config|
|
43
|
+
options[:config] = config
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-d", "--download BACKUP_INDEX", "download the backup specified by index. Run eybackup -l to get the index.") do |index|
|
47
|
+
options[:command] = :download
|
48
|
+
options[:index] = index
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgres.") do |engine|
|
52
|
+
options[:engine] = engine
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index WARNING! will overwrite the current db with the backup. Run eybackup -l to get the index.") do |index|
|
56
|
+
options[:command] = :restore
|
57
|
+
options[:index] = index
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-k", "--key KEYID", "Public key ID to use for the backup operation") do |key_id|
|
61
|
+
options[:key_id] = key_id
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-q", "--quiet", "Supress output to STDOUT") do
|
65
|
+
options[:quiet] = true
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-s", "--split_size INTEGER", "Maximum size of a single backup file before splitting.") do |split_size|
|
69
|
+
options[:split_size] = split_size.to_i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.parse!(argv)
|
74
|
+
|
75
|
+
options
|
76
|
+
end
|
77
|
+
|
78
|
+
def config_for(filename)
|
79
|
+
if File.exist?(filename)
|
80
|
+
config = YAML::load(File.read(filename))
|
81
|
+
config[:environment] ||= config[:env]
|
82
|
+
config
|
83
|
+
else
|
84
|
+
abort "You need to have a backup file at #{filename}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|