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 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)$/