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.
@@ -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