ey_cloud_server 1.4.47 → 1.4.49

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- if database_exists?(database_name)
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
- def database_exists?(database_name)
42
- runs?("PGPASSWORD='#{password}' psql -l -h #{host} | grep '#{database_name}'")
34
+ run(command, database_name)
43
35
  end
44
36
 
45
- def drop_database(database_name)
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
- EY::Backup.logger.fatal(%Q{ERROR: Target database has active connections. For more information, see "Restore or load a database" in docs.engineyard.com})
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
- spawn("PGPASSWORD='#{password}' dropdb -h #{host} -U#{username} #{database_name}")
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
- drop_database(database_name)
62
- create_database(database_name)
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
@@ -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
@@ -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 #{command.inspect}"
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
- pid, *_ = Open4.popen4(command)
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
 
@@ -6,4 +6,3 @@ module EY
6
6
  end
7
7
 
8
8
  require 'ey_cloud_server/version'
9
- require 'ey_cloud_server/mysql_start'
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module CloudServer
3
- VERSION = '1.4.47'
3
+ VERSION = '1.4.49'
4
4
  end
5
5
  end
@@ -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: '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'
@@ -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
@@ -4,7 +4,7 @@ module Helpers
4
4
  PUBLIC_KEY_ID = '4D5AA1CE74A97ADB'
5
5
 
6
6
  def run_before
7
- public_key, private_key = read_keys
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
- # print "in stub_configs: #{YAML::dump(@database_config)}, #{YAML::dump(spec_config)}\n"
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.blank? ? "" : "-p#{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
- ignored, status = Process::waitpid2 pid
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'].blank?
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'] || raise("No mysql user provided")
177
+ spec_config['mysql_user'] || "root"
178
178
  end
179
179
 
180
180
  def postgresql_user
181
- spec_config['postgresql_user'] || raise("No postgresql user provided")
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'] || 'localhost'
193
+ spec_config['mysql_host'] || '127.0.0.1'
194
194
  end
195
195
 
196
196
  def postgresql_host
197
- spec_config['postgresql_host'] || 'localhost'
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