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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA512:
3
- metadata.gz: 8ea9f9720c697f5544e918ffc6efaa33294ee3b1ba9c5bf3b79e7dabb8fb7215aae5e9daea553db1d570785e0f86dc8522f81d2ad7ff8b720f3a7d53c523583d
4
- data.tar.gz: a2e1b80bc70a1a6c01ec14de5691f467a8cd1fe443155220fbfed4e58ddbed7d87543e2a84978c781b06991ecd482fd7759aa5b4c912dd7a2122f4f352c71cf9
5
- SHA1:
6
- metadata.gz: eae0875e369ab43aba8d8f8f8df4f73c718afcfb
7
- data.tar.gz: 959fdaab084788b57dc364842f57a8a90b3e9e44
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b322ebd431ff9f4bd27a8a908d145ad4e70f01e9
4
+ data.tar.gz: 4a67d4081f42712cd36e8d683f1b00f767196d7a
5
+ SHA512:
6
+ metadata.gz: 25bf1195e76d0042b379f7bc3aa9fb21e7ff30b5ab8b0b93cabc698c54032ec8dc15a20cd3b163e3d314fda03404310ca4e437fd2612d4d87e0714469b791499
7
+ data.tar.gz: 1211cea6558f7b4d1639bc686d24c015522fd35a69f55733b6cc7091a2a7e66ae2b920e597e48b009d99734701c19301d5188a306989f15074fe0b02744bb0d7
@@ -3,15 +3,15 @@ require File.dirname(__FILE__) + "/../../lib/ey_backup"
3
3
  require 'fakeweb'
4
4
  require 'fakefs/safe'
5
5
  require 'randexp'
6
+ require 'fog/aws'
7
+
8
+ Fog.mock!
6
9
 
7
10
  require File.dirname(__FILE__) + '/../../spec/fakefs_hax'
8
11
  require File.dirname(__FILE__) + '/../../spec/helpers'
9
12
 
10
13
  FakeWeb.allow_net_connect = false # if it's not here, it doesn't exist
11
14
  Randexp::Dictionary.words
12
- Fog.mock!
13
-
14
- Fog.mock!
15
15
 
16
16
  World(Helpers)
17
17
 
@@ -6,7 +6,6 @@ require 'fileutils'
6
6
  require 'json/ext'
7
7
  require 'open-uri'
8
8
  require 'rest_client'
9
- require 'dbi'
10
9
  require 'zlib'
11
10
  require 'stringio'
12
11
  require 'yaml'
@@ -29,7 +28,8 @@ module EY
29
28
  @enzyme_api ||= EY::Enzyme::API.new(
30
29
  enzyme_config[:api],
31
30
  enzyme_config[:instance_id],
32
- enzyme_config[:token]
31
+ enzyme_config[:token],
32
+ EY::Backup.logger
33
33
  )
34
34
  end
35
35
 
@@ -43,7 +43,6 @@ module EY
43
43
  module CloudServer; end
44
44
  end
45
45
 
46
- require lib_dir + '/big-brother'
47
46
  require lib_dir + '/bucket_minder'
48
47
  require lib_dir + '/ey-api'
49
48
  require lib_dir + '/snapshot_minder'
@@ -29,7 +29,7 @@ module EY
29
29
  def list
30
30
  @commands_called << "list_snapshots"
31
31
  [EY::InstanceAPIClient::Snapshot.new({
32
- 'state' => 'in progres',
32
+ 'state' => 'in progress',
33
33
  'progress' => '0%',
34
34
  'volume_type' => 'db',
35
35
  'snapshot_id' => 12,
@@ -6,7 +6,8 @@ require 'open4'
6
6
  require 'zlib'
7
7
  require 'ostruct'
8
8
  require 'fileutils'
9
- require 'fog'
9
+ require 'fog/aws'
10
+ require 'timeout'
10
11
 
11
12
  module Fog
12
13
  module AWS
@@ -72,6 +73,7 @@ module EY
72
73
 
73
74
  def restore
74
75
  databases.each do |database|
76
+ @engine.check_if_replica
75
77
  Loader.run(database, @options[:index])
76
78
  end
77
79
  end
@@ -110,6 +112,10 @@ module EY
110
112
  EY::Backup.logger ||= Logger.quiet
111
113
  else
112
114
  EY::Backup.logger ||= Logger.new
115
+
116
+ if @options[:verbose]
117
+ EY::Backup.logger.set_verbose
118
+ end
113
119
  end
114
120
  end
115
121
 
@@ -130,7 +136,7 @@ module EY
130
136
  if ! @options.key?(:dbhost) or @options[:dbhost] == nil or @options[:dbhost] == ""
131
137
  @options[:dbhost] = 'localhost'
132
138
  end
133
- @engine = engine_class.new(@options[:dbuser], @options[:dbpass], @options[:dbhost], @options[:key_id])
139
+ @engine = engine_class.new(@options[:dbuser], @options[:dbpass], @options[:dbhost], @options[:key_id], @options[:force])
134
140
  end
135
141
 
136
142
  def dispatch
@@ -1,4 +1,4 @@
1
- require 'fog'
1
+ require 'fog/aws'
2
2
 
3
3
  module EY
4
4
  module Backup
@@ -3,7 +3,7 @@ module EY
3
3
  class Base
4
4
  extend Forwardable
5
5
 
6
- def_delegators :logger, :fatal, :error, :warn, :info, :debug, :say
6
+ def_delegators :logger, :fatal, :error, :warn, :info, :verbose, :debug, :say
7
7
 
8
8
  def logger
9
9
  EY::Backup.logger
@@ -5,6 +5,7 @@ module EY
5
5
 
6
6
  def run(argv)
7
7
  options = default_options.merge(opt_parse(argv))
8
+ verify_restore if options[:command] == :restore and !options[:force]
8
9
 
9
10
  config_path = options[:config] || "/etc/.#{options[:engine]}.backups.yml"
10
11
 
@@ -16,8 +17,18 @@ module EY
16
17
  :command => :new_backup,
17
18
  :format => "gzip",
18
19
  :engine => 'mysql',
20
+ :verbose => false,
19
21
  }
20
22
  end
23
+
24
+ def verify_restore
25
+ res = ''
26
+ puts "This will overwrite your database, are you sure you would like to proceed (Y/n)?"
27
+ Timeout::timeout(30){
28
+ res = gets.strip
29
+ }
30
+ abort("You indicated '#{res}', exiting!") unless res.upcase == 'Y'
31
+ end
21
32
 
22
33
  def opt_parse(argv)
23
34
  # Build a parser for the command line arguments
@@ -25,12 +36,13 @@ module EY
25
36
 
26
37
  opts = OptionParser.new do |opts|
27
38
  opts.version = EY::CloudServer::VERSION
39
+ puts '' # add a blank line
28
40
 
29
41
  opts.banner = "Usage: eybackup [-flag] [argument]"
30
- opts.define_head "eybackup: manage dump (mysqldump/pg_dump) style backups of your database."
42
+ opts.define_head " eybackup: manage dump (mysqldump/pg_dump) style backups of your database."
31
43
  opts.separator '*'*80
32
44
 
33
- opts.on("-l", "--list-backup DATABASE", "List mysql backups for DATABASE") do |db|
45
+ opts.on("-l", "--list-backup DATABASE", "List backups for DATABASE") do |db|
34
46
  if db == "all"
35
47
  db = nil
36
48
  end
@@ -38,7 +50,10 @@ module EY
38
50
  options[:command] = :list
39
51
  end
40
52
 
41
- opts.on("-n", "--new-backup", "Create new mysql backup") do
53
+ opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgresql.") do |engine|
54
+ options[:engine] = engine
55
+ end
56
+ opts.on("-n", "--new-backup", "Create a new backup (default)") do
42
57
  options[:command] = :new_backup
43
58
  end
44
59
 
@@ -53,36 +68,49 @@ module EY
53
68
  opts.on("-t", "--tmp_dir TMPDIR", "Use the given directory for temporary storage.") do |tmp_dir|
54
69
  options[:tmp_dir] = tmp_dir
55
70
  end
71
+
72
+ opts.on("-k", "--key KEYID", "Public key ID to use for the backup operation") do |key_id|
73
+ options[:key_id] = key_id
74
+ end
75
+
76
+ opts.on("-q", "--quiet", "Suppress output to STDOUT") do
77
+ options[:quiet] = true
78
+ end
79
+
80
+ opts.on("-s", "--split_size INTEGER", "Maximum size of a single backup file before splitting.") do |split_size|
81
+ options[:split_size] = split_size.to_i
82
+ end
56
83
 
57
- opts.on("-d", "--download BACKUP_INDEX", "download the backup specified by index. Run eybackup -l to get the index.") do |index_and_db|
84
+ opts.on("-v", "--verbose", "Show verbose output") do
85
+ options[:verbose] = true
86
+ end
87
+
88
+ opts.on("-d", "--download BACKUP_INDEX",
89
+ "Download the backup specified by index.",
90
+ ' Run `eybackup -l #{db_name}` to get the index.',
91
+ ' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
58
92
  options[:command] = :download
59
93
  db, index = split_index(index_and_db)
60
94
  options[:index] = index
61
95
  options[:db] = db
62
96
  end
63
97
 
64
- opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgres.") do |engine|
65
- options[:engine] = engine
66
- end
67
-
68
- 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_and_db|
98
+ opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index.",
99
+ " **WARNING!** will overwrite the current db with the backup.",
100
+ ' Run `eybackup -l #{db_name}` to get the index.',
101
+ ' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
69
102
  options[:command] = :restore
70
103
  db, index = split_index(index_and_db)
71
104
  options[:index] = index
72
105
  options[:db] = db
73
106
  end
74
-
75
- opts.on("-k", "--key KEYID", "Public key ID to use for the backup operation") do |key_id|
76
- options[:key_id] = key_id
107
+
108
+ options[:force] = false
109
+ opts.on("-f", "--force", "Force backup restore, bypass confirmation prompts.",
110
+ " For use with automated restore operations (e.g. Staging).") do
111
+ options[:force] = true
77
112
  end
78
113
 
79
- opts.on("-q", "--quiet", "Supress output to STDOUT") do
80
- options[:quiet] = true
81
- end
82
-
83
- opts.on("-s", "--split_size INTEGER", "Maximum size of a single backup file before splitting.") do |split_size|
84
- options[:split_size] = split_size.to_i
85
- end
86
114
  end
87
115
 
88
116
  opts.parse!(argv)
@@ -11,8 +11,9 @@ module EY
11
11
  @backend = backend
12
12
  @environment = environment
13
13
  @name = name
14
+ @backup_size = nil
14
15
  end
15
- attr_reader :name, :engine, :environment, :keep, :base_path
16
+ attr_reader :name, :engine, :environment, :keep, :base_path, :backup_size
16
17
 
17
18
  def bucket_minder
18
19
  @backend.bucket_minder
@@ -28,6 +29,7 @@ module EY
28
29
 
29
30
  basename = generate_basename
30
31
  file = @engine.dump(@name, basename)
32
+ @backup_size = number_to_human_size(File.size(file))
31
33
 
32
34
  BackupSet.from(self, basename, file)
33
35
  end
@@ -43,6 +45,18 @@ module EY
43
45
  def timestamp
44
46
  Time.now.strftime("%Y-%m-%dT%H-%M-%S")
45
47
  end
48
+
49
+ def number_to_human_size(size)
50
+ if size < 1024
51
+ "#{size} bytes"
52
+ elsif size < 1024.0 * 1024.0
53
+ "%.01f KB" % (size / 1024.0)
54
+ elsif size < 1024.0 * 1024.0 * 1024.0
55
+ "%.01f MB" % (size / 1024.0 / 1024.0)
56
+ else
57
+ "%.01f GB" % (size / 1024.0 / 1024.0 / 1024.0)
58
+ end
59
+ end
46
60
  end
47
61
  end
48
62
  end
@@ -1,14 +1,40 @@
1
1
  module EY
2
2
  module Backup
3
3
  class Dumper < Base
4
+ require 'shellwords'
4
5
  include Logging
5
6
 
6
7
  attr_reader :database
7
8
 
8
9
  def self.run(databases, split_size)
10
+ exceptions = []
11
+ completed = []
9
12
  databases.each do |database|
10
- new(database).run(split_size)
13
+ begin
14
+ backup_size = new(database).run(split_size)
15
+ completed << "#{database.name} (#{backup_size})"
16
+ rescue => e
17
+ puts e
18
+ exceptions << database.name
19
+ next
20
+ end
11
21
  end
22
+ message = ''
23
+ message = message + "Failures: #{exceptions}" if ! exceptions.empty?
24
+ message = message + ", " if ! exceptions.empty? and ! completed.empty?
25
+ message = message + "Completed successfully: #{completed}" if ! completed.empty?
26
+
27
+ alert_level=exceptions.empty? ? 'OKAY' : 'FAILURE'
28
+
29
+ message.gsub!("\n", '\n')
30
+ full_txt= "Severity: #{alert_level}\n" \
31
+ + "Time: #{Time.now.to_i}\n" \
32
+ + "Type: process-dbbackup summary\n" \
33
+ + "Plugin: exec\n" \
34
+ + "raw_message: '#{message}'"
35
+ full_txt = Shellwords.escape(full_txt)
36
+ alert_command = %Q(echo #{full_txt} | /engineyard/bin/ey-alert.rb 2>&1)
37
+ system(alert_command)
12
38
  end
13
39
 
14
40
  def initialize(database)
@@ -16,16 +42,18 @@ module EY
16
42
  end
17
43
 
18
44
  def run(split_size)
19
- info "Doing database: #{@database.name}"
45
+ info("Doing database: #{@database.name}")
20
46
 
21
47
  backup_set = @database.dump
22
48
  backup_set.split!(split_size)
23
49
  backup_set.upload!
24
50
 
25
- info "Successful backup: #{@database.name}.#{backup_set.normalized_name}"
51
+ okay("Successful backup: #{@database.name} (#{@database.backup_size})", @database.name)
26
52
  backup_set.cleanup
27
53
  backup_set.rm!
54
+ @database.backup_size
28
55
  end
56
+
29
57
  end
30
58
  end
31
59
  end
@@ -3,7 +3,7 @@ module EY
3
3
  class Engine < Base
4
4
  include Spawner
5
5
 
6
- attr_reader :username, :password, :host, :key_id
6
+ attr_reader :username, :password, :host, :key_id, :force
7
7
 
8
8
  def self.label
9
9
  @label
@@ -26,8 +26,8 @@ module EY
26
26
  EY::Backup.logger.fatal("Unknown database engine: #{label}")
27
27
  end
28
28
 
29
- def initialize(username, password, host, key_id)
30
- @username, @password, @host, @key_id = username, password, host, key_id
29
+ def initialize(username, password, host, key_id, force)
30
+ @username, @password, @host, @key_id, @force = username, password, host, key_id, force
31
31
  end
32
32
 
33
33
  def gpg?
@@ -6,7 +6,7 @@ module EY
6
6
  def dump(database_name, basename)
7
7
  file = basename + '.sql'
8
8
 
9
- command = "mysqldump #{username_option} #{host_option} #{password_option} #{single_transaction_option(database_name)} #{routines_option} #{database_name}"
9
+ command = "mysqldump #{username_option} #{host_option} #{single_transaction_option(database_name)} #{routines_option} #{master_data_option} #{databases_option(database_name)} 2> /tmp/eybackup.$$.dumperr"
10
10
 
11
11
  if gpg?
12
12
  command << " | " << GPGEncryptor.command_for(key_id)
@@ -18,12 +18,13 @@ module EY
18
18
 
19
19
  command << " > #{file}"
20
20
 
21
- run(command)
21
+ run(command, database_name)
22
22
 
23
23
  file
24
24
  end
25
25
 
26
26
  def load(database_name, file)
27
+ cycle_database(database_name)
27
28
  command = "cat #{file}"
28
29
 
29
30
  if gpg?
@@ -32,17 +33,30 @@ module EY
32
33
  command << " | " << GZipper.gunzip
33
34
  end
34
35
 
35
- command << " | mysql #{username_option} #{host_option} #{password_option} #{database_name}"
36
+ command << " | mysql #{username_option} #{host_option} #{database_name}"
36
37
 
37
38
  run(command)
38
39
  end
40
+
41
+ def check_if_replica
42
+ err_msg="ERROR: Target host: '#{host}' is currently: "
43
+ err_msg=err_msg + "a replica based on 'show slave status', " if db_replicating?
44
+ err_msg=err_msg + "in read_only mode, " if read_only_on?
45
+ err_msg=err_msg + "restores should be processed against the master."
46
+
47
+ EY::Backup.logger.fatal(%Q{#{err_msg}}) if db_replicating? or read_only_on?
48
+ end
39
49
 
40
50
  def routines_option
41
51
  "--routines"
42
52
  end
43
-
44
- def password_option
45
- "-p'#{password}'" unless password.nil? || password.empty?
53
+
54
+ def master_data_option
55
+ "--master-data=2" if log_bin_on?
56
+ end
57
+
58
+ def databases_option(db)
59
+ "--add-drop-database --databases #{db}"
46
60
  end
47
61
 
48
62
  def host_option
@@ -59,9 +73,37 @@ module EY
59
73
 
60
74
  def db_has_myisam?(database_name)
61
75
  query, stdout = "SELECT 1 FROM information_schema.tables WHERE table_schema='#{database_name}' AND engine='MyISAM' LIMIT 1;", StringIO.new
62
- spawn(%Q{mysql #{username_option} #{password_option} #{host_option} -N -e"#{query}"}, stdout)
76
+ spawn(%Q{mysql #{username_option} #{host_option} -N -e"#{query}"}, stdout)
63
77
  stdout.string.strip == '1'
64
78
  end
79
+
80
+ def db_replicating?
81
+ stdout = StringIO.new
82
+ spawn(%Q{mysql #{username_option} #{host_option} -BN -e"show slave status"|wc -l}, stdout)
83
+ stdout.string.to_i > 0
84
+ end
85
+
86
+ def read_only_on?
87
+ stdout = StringIO.new
88
+ spawn(%Q{mysql #{username_option} #{host_option} -BN -e"select @@global.read_only"}, stdout)
89
+ stdout.string.to_i == 1
90
+ end
91
+
92
+ def log_bin_on?
93
+ stdout = StringIO.new
94
+ spawn(%Q{mysql #{username_option} #{host_option} -BN -e"select @@global.log_bin"}, stdout)
95
+ stdout.string.to_i == 1
96
+ end
97
+
98
+ def cycle_database(database_name)
99
+ query = "show create database #{database_name} \\G"
100
+ create_cmd = %x(mysql #{username_option} #{host_option} -NB -e "#{query}"|tail -n 1)
101
+ create_cmd="Create database #{database_name}" if create_cmd==""
102
+
103
+ %x(mysql #{username_option} #{host_option} -e 'DROP DATABASE IF EXISTS #{database_name}')
104
+ %x(mysql #{username_option} #{host_option} -e '#{create_cmd}')
105
+
106
+ end
65
107
 
66
108
  def suffix
67
109
  /sql\.(gz|gpz)$/