ey_cloud_server 1.4.47 → 1.4.49
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.
- checksums.yaml +7 -7
- data/features/support/env.rb +3 -3
- data/lib/ey-flex.rb +2 -3
- data/lib/ey-flex/snapshot_minder.rb +1 -1
- data/lib/ey_backup.rb +8 -2
- data/lib/ey_backup/backend.rb +1 -1
- data/lib/ey_backup/base.rb +1 -1
- data/lib/ey_backup/cli.rb +47 -19
- data/lib/ey_backup/database.rb +15 -1
- data/lib/ey_backup/dumper.rb +31 -3
- data/lib/ey_backup/engine.rb +3 -3
- data/lib/ey_backup/engines/mysql_engine.rb +49 -7
- data/lib/ey_backup/engines/postgresql_engine.rb +69 -21
- data/lib/ey_backup/logger.rb +54 -4
- data/lib/ey_backup/spawner.rb +31 -5
- data/lib/ey_cloud_server.rb +0 -1
- data/lib/ey_cloud_server/version.rb +1 -1
- data/spec/config.yml +11 -0
- data/spec/ey_backup/mysql_backups_spec.rb +11 -10
- data/spec/ey_backup/postgres_backups_spec.rb +9 -9
- data/spec/helpers.rb +16 -10
- data/spec/spec_helper.rb +1 -2
- metadata +126 -141
- data/bin/binary_log_purge +0 -263
- data/bin/clear_binlogs_from_slave +0 -7
- data/bin/ey-agent +0 -5
- data/bin/mysql_start +0 -6
- data/lib/ey-flex/big-brother.rb +0 -80
- data/lib/ey_cloud_server/mysql_start.rb +0 -155
- data/spec/big-brother_spec.rb +0 -12
@@ -6,7 +6,7 @@ module EY
|
|
6
6
|
def dump(database_name, basename)
|
7
7
|
file = basename + '.dump'
|
8
8
|
|
9
|
-
command = "PGPASSWORD='#{password}' pg_dump -h #{host} --format=c -Upostgres #{database_name}"
|
9
|
+
command = "PGPASSWORD='#{password}' pg_dump -h #{host} --create --format=c -Upostgres #{database_name} 2> /tmp/eybackup.$$.dumperr"
|
10
10
|
|
11
11
|
if gpg?
|
12
12
|
command << " | " << GPGEncryptor.command_for(key_id)
|
@@ -15,17 +15,13 @@ module EY
|
|
15
15
|
|
16
16
|
command << " > #{file}"
|
17
17
|
|
18
|
-
run(command)
|
18
|
+
run(command, database_name)
|
19
19
|
|
20
20
|
file
|
21
21
|
end
|
22
22
|
|
23
23
|
def load(database_name, file)
|
24
|
-
|
25
|
-
cycle_database(database_name)
|
26
|
-
else
|
27
|
-
create_database(database_name)
|
28
|
-
end
|
24
|
+
cycle_database(database_name)
|
29
25
|
|
30
26
|
command = "cat #{file}"
|
31
27
|
|
@@ -33,33 +29,85 @@ module EY
|
|
33
29
|
raise "Cannot load a GPG backup"
|
34
30
|
end
|
35
31
|
|
36
|
-
command << " | PGPASSWORD='#{password}' pg_restore -h #{host} --format=c -Upostgres -d #{database_name}"
|
37
|
-
|
38
|
-
run(command)
|
39
|
-
end
|
32
|
+
command << " | PGPASSWORD='#{password}' pg_restore -h #{host} --clean --format=c -Upostgres -d #{database_name}"
|
40
33
|
|
41
|
-
|
42
|
-
runs?("PGPASSWORD='#{password}' psql -l -h #{host} | grep '#{database_name}'")
|
34
|
+
run(command, database_name)
|
43
35
|
end
|
44
36
|
|
45
|
-
def
|
37
|
+
def check_connections(database_name)
|
46
38
|
stdout = StringIO.new()
|
47
|
-
active_connections = spawn(%Q{PGPASSWORD='#{password}' psql -U postgres -t -c "select count(*) from pg_stat_activity where datname='#{database_name}';"}, stdout)
|
39
|
+
active_connections = spawn(%Q{PGPASSWORD='#{password}' psql -U postgres -h #{host} -t -c "select count(*) from pg_stat_activity where datname='#{database_name}';"}, stdout)
|
48
40
|
|
49
41
|
if stdout.string.to_i > 0
|
50
|
-
|
42
|
+
res = ''
|
43
|
+
unless force
|
44
|
+
puts "There are currently #{stdout.string.to_i} connections on database: '#{database_name}'; can I kill these to continue (Y/n):"
|
45
|
+
Timeout::timeout(30){
|
46
|
+
res = gets.strip
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
if res.upcase == 'Y' or force
|
51
|
+
cancel_connections(database_name)
|
52
|
+
else
|
53
|
+
EY::Backup.logger.fatal(%Q{ERROR: Target database has active connections. For more information, see "Restore or load a database" in docs.engineyard.com})
|
54
|
+
end
|
51
55
|
end
|
52
|
-
|
53
|
-
|
56
|
+
end
|
57
|
+
|
58
|
+
def cancel_connections(database_name)
|
59
|
+
spawn(%Q{psql -U postgres -h #{host} -c"SELECT pg_terminate_backend(pg_stat_activity.pid)
|
60
|
+
FROM pg_stat_activity
|
61
|
+
WHERE pg_stat_activity.datname = '#{database_name}';"})
|
62
|
+
end
|
63
|
+
|
64
|
+
def drop_database(database_name)
|
65
|
+
spawn("PGPASSWORD='#{password}' dropdb -h #{host} -Upostgres #{database_name}")
|
54
66
|
end
|
55
67
|
|
56
68
|
def create_database(database_name)
|
57
69
|
spawn("PGPASSWORD='#{password}' createdb -U#{username} -h #{host} #{database_name}")
|
58
70
|
end
|
59
|
-
|
71
|
+
|
72
|
+
def check_if_replica
|
73
|
+
stdout = StringIO.new()
|
74
|
+
spawn(%Q{PGPASSWORD='#{password}' psql -U postgres -h #{host} -t -c "select pg_is_in_recovery()"| head -n 1}, stdout)
|
75
|
+
unless stdout.string.chomp =~ /^\W*f$/
|
76
|
+
EY::Backup.logger.fatal(%Q{ERROR: Target host: '#{host}' is currently a replica in recovery mode; restore operations need to be processed against the master.})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_command(database_name)
|
81
|
+
stdout = StringIO.new()
|
82
|
+
|
83
|
+
spawn(%Q{PGPASSWORD='#{password}' psql -U postgres -h #{host} -t -c "SELECT 'CREATE DATABASE ' || datname ||
|
84
|
+
' WITH OWNER ' || pg_user.usename ||
|
85
|
+
CASE (select pg_encoding_to_char(encoding) from pg_database where datname='template1')
|
86
|
+
WHEN pg_encoding_to_char(encoding)
|
87
|
+
THEN ''
|
88
|
+
ELSE ' template=template0'
|
89
|
+
END ||
|
90
|
+
' ENCODING ''' || pg_encoding_to_char(encoding) ||
|
91
|
+
''' LC_COLLATE ''' || datcollate||
|
92
|
+
''' LC_CTYPE ''' || datctype ||
|
93
|
+
''' CONNECTION LIMIT ' || datconnlimit || ';'
|
94
|
+
FROM pg_database
|
95
|
+
INNER JOIN pg_user
|
96
|
+
ON pg_user.usesysid = pg_database.datdba
|
97
|
+
WHERE datname = '#{database_name}'"
|
98
|
+
}, stdout)
|
99
|
+
stdout.string
|
100
|
+
end
|
101
|
+
|
60
102
|
def cycle_database(database_name)
|
61
|
-
|
62
|
-
|
103
|
+
create_cmd = create_command(database_name).chomp
|
104
|
+
if create_cmd == ''
|
105
|
+
create_database(database_name)
|
106
|
+
else
|
107
|
+
check_connections(database_name)
|
108
|
+
drop_database(database_name)
|
109
|
+
spawn(%Q{PGPASSWORD='#{password}' psql -U postgres -h #{host} -t -c "#{create_cmd}"})
|
110
|
+
end
|
63
111
|
end
|
64
112
|
|
65
113
|
def suffix
|
data/lib/ey_backup/logger.rb
CHANGED
@@ -2,15 +2,13 @@ module EY
|
|
2
2
|
module Backup
|
3
3
|
class Logger
|
4
4
|
extend Forwardable
|
5
|
+
require 'shellwords'
|
5
6
|
|
6
7
|
attr_accessor :stdout, :stderr
|
7
8
|
|
8
9
|
alias_method :fatal, :abort
|
9
10
|
public :fatal
|
10
11
|
|
11
|
-
def_delegator :stderr, :puts, :error
|
12
|
-
def_delegator :stderr, :puts, :warn
|
13
|
-
def_delegator :stdout, :puts, :info
|
14
12
|
def_delegator :stdout, :puts, :puts
|
15
13
|
|
16
14
|
def self.quiet
|
@@ -19,18 +17,70 @@ module EY
|
|
19
17
|
|
20
18
|
def initialize(stdout = $stdout, stderr = $stderr)
|
21
19
|
@stdout, @stderr = stdout, stderr
|
20
|
+
@verbose = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def push_dashboard_alert(message, alert_level, db = nil)
|
24
|
+
message.gsub!("\n", '\n')
|
25
|
+
type="process-dbbackup"
|
26
|
+
type = type + " #{db}" unless db.nil?
|
27
|
+
full_txt= "Severity: #{alert_level}\n" \
|
28
|
+
+ "Time: #{Time.now.to_i}\n" \
|
29
|
+
+ "Type: #{type}\n" \
|
30
|
+
+ "Plugin: exec\n" \
|
31
|
+
+ "raw_message: '#{message}'"
|
32
|
+
full_txt = Shellwords.escape(full_txt)
|
33
|
+
alert_command = %Q(echo #{full_txt} | /engineyard/bin/ey-alert.rb 2>&1)
|
34
|
+
verbose "Sending dashboard alert"
|
35
|
+
system(alert_command)
|
36
|
+
end
|
37
|
+
|
38
|
+
def info(msg)
|
39
|
+
puts "#{Time.now} #{msg}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def verbose(msg)
|
43
|
+
info(msg) if @verbose
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_verbose()
|
47
|
+
@verbose = true
|
22
48
|
end
|
23
49
|
|
24
50
|
def say(msg, newline = true)
|
25
51
|
newline ? info(msg) : stdout.print(msg)
|
26
52
|
end
|
53
|
+
|
54
|
+
def okay(msg, db = nil)
|
55
|
+
stderr.puts("#{Time.now} OKAY: #{msg}")
|
56
|
+
# push_dashboard_alert(msg, "OKAY", db), we don't push OKAY alerts, we push a summary alert instead.
|
57
|
+
end
|
58
|
+
|
59
|
+
def warn(msg, db = nil)
|
60
|
+
stderr.puts("#{Time.now} WARNING: #{msg}")
|
61
|
+
push_dashboard_alert(msg, "WARNING", db)
|
62
|
+
end
|
63
|
+
|
64
|
+
def error(msg, db = nil)
|
65
|
+
stderr.puts("#{Time.now} ERROR: #{msg}")
|
66
|
+
push_dashboard_alert("#{msg}. Details at /var/log/eybackup.log.", "FAILURE", db)
|
67
|
+
end
|
68
|
+
|
69
|
+
def exception(type, msg)
|
70
|
+
self.error(msg)
|
71
|
+
end
|
72
|
+
|
73
|
+
def debug(msg)
|
74
|
+
stderr.puts("#{Time.now} DEBUG: #{msg}")
|
75
|
+
end
|
76
|
+
|
27
77
|
end
|
28
78
|
|
29
79
|
module Logging
|
30
80
|
extend Forwardable
|
31
81
|
|
32
82
|
def_delegator EY::Backup, :logger
|
33
|
-
def_delegators :logger, :fatal, :error, :warn, :info, :puts, :say
|
83
|
+
def_delegators :logger, :fatal, :error, :warn, :okay, :info, :verbose, :debug, :puts, :say
|
34
84
|
end
|
35
85
|
end
|
36
86
|
end
|
data/lib/ey_backup/spawner.rb
CHANGED
@@ -18,15 +18,41 @@ module EY
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def run(command)
|
22
|
-
unless runs?(command)
|
23
|
-
raise "Failed to run
|
21
|
+
def run(command, db = nil)
|
22
|
+
unless runs?(command, db)
|
23
|
+
raise "Failed to run backup command."
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def runs?(command)
|
28
|
-
|
27
|
+
def runs?(command, db)
|
28
|
+
# This is to detect failures anywhere in the pipeline.
|
29
|
+
wrapper = <<-EOT
|
30
|
+
status=0
|
31
|
+
set -o pipefail
|
32
|
+
#{command}
|
33
|
+
status=$?
|
34
|
+
|
35
|
+
if [ $status -gt 0 ]; then exit 1; fi
|
36
|
+
EOT
|
37
|
+
|
38
|
+
verbose "Running command: #{command}"
|
39
|
+
pid, *_ = Open4.popen4(wrapper)
|
29
40
|
pid, status = Process::waitpid2(pid)
|
41
|
+
|
42
|
+
if ! status.success?
|
43
|
+
dumperr = File.read("/tmp/eybackup.#{pid}.dumperr")
|
44
|
+
error("DB dump failed. The error returned was: #{dumperr}", db)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Clean up:
|
48
|
+
# This whole error output thing is hideous.
|
49
|
+
# We need the errors, but as we're dealing with a pipeline we
|
50
|
+
# need to capture them separately rather than send them as
|
51
|
+
# input to the next process. We construct the backup command
|
52
|
+
# in the backup engine but only know the pid of the resulting
|
53
|
+
# backup here. Ugh.
|
54
|
+
system("rm /tmp/eybackup.#{pid}.dumperr") if File.exists?("/tmp/eybackup.#{pid}.dumperr")
|
55
|
+
|
30
56
|
status.success?
|
31
57
|
end
|
32
58
|
|
data/lib/ey_cloud_server.rb
CHANGED
data/spec/config.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
---
|
2
|
+
tmp_dir:
|
3
|
+
data_json:
|
4
|
+
mysql_user: root
|
5
|
+
mysql_password:
|
6
|
+
mysql_host: localhost
|
7
|
+
postgresql_user: postgres
|
8
|
+
postgresql_password: ketchup
|
9
|
+
postgresql_host: localhost
|
10
|
+
aws_secret_id: 'AKIAIDNXVLT3THIWYMNQ'
|
11
|
+
aws_secret_key: 'DZBOcAI1qpYF0V+ZL2SeA7Jzt15boFxIo+BD7vda'
|
@@ -23,6 +23,7 @@ describe "MySQL Backups" do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it "makes GPG encrypted backups" do
|
26
|
+
import_gpg
|
26
27
|
EY::Backup.run(["-c", backup_config_file, '-k', Helpers::PUBLIC_KEY_ID])
|
27
28
|
|
28
29
|
EY::Backup.run(["-c", backup_config_file, "-l", @db_name])
|
@@ -89,29 +90,29 @@ describe "MySQL Backups" do
|
|
89
90
|
it 'manually restores an encrypted backup' do
|
90
91
|
run_sql("CREATE TABLE `foo` (`id` int(11) NOT NULL auto_increment, PRIMARY KEY(`id`));", @db_name).should be_true
|
91
92
|
run_sql("CREATE TABLE `bar` (`id` int(11) NOT NULL auto_increment, PRIMARY KEY(`id`));", @db_name).should be_true
|
92
|
-
|
93
|
+
|
93
94
|
import_gpg
|
94
|
-
|
95
|
+
|
95
96
|
EY::Backup.run(["-c", backup_config_file, '-k', Helpers::PUBLIC_KEY_ID])
|
96
|
-
|
97
|
+
|
97
98
|
run_sql("DROP TABLE `bar`;", @db_name).should be_true
|
98
99
|
run_sql("SELECT * FROM `bar`;", @db_name).should be_false
|
99
|
-
|
100
|
+
|
100
101
|
reset_logger
|
101
|
-
|
102
|
+
|
102
103
|
EY::Backup.run(["-c", backup_config_file, "-l", @db_name])
|
103
|
-
|
104
|
+
|
104
105
|
stdout.should include("1 backup(s) found")
|
105
|
-
|
106
|
+
|
106
107
|
EY::Backup.run(["-c", backup_config_file, "--download", "0:#{@db_name}"])
|
107
108
|
file = Dir["#{EY::Backup.tmp_dir}/*#{@db_name}*"].first
|
108
109
|
stdout = StringIO.new
|
109
110
|
EY::Backup::Spawner.spawn("gpg --no-default-keyring --keyring #{Helpers::PUBLIC_KEY_PATH} --secret-keyring #{Helpers::PRIVATE_KEY_PATH} --decrypt", stdout, file)
|
110
|
-
|
111
|
+
|
111
112
|
stdout.rewind
|
112
|
-
|
113
|
+
|
113
114
|
run_sql_pipe(stdout, @db_name)
|
114
|
-
|
115
|
+
|
115
116
|
run_sql("SELECT * FROM `foo`;", @db_name).should be_true
|
116
117
|
run_sql("SELECT * FROM `bar`;", @db_name).should be_true
|
117
118
|
FileUtils.rm(file)
|
@@ -24,11 +24,11 @@ describe "Postgres Backups" do
|
|
24
24
|
|
25
25
|
it "makes GPG encrypted backups" do
|
26
26
|
import_gpg
|
27
|
-
|
27
|
+
|
28
28
|
EY::Backup.run(["-c", backup_config_file, '-e', 'postgresql', '-k', Helpers::PUBLIC_KEY_ID])
|
29
|
-
|
29
|
+
|
30
30
|
EY::Backup.run(["-c", backup_config_file, '-e', 'postgresql', "-l", @db_name])
|
31
|
-
|
31
|
+
|
32
32
|
stdout.should match(/0:#{@db_name}/)
|
33
33
|
end
|
34
34
|
|
@@ -56,19 +56,19 @@ describe "Postgres Backups" do
|
|
56
56
|
it "restores split backups" do
|
57
57
|
run_psql("CREATE TABLE foo (id integer NOT NULL);", @db_name).should be_true
|
58
58
|
run_psql("CREATE TABLE bar (id integer NOT NULL);", @db_name).should be_true
|
59
|
-
|
59
|
+
|
60
60
|
EY::Backup.run(["-c", backup_config_file, "-e", "postgresql", "-s", "100"])
|
61
61
|
EY::Backup.run(["-c", backup_config_file, "-e", "postgresql", "-l", @db_name]).first.parts.should > 1
|
62
|
-
|
62
|
+
|
63
63
|
run_psql("DROP TABLE bar;", @db_name).should be_true
|
64
64
|
run_psql("SELECT * FROM bar;", @db_name).should be_false
|
65
|
-
|
65
|
+
|
66
66
|
EY::Backup.run(["-c", backup_config_file, "-l", @db_name, '-e', 'postgresql'])
|
67
|
-
|
67
|
+
|
68
68
|
stdout.should include("1 backup(s) found")
|
69
|
-
|
69
|
+
|
70
70
|
EY::Backup.run(["-c", backup_config_file, "-r", "0:#{@db_name}", '-e', 'postgresql'])
|
71
|
-
|
71
|
+
|
72
72
|
run_psql("SELECT * FROM foo;", @db_name).should be_true
|
73
73
|
run_psql("SELECT * FROM bar;", @db_name).should be_true
|
74
74
|
end
|
data/spec/helpers.rb
CHANGED
@@ -4,7 +4,7 @@ module Helpers
|
|
4
4
|
PUBLIC_KEY_ID = '4D5AA1CE74A97ADB'
|
5
5
|
|
6
6
|
def run_before
|
7
|
-
|
7
|
+
read_keys
|
8
8
|
|
9
9
|
FakeWeb.clean_registry
|
10
10
|
FakeWeb.passthrough_uri(:any, %r{^https?://s3.amazonaws.com/})
|
@@ -28,7 +28,7 @@ module Helpers
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def stub_configs
|
31
|
-
#
|
31
|
+
#print "in stub_configs: #{YAML::dump(@database_config)}, #{YAML::dump(spec_config)}\n"
|
32
32
|
config = @database_config || spec_config
|
33
33
|
config = config.merge({ :tmp_dir => tmp_dir })
|
34
34
|
# print "in stub_configs2: #{YAML::dump(config)}\n"
|
@@ -59,7 +59,7 @@ module Helpers
|
|
59
59
|
FileUtils.rm_f(backup_config_file)
|
60
60
|
filenames = Dir.glob("#{tmp_dir}/*")
|
61
61
|
if filenames.any?
|
62
|
-
raise "failed to remove #{filenames.join(' ')}"
|
62
|
+
raise "failed to remove #{filenames.join(', ')}"
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -82,7 +82,7 @@ module Helpers
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def mysql_password_option
|
85
|
-
mysql_password.
|
85
|
+
mysql_password.nil? ? "" : "-p#{mysql_password}"
|
86
86
|
end
|
87
87
|
|
88
88
|
def generate_database_name(type)
|
@@ -156,7 +156,7 @@ module Helpers
|
|
156
156
|
stdin.flush
|
157
157
|
stdin.close
|
158
158
|
|
159
|
-
|
159
|
+
_, status = Process::waitpid2 pid
|
160
160
|
|
161
161
|
status
|
162
162
|
end
|
@@ -166,7 +166,7 @@ module Helpers
|
|
166
166
|
end
|
167
167
|
|
168
168
|
def tmp_dir
|
169
|
-
if spec_config['tmp_dir'].
|
169
|
+
if spec_config['tmp_dir'].nil?
|
170
170
|
File.dirname(__FILE__) + "/tmp/"
|
171
171
|
else
|
172
172
|
spec_config['tmp_dir'] || File.dirname("./tmp/")
|
@@ -174,11 +174,11 @@ module Helpers
|
|
174
174
|
end
|
175
175
|
|
176
176
|
def mysql_user
|
177
|
-
spec_config['mysql_user'] ||
|
177
|
+
spec_config['mysql_user'] || "root"
|
178
178
|
end
|
179
179
|
|
180
180
|
def postgresql_user
|
181
|
-
spec_config['postgresql_user'] ||
|
181
|
+
spec_config['postgresql_user'] || "postgres"
|
182
182
|
end
|
183
183
|
|
184
184
|
def mysql_password
|
@@ -190,11 +190,11 @@ module Helpers
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def mysql_host
|
193
|
-
spec_config['mysql_host'] || '
|
193
|
+
spec_config['mysql_host'] || '127.0.0.1'
|
194
194
|
end
|
195
195
|
|
196
196
|
def postgresql_host
|
197
|
-
spec_config['postgresql_host'] || '
|
197
|
+
spec_config['postgresql_host'] || '127.0.0.1'
|
198
198
|
end
|
199
199
|
|
200
200
|
def created_mysql_dbs
|
@@ -215,8 +215,14 @@ module Helpers
|
|
215
215
|
|
216
216
|
def self.load_spec_config
|
217
217
|
filename = File.dirname(__FILE__) + '/config.yml'
|
218
|
+
|
218
219
|
if File.exist?(filename)
|
219
220
|
@spec_config = YAML.load_file(filename)
|
221
|
+
elsif Fog.mocking?
|
222
|
+
@spec_config = {
|
223
|
+
'aws_secret_id' => SecureRandom.base64(10),
|
224
|
+
'aws_secret_key' => SecureRandom.base64(20),
|
225
|
+
}
|
220
226
|
else
|
221
227
|
abort "Please setup your spec/config.yml file"
|
222
228
|
end
|