drebs 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +32 -7
- data/Rakefile +7 -6
- data/bin/drebs +105 -225
- data/config/example.yml +28 -0
- data/drebs.gemspec +26 -10
- data/lib/drebs.rb +8 -7
- data/lib/drebs/cloud.rb +91 -0
- data/lib/drebs/main.rb +179 -0
- data/test/helper.rb +126 -0
- data/test/unit/drebs/drebs_test.rb +11 -0
- data/test/unit/drebs/main_test.rb +44 -0
- data/tmp_test_data/db.sqlite +0 -0
- metadata +64 -27
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NDE5YTM0ZDEwMWJlNmZiMmY4NzQ2ZjhjOTI4NWQ0ODc4ZWExNzRmNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDhlYmU3ZWFiYThiZTdiYmYxOTZiZjRiMWQ0NDM0YjEyN2VkZmM2Zg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NTlhYzQ1NWVhYzliOWE3YTlkNmNlMDlkMjQ2ZTdjNjY1NzE1OTViZjA4NDRi
|
10
|
+
ZDQ0MTM1NDFiZGI1YTFmYTAwNDBkMWQwY2Y2NTJiODZjNTY3MmM2NzI0MzA1
|
11
|
+
MGRiOTE0M2VkMzAwYTY4ODlmM2IxMzFjYmZiMDUyZGE3OTNhY2Q=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTAxZTc1NzUwNjllNTlmMzgzYTMwODQ0NTNjOWE2ZGNlYTdlY2I3MzkyYWU2
|
14
|
+
NjJjOWY0NTE0ZGE3NWE4NDAyZmUxNjBlYjVlYWQxODM5MTVlNGRkZWQ3Yzg3
|
15
|
+
NjRhZTM0NDFmY2UwOTk2YTI0MGQyMTVhMDA0YzAwMzUxNmY0MTg=
|
data/README.md
CHANGED
@@ -8,14 +8,39 @@
|
|
8
8
|
|
9
9
|
## Installation & Setup
|
10
10
|
1. Clone the repo or install the gem
|
11
|
-
1.
|
12
|
-
1.
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
1. Output the example configuration to a file: drebs check_config example_config > your_config.yml
|
12
|
+
1. Create an AWS account with authorization limited to create, list, & delete snapshots (Example comming soon)
|
13
|
+
1. Add AWS API keys for above account to your_config.yml
|
14
|
+
1. configure your_config.yml per your backup requirements
|
15
|
+
1. test your configuration: drebs check_config your_config.yml && drebs check_cloud your_config.yml
|
16
|
+
1. Add Crontab entry: 0 * * * * drebs execute your_config.yml
|
16
17
|
|
17
18
|
## Todo
|
18
|
-
*
|
19
|
-
* Refactor using main with db for state and external config
|
19
|
+
* Improve test coverage
|
20
20
|
* Use Whenever gem for crontab setup
|
21
21
|
* Arbitrary execution intervals (Snapshots every 5 minutes instead of every hour)
|
22
|
+
* AWS API keys and other config values from Instance Data: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html
|
23
|
+
* Add example AWS user access configuration
|
24
|
+
* Implement support for pruning to occur from some other host so that the keys to delete snapshots are not available on host.
|
25
|
+
|
26
|
+
## Testing notes
|
27
|
+
|
28
|
+
* __shell command__: If you do: `drebs shell some_config` you will end up at a shell with `@drebs` defined and you will be able to access `@drebs.db`, `@drebs.config`, & `@drebs.cloud`. If you set `@drebs.cloud` to be an instance of TestCloud from the test suite you should be able to execute various functions without actually hitting AWS and so work from your dev box.
|
29
|
+
|
30
|
+
* Due to the nature of drebs being designed to be run from an ec2 you will need to be on your ec2 instance to test many of the AWS interactions.
|
31
|
+
|
32
|
+
* You should be able to verify data on a snapshot by creating an ebs volume from the snapshot, attaching the volume to your instance and then mounting its file system on some mount point - [aws docs on using volumes](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-using-volumes.html)
|
33
|
+
|
34
|
+
## Copyright 2014 [dojo4](www.dojo4.com)
|
35
|
+
|
36
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
37
|
+
you may not use this file except in compliance with the License.
|
38
|
+
You may obtain a copy of the License at
|
39
|
+
|
40
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
41
|
+
|
42
|
+
Unless required by applicable law or agreed to in writing, software
|
43
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
44
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
45
|
+
See the License for the specific language governing permissions and
|
46
|
+
limitations under the License.
|
data/Rakefile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
This.rubyforge_project = 'DREBS'
|
2
|
-
This.
|
3
|
-
This.email = "
|
2
|
+
This.authors = ["Garett Shulman", "Miles Matthias"]
|
3
|
+
This.email = "miles@dojo4.com"
|
4
4
|
This.homepage = "https://github.com/dojo4/#{ This.lib }"
|
5
|
-
|
5
|
+
This.licenses = "Apache-2.0"
|
6
6
|
|
7
7
|
task :default do
|
8
8
|
puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
|
@@ -27,6 +27,7 @@ def run_tests!(which = nil)
|
|
27
27
|
div = ('=' * 119)
|
28
28
|
line = ('-' * 119)
|
29
29
|
|
30
|
+
puts "running #{ test_rbs.count } test files..."
|
30
31
|
test_rbs.each_with_index do |test_rb, index|
|
31
32
|
testno = index + 1
|
32
33
|
command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
|
@@ -56,7 +57,6 @@ def run_tests!(which = nil)
|
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
60
|
task :gemspec do
|
61
61
|
ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
|
62
62
|
ignore_directories = ['pkg', 'db']
|
@@ -89,7 +89,7 @@ task :gemspec do
|
|
89
89
|
#has_rdoc = true #File.exist?('doc')
|
90
90
|
test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
|
91
91
|
summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
|
92
|
-
description = object.respond_to?(:description) ? object.description : "
|
92
|
+
description = object.respond_to?(:description) ? object.description : "#{ lib }: Disaster Recovery for Elastic Block Store. An AWS EBS backup script."
|
93
93
|
|
94
94
|
if This.extensions.nil?
|
95
95
|
This.extensions = []
|
@@ -141,9 +141,10 @@ spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
|
|
141
141
|
spec.extensions.push(*<%= extensions.inspect %>)
|
142
142
|
|
143
143
|
spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
|
144
|
-
spec.
|
144
|
+
spec.authors = <%= This.authors.inspect %>
|
145
145
|
spec.email = <%= This.email.inspect %>
|
146
146
|
spec.homepage = <%= This.homepage.inspect %>
|
147
|
+
spec.licenses = <%= This.licenses.inspect %>
|
147
148
|
end
|
148
149
|
__
|
149
150
|
}
|
data/bin/drebs
CHANGED
@@ -1,249 +1,129 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'right_aws'
|
5
|
-
require 'logger'
|
6
4
|
require 'main'
|
5
|
+
require 'logger'
|
7
6
|
require 'systemu'
|
8
|
-
require '
|
7
|
+
require 'yaml'
|
9
8
|
require 'socket'
|
10
9
|
require 'net/smtp'
|
10
|
+
require 'pry'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
'pre_snapshot_tasks'=> [
|
24
|
-
'pg_dump some_app_production > /path/to/backups/on/snapshoted/volume/some_app_production.sql',
|
25
|
-
'mongodump -d another_app-production -o /path/to/backups/on/snapshoted/volume/'
|
26
|
-
]
|
27
|
-
},
|
28
|
-
{
|
29
|
-
'hours_between'=>6, 'num_to_keep'=>4,
|
30
|
-
'mount_point'=>'/dev/sdh',
|
31
|
-
'pre_snapshot_tasks'=> []
|
32
|
-
},
|
33
|
-
{
|
34
|
-
'hours_between'=>24, 'num_to_keep'=>4,
|
35
|
-
'mount_point'=>'/dev/sda1',
|
36
|
-
'pre_snapshot_tasks'=> []
|
37
|
-
},
|
38
|
-
{
|
39
|
-
'hours_between'=>96, 'num_to_keep'=>4,
|
40
|
-
'mount_point'=>'/dev/sda1',
|
41
|
-
'pre_snapshot_tasks'=> []
|
42
|
-
}
|
43
|
-
]
|
44
|
-
LOG_PATH = '/usr/local/var/drebs.log'
|
45
|
-
BACKUP_STATE_FILE_PATH = '/usr/local/var/drebs_state.json'
|
46
|
-
EMAIL_ON_EXCEPTION = 'admin@your.org'
|
47
|
-
EMAIL_HOST = 'imap.gmail.com'
|
48
|
-
EMAIL_PORT = 993
|
49
|
-
EMAIL_USERNAME = 'your smpt username'
|
50
|
-
EMAIL_PASSWORD = 'your smpt password'
|
51
|
-
###### End User Config
|
52
|
-
|
53
|
-
def initialize(options = {})
|
54
|
-
@drebs_host_name = options[:drebs_host_name] || options['drebs_host_name'] || DREBS_HOST_NAME
|
55
|
-
@aws_access_key_id = options[:aws_access_key_id] || options['aws_access_key_id'] || AWS_ACCESS_KEY_ID
|
56
|
-
@aws_secret_access_key = options[:aws_secret_access_key] || options['aws_secret_access_key'] || AWS_SECRET_ACCESS_KEY
|
57
|
-
@region = options[:region] || options['region'] || REGION
|
58
|
-
@backup_strategy = options[:backup_strategy] || options['backup_strategy'] || BACKUP_STRATEGY
|
59
|
-
@log_path = options[:log_path] || options['log_path'] || LOG_PATH
|
60
|
-
@backup_state_file_path = options[:backup_state_file_path] || options['backup_state_file_path'] || BACKUP_STATE_FILE_PATH
|
61
|
-
@email_on_exception = options[:email_on_exception] || options['email_on_exception'] || EMAIL_ON_EXCEPTION
|
62
|
-
@log = Logger.new(@log_path, 0, 10 * 1024 * 1024)
|
63
|
-
end
|
64
|
-
|
65
|
-
def setup_backup_data(backup_strategy=@backup_strategy)
|
66
|
-
backup_data = Marshal.load(Marshal.dump(@backup_strategy))
|
67
|
-
backup_data.collect{|a_backup_strategy|
|
68
|
-
a_backup_strategy['hours_until_next_run'] = a_backup_strategy['hours_between']
|
69
|
-
a_backup_strategy['previous_snapshots'] = []
|
70
|
-
}
|
71
|
-
return backup_data
|
72
|
-
end
|
73
|
-
|
74
|
-
def save_backup_data()
|
75
|
-
open @backup_state_file_path, "w" do |h|
|
76
|
-
h.write(@backup_data.to_json)
|
12
|
+
Main {
|
13
|
+
|
14
|
+
argument('config_path'){
|
15
|
+
argument :optional
|
16
|
+
description "A configuration file path. Passing 'example_config' uses an example configuration. Give it a try: >drebs check_config example_config"
|
17
|
+
}
|
18
|
+
|
19
|
+
mode 'execute' do
|
20
|
+
def run
|
21
|
+
setup
|
22
|
+
@drebs.execute
|
77
23
|
end
|
78
24
|
end
|
79
|
-
|
80
|
-
|
81
|
-
|
25
|
+
|
26
|
+
# mode 'install_crontab' do
|
27
|
+
# def run
|
28
|
+
# setup
|
29
|
+
# @drebs.install_crontab
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
|
33
|
+
mode 'check_config' do
|
34
|
+
def run
|
35
|
+
setup
|
36
|
+
puts("No config errors found!")
|
37
|
+
end
|
82
38
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
39
|
+
|
40
|
+
mode 'check_cloud' do
|
41
|
+
def run
|
42
|
+
setup
|
43
|
+
@drebs.check_cloud
|
44
|
+
end
|
86
45
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
return nil
|
46
|
+
|
47
|
+
mode 'shell' do
|
48
|
+
def run
|
49
|
+
setup
|
50
|
+
binding.pry(:hooks => Pry::Hooks.new, :prompt => proc{|*a| "drebs>"})
|
51
|
+
end
|
94
52
|
end
|
95
|
-
|
96
|
-
def
|
97
|
-
|
98
|
-
local_instance[:block_device_mappings].each {|volume|
|
99
|
-
return volume if volume[:device_name] == mount_point
|
100
|
-
}
|
101
|
-
return nil
|
53
|
+
|
54
|
+
def run
|
55
|
+
help!
|
102
56
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
57
|
+
|
58
|
+
db {
|
59
|
+
create_table :strategies do
|
60
|
+
String :config
|
61
|
+
String :snapshots
|
62
|
+
String :status
|
63
|
+
String :time_til_next_run
|
64
|
+
String :time_between_runs
|
65
|
+
String :num_to_keep
|
66
|
+
String :pre_snapshot_tasks
|
67
|
+
String :post_snapshot_tasks
|
68
|
+
String :mount_point
|
69
|
+
end unless table_exists? :strategies
|
70
|
+
}
|
71
|
+
|
72
|
+
def setup()
|
73
|
+
unless config_path = params['config_path'].value
|
74
|
+
raise 'Please provide a value for config_path.'
|
75
|
+
end
|
76
|
+
@config = load_config(config_path)
|
77
|
+
|
78
|
+
require File.join(lib_dir(), 'drebs', 'cloud.rb')
|
79
|
+
require File.join(lib_dir(), 'drebs', 'main.rb')
|
80
|
+
|
81
|
+
config_errors = Drebs::Main.check_config(example_config('print' => false), @config)
|
82
|
+
if config_errors.length == 0
|
83
|
+
@drebs = Drebs::Main.new('config' => @config, 'db' => db)
|
84
|
+
else
|
85
|
+
config_errors.each{|config_error| puts(config_error)}
|
86
|
+
end
|
108
87
|
end
|
109
|
-
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
@log.error(error_string)
|
121
|
-
send_email("DREBS Error!", error_string)
|
88
|
+
|
89
|
+
def base_dir(*args, &block)
|
90
|
+
basedir = File.dirname(File.expand_path(__FILE__))
|
91
|
+
File.join(basedir, *args)
|
92
|
+
ensure
|
93
|
+
if block
|
94
|
+
begin
|
95
|
+
$LOAD_PATH.unshift(basedir)
|
96
|
+
block.call()
|
97
|
+
ensure
|
98
|
+
$LOAD_PATH.shift()
|
122
99
|
end
|
123
|
-
end
|
124
|
-
snapshot = ec2.create_snapshot(ebs[:ebs_volume_id], "DREBS #{@drebs_host_name} #{ip}:#{mount_point} #{instance_id}:#{volume_id}")
|
125
|
-
Thread.new(snapshot[:aws_id], post_snapshot_tasks) {|snapshot_id, post_snapshot_tasks|
|
126
|
-
1.upto(500) {|a|
|
127
|
-
sleep(3)
|
128
|
-
break if get_snapshot(snapshot_id)[:aws_status] == 'completed'
|
129
|
-
}
|
130
|
-
post_snapshot_tasks.each do |task|
|
131
|
-
result = systemu(task)
|
132
|
-
unless result[0].exitstatus == 0
|
133
|
-
error_string = "Error while executing post-snapshot task: #{task} on #{@drebs_host_name} #{ip}:#{mount_point} #{instance_id}:#{volume_id} #{result[1]} #{result[2]}"
|
134
|
-
@log.error(error_string)
|
135
|
-
send_email("DREBS Error!", error_string)
|
136
|
-
end
|
137
|
-
end if post_snapshot_tasks
|
138
|
-
}
|
139
|
-
return snapshot
|
140
|
-
end
|
141
|
-
|
142
|
-
def find_local_snapshots(mount_point='/dev/sdh')
|
143
|
-
return nil if not ebs = find_local_ebs(mount_point)
|
144
|
-
snapshots = []
|
145
|
-
ec2.describe_snapshots.each {|snapshot|
|
146
|
-
snapshots.push(snapshot) if snapshot[:aws_volume_id] == ebs[:ebs_volume_id]
|
147
|
-
}
|
148
|
-
return snapshots
|
100
|
+
end
|
149
101
|
end
|
150
|
-
|
151
|
-
def
|
152
|
-
|
153
|
-
backup_data.collect {|a_backup_strategy|
|
154
|
-
if a_backup_strategy['previous_snapshots'].count > a_backup_strategy['num_to_keep']
|
155
|
-
to_prune[a_backup_strategy['previous_snapshots'].shift] = nil
|
156
|
-
end
|
157
|
-
}
|
158
|
-
to_prune.each_key {|snapshot_to_prune|
|
159
|
-
ec2.delete_snapshot(snapshot_to_prune) unless backup_data.any? {|a_backup_strategy|
|
160
|
-
a_backup_strategy['previous_snapshots'].include?(snapshot_to_prune)
|
161
|
-
}
|
162
|
-
}
|
102
|
+
|
103
|
+
def lib_dir(&block)
|
104
|
+
base_dir(['..', 'lib'], &block)
|
163
105
|
end
|
164
|
-
|
165
|
-
def
|
166
|
-
|
167
|
-
port = options[:email_port] || options['email_port'] || EMAIL_PORT
|
168
|
-
username = options[:email_username] || options['email_username'] || EMAIL_USERNAME
|
169
|
-
password = options[:email_password] || options['email_password'] || EMAIL_PASSWORD
|
170
|
-
|
171
|
-
|
172
|
-
msg = "Subject: #{subject}\n\n#{body}"
|
173
|
-
smtp = Net::SMTP.new 'smtp.gmail.com', 587
|
174
|
-
smtp.enable_starttls
|
175
|
-
smtp.start('gmail.com', username, password, :login) {|smtp|
|
176
|
-
smtp.send_message(msg, username, @email_on_exception)
|
177
|
-
}
|
106
|
+
|
107
|
+
def config_dir(&block)
|
108
|
+
base_dir(['..', 'config'], &block)
|
178
109
|
end
|
179
110
|
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
mount_points.map do |mount_point|
|
188
|
-
strategies = @backup_data.select{|strategy| strategy['mount_point'] == mount_point}
|
189
|
-
pre_snapshot_tasks = strategies.map {|strategy| strategy['pre_snapshot_tasks']}.compact.flatten.uniq
|
190
|
-
post_snapshot_tasks = strategies.map {|strategy| strategy['post_snapshot_tasks']}.compact.flatten.uniq
|
191
|
-
unless strategies.empty?
|
192
|
-
@log.info("creating snapshot of #{mount_point}")
|
193
|
-
snapshot = create_local_snapshot(pre_snapshot_tasks, post_snapshot_tasks, mount_point)
|
194
|
-
end
|
195
|
-
strategies.collect {|strategy|
|
196
|
-
strategy['previous_snapshots'].push(snapshot[:aws_id])
|
197
|
-
}
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
save_backup_data
|
202
|
-
else
|
203
|
-
load_backup_data
|
204
|
-
@backup_data.collect {|strategy|
|
205
|
-
strategy['hours_until_next_run'] -= 1
|
206
|
-
}
|
207
|
-
backup_now = @backup_data.collect {|strategy| strategy if strategy['hours_until_next_run'] <= 0}.compact
|
208
|
-
|
209
|
-
|
210
|
-
mount_points = @backup_data.map {|strategy| strategy['mount_point']}.compact.flatten.uniq
|
211
|
-
|
212
|
-
mount_points.map do |mount_point|
|
213
|
-
strategies = @backup_data.select{|strategy| strategy['mount_point'] == mount_point}
|
214
|
-
pre_snapshot_tasks = strategies.map {|strategy| strategy['pre_snapshot_tasks']}.compact.flatten.uniq
|
215
|
-
post_snapshot_tasks = strategies.map {|strategy| strategy['post_snapshot_tasks']}.compact.flatten.uniq
|
216
|
-
unless strategies.empty?
|
217
|
-
@log.info("creating snapshot of #{mount_point}")
|
218
|
-
snapshot = create_local_snapshot(pre_snapshot_tasks, post_snapshot_tasks, mount_point)
|
219
|
-
end
|
220
|
-
strategies.collect {|strategy|
|
221
|
-
strategy['previous_snapshots'].push(snapshot[:aws_id])
|
222
|
-
strategy['hours_until_next_run'] = strategy['hours_between']
|
223
|
-
}
|
224
|
-
|
225
|
-
end
|
226
|
-
|
227
|
-
prune_backups(@backup_data)
|
228
|
-
save_backup_data
|
229
|
-
end
|
230
|
-
rescue Exception => error
|
231
|
-
@log.error("Exception occured during backup: #{error.message}\n#{error.backtrace.join("\n")}")
|
232
|
-
send_email("DREBS Error! on #{@drebs_host_name}", "Host: #{@drebs_host_name} AWS Instance: #{find_local_instance[:aws_instance_id]}\n#{error.message}\n#{error.backtrace.join("\n")}")
|
111
|
+
def example_config(params)
|
112
|
+
config_path = File.join(config_dir, 'example.yml')
|
113
|
+
example_config = IO.read(config_path)
|
114
|
+
if params['print']
|
115
|
+
puts("Example YAML Config File (#{config_path}):")
|
116
|
+
puts(example_config)
|
117
|
+
puts()
|
233
118
|
end
|
119
|
+
YAML::load(example_config)
|
234
120
|
end
|
235
121
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
Main {
|
242
|
-
def run
|
243
|
-
drebs = DREBS.new
|
244
|
-
drebs.run_drebs_cron
|
122
|
+
def load_config(config_path)
|
123
|
+
if config_path == 'example_config'
|
124
|
+
example_config('print' => true)
|
125
|
+
else
|
126
|
+
YAML::load(IO.read(config_path))
|
245
127
|
end
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
__END__
|
128
|
+
end
|
129
|
+
}
|
data/config/example.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
---
|
2
|
+
aws_access_key_id: EXAMPLE_KEY_ID
|
3
|
+
aws_secret_access_key: EXAMPLE_ACCESS_KEY
|
4
|
+
region: us-west-1
|
5
|
+
strategies:
|
6
|
+
- hours_between: 1
|
7
|
+
num_to_keep: 5
|
8
|
+
mount_point: /dev/sdh
|
9
|
+
pre_snapshot_tasks:
|
10
|
+
- pg_dump some_app_prodction > /path/to/backups/on/volume/being/snapshoted
|
11
|
+
- mongodump --db some_app_prodction --out /path/to/backups/on/volume/being/snapshoted
|
12
|
+
post_snapshot_tasks:
|
13
|
+
- hours_between: 6
|
14
|
+
num_to_keep: 4
|
15
|
+
mount_point: /dev/sdh
|
16
|
+
- hours_between: 24
|
17
|
+
num_to_keep: 4
|
18
|
+
mount_point: /dev/sda1
|
19
|
+
- hours_between: 96
|
20
|
+
num_to_keep: 4
|
21
|
+
mount_point: /dev/sda1
|
22
|
+
log_path: /usr/local/var/drebs.log
|
23
|
+
email_on_exception: admin@your.org
|
24
|
+
email_host: 'smtp.gmail.com'
|
25
|
+
email_port: 587
|
26
|
+
email_domain: 'gmail.com'
|
27
|
+
email_user: = 'Your smtp username'
|
28
|
+
email_password: = 'Your smtp password'
|
data/drebs.gemspec
CHANGED
@@ -3,19 +3,32 @@
|
|
3
3
|
|
4
4
|
Gem::Specification::new do |spec|
|
5
5
|
spec.name = "drebs"
|
6
|
-
spec.version = "0.0
|
6
|
+
spec.version = "0.1.0"
|
7
7
|
spec.platform = Gem::Platform::RUBY
|
8
8
|
spec.summary = "drebs"
|
9
|
-
spec.description = "
|
9
|
+
spec.description = "drebs: Disaster Recovery for Elastic Block Store. An AWS EBS backup script."
|
10
10
|
|
11
11
|
spec.files =
|
12
12
|
["README.md",
|
13
13
|
"Rakefile",
|
14
14
|
"bin",
|
15
15
|
"bin/drebs",
|
16
|
+
"config",
|
17
|
+
"config/example.yml",
|
16
18
|
"drebs.gemspec",
|
17
19
|
"lib",
|
18
|
-
"lib/drebs
|
20
|
+
"lib/drebs",
|
21
|
+
"lib/drebs.rb",
|
22
|
+
"lib/drebs/cloud.rb",
|
23
|
+
"lib/drebs/main.rb",
|
24
|
+
"test",
|
25
|
+
"test/helper.rb",
|
26
|
+
"test/unit",
|
27
|
+
"test/unit/drebs",
|
28
|
+
"test/unit/drebs/drebs_test.rb",
|
29
|
+
"test/unit/drebs/main_test.rb",
|
30
|
+
"tmp_test_data",
|
31
|
+
"tmp_test_data/db.sqlite"]
|
19
32
|
|
20
33
|
spec.executables = ["drebs"]
|
21
34
|
spec.require_path = "lib"
|
@@ -23,21 +36,24 @@ spec.require_path = "lib"
|
|
23
36
|
spec.test_files = nil
|
24
37
|
|
25
38
|
|
26
|
-
spec.add_dependency(*["right_aws", "
|
39
|
+
spec.add_dependency(*["right_aws", ">= 3.1.0"])
|
27
40
|
|
28
|
-
spec.add_dependency(*["logger", "
|
41
|
+
spec.add_dependency(*["logger", ">= 1.2.8"])
|
29
42
|
|
30
|
-
spec.add_dependency(*["main", "
|
43
|
+
spec.add_dependency(*["main", ">= 5.2.0"])
|
31
44
|
|
32
|
-
spec.add_dependency(*["systemu", "
|
45
|
+
spec.add_dependency(*["systemu", ">= 2.4.2"])
|
33
46
|
|
34
|
-
spec.add_dependency(*["json", "
|
47
|
+
spec.add_dependency(*["json", ">= 1.5.1"])
|
48
|
+
|
49
|
+
spec.add_dependency(*["pry", ">= 0.9.12.6"])
|
35
50
|
|
36
51
|
|
37
52
|
spec.extensions.push(*[])
|
38
53
|
|
39
54
|
spec.rubyforge_project = "DREBS"
|
40
|
-
spec.
|
41
|
-
spec.email = "
|
55
|
+
spec.authors = ["Garett Shulman", "Miles Matthias"]
|
56
|
+
spec.email = "miles@dojo4.com"
|
42
57
|
spec.homepage = "https://github.com/dojo4/drebs"
|
58
|
+
spec.licenses = "Apache-2.0"
|
43
59
|
end
|
data/lib/drebs.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# DREBS libs
|
6
6
|
#
|
7
7
|
module DREBS
|
8
|
-
Version = '0.0
|
8
|
+
Version = '0.1.0' unless defined?(Version)
|
9
9
|
|
10
10
|
def version
|
11
11
|
DREBS::Version
|
@@ -13,12 +13,13 @@
|
|
13
13
|
|
14
14
|
def dependencies
|
15
15
|
{
|
16
|
-
'right_aws' => [
|
17
|
-
'logger' => [
|
18
|
-
'main' => [
|
19
|
-
'systemu' => [
|
20
|
-
'json' => [
|
21
|
-
|
16
|
+
'right_aws' => ['right_aws', '>= 3.1.0'],
|
17
|
+
'logger' => ['logger' , '>= 1.2.8'],
|
18
|
+
'main' => ['main' , '>= 5.2.0'],
|
19
|
+
'systemu' => ['systemu' , '>= 2.4.2'],
|
20
|
+
'json' => ['json' , '>= 1.5.1'],
|
21
|
+
'pry' => ['pry' , '>= 0.9.12.6'],
|
22
|
+
}
|
22
23
|
end
|
23
24
|
|
24
25
|
def libdir(*args, &block)
|
data/lib/drebs/cloud.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'right_aws'
|
2
|
+
|
3
|
+
module Drebs
|
4
|
+
class Cloud
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_cloud
|
10
|
+
ec2
|
11
|
+
find_local_instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def ec2
|
15
|
+
key_id = @config["aws_access_key_id"]
|
16
|
+
key = @config["aws_secret_access_key"]
|
17
|
+
region = @config["region"]
|
18
|
+
return RightAws::Ec2.new(key_id, key, {:region=>region})
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_local_instance
|
22
|
+
#find a better way... right-aws?
|
23
|
+
private_ip = UDPSocket.open{|s| s.connect("8.8.8.8", 1); s.addr.last}
|
24
|
+
ec2.describe_instances.each do |instance|
|
25
|
+
return instance if instance[:private_ip_address] == private_ip
|
26
|
+
end
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_local_ebs(mount_point)
|
31
|
+
return nil if not local_instance = find_local_instance
|
32
|
+
local_instance[:block_device_mappings].each do |volume|
|
33
|
+
return volume if volume[:device_name] == mount_point
|
34
|
+
end
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def local_ebs_ids
|
39
|
+
@ebs_ids ||= find_local_instance[:block_device_mappings].map do |volume|
|
40
|
+
volume[:ebs_volume_id]
|
41
|
+
end rescue nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_snapshot(snapshot_id)
|
45
|
+
ec2.describe_snapshots {|a_snapshot|
|
46
|
+
return a_snapshot if a_snapshot[:aws_id] == snapshot_id
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_local_snapshot(pre_snapshot_tasks, post_snapshot_tasks, mount_point)
|
51
|
+
local_instance=find_local_instance
|
52
|
+
ip = local_instance[:ip_address]
|
53
|
+
instance_id = local_instance[:aws_instance_id]
|
54
|
+
volume_id = local_instance[:block_device_mappings].select{|m| m[:device_name]==mount_point}.first[:ebs_volume_id]
|
55
|
+
return nil if not ebs = find_local_ebs(mount_point)
|
56
|
+
pre_snapshot_tasks.each do |task|
|
57
|
+
result, stdout, stderr = systemu(task)
|
58
|
+
unless result.exitstatus == 0
|
59
|
+
raise Exception(
|
60
|
+
"Error while executing pre-snapshot task: #{task} on #{ip}:#{mount_point} #{instance_id}:#{volume_id} "
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end if pre_snapshot_tasks
|
64
|
+
snapshot = ec2.create_snapshot(ebs[:ebs_volume_id], "DREBS #{ip}:#{mount_point} #{instance_id}:#{volume_id}")
|
65
|
+
Thread.new(snapshot[:aws_id], post_snapshot_tasks) do |snapshot_id, post_snapshot_tasks|
|
66
|
+
1.upto(500) do |a|
|
67
|
+
sleep(3)
|
68
|
+
break if get_snapshot(snapshot_id)[:aws_status] == 'completed'
|
69
|
+
end
|
70
|
+
post_snapshot_tasks.each do |task|
|
71
|
+
result = systemu(task)
|
72
|
+
unless result.exitstatus == 0
|
73
|
+
raise Exception(
|
74
|
+
"Error while executing post-snapshot task: #{task} on #{ip}:#{mount_point} #{instance_id}:#{volume_id} "
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end if post_snapshot_tasks
|
78
|
+
end
|
79
|
+
return snapshot
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_local_snapshots(mount_point)
|
83
|
+
return nil if not ebs = find_local_ebs(mount_point)
|
84
|
+
snapshots = []
|
85
|
+
ec2.describe_snapshots.each {|snapshot|
|
86
|
+
snapshots.push(snapshot) if snapshot[:aws_volume_id] == ebs[:ebs_volume_id]
|
87
|
+
}
|
88
|
+
return snapshots
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/drebs/main.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
module Drebs
|
2
|
+
class Main
|
3
|
+
attr_reader :config
|
4
|
+
attr_reader :cloud
|
5
|
+
attr_reader :db
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
unless @config = params['config'].clone()
|
9
|
+
raise "No config_file_path passed!"
|
10
|
+
end
|
11
|
+
unless @db = params['db']
|
12
|
+
raise "No db passed!"
|
13
|
+
end
|
14
|
+
update_strategies(@config.delete("strategies"))
|
15
|
+
@cloud = Drebs::Cloud.new(@config)
|
16
|
+
@log = Logger.new(@config["log_path"])
|
17
|
+
@log.level = Logger::WARN
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_cloud
|
21
|
+
@cloud.check_cloud()
|
22
|
+
end
|
23
|
+
|
24
|
+
def Main.check_config(reference_config, other_config)
|
25
|
+
reference_config = reference_config.clone()
|
26
|
+
reference_strategy = reference_config.delete('strategies').last
|
27
|
+
|
28
|
+
errors = []
|
29
|
+
|
30
|
+
config_ok = reference_config.keys.each do |key|
|
31
|
+
config_ok = other_config.has_key?(key) and other_config[key] != nil and other_config[key] != ""
|
32
|
+
errors.push("Missing key/value for key: #{key}") unless config_ok
|
33
|
+
end
|
34
|
+
|
35
|
+
strategies = other_config['strategies']
|
36
|
+
if strategies.is_a?(Array) and strategies.first()
|
37
|
+
strategies.each_with_index do |strategy, i|
|
38
|
+
strategies_ok = reference_strategy.keys.all?{|key|
|
39
|
+
unless strategy.has_key?(key) and strategy[key] != nil and strategy[key] != ""
|
40
|
+
errors.push("Missing key/value for key: #{key}") unless config_ok
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
else
|
45
|
+
errors.push("Missing strategies array")
|
46
|
+
end
|
47
|
+
|
48
|
+
return errors
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def update_strategies(new_strategies)
|
53
|
+
new_strategies.each do |strategy|
|
54
|
+
exists = @db[:strategies].filter(:config=>strategy.to_yaml).update(:status=>"active")
|
55
|
+
if exists==0
|
56
|
+
pre_snapshot_tasks = strategy['pre_snapshot_tasks']
|
57
|
+
pre_snapshot_tasks = pre_snapshot_tasks ? pre_snapshot_tasks.join(",") : ""
|
58
|
+
post_snapshot_tasks = strategy['post_snapshot_tasks']
|
59
|
+
post_snapshot_tasks = post_snapshot_tasks ? post_snapshot_tasks.join(",") : ""
|
60
|
+
@db[:strategies].insert(
|
61
|
+
:config=>strategy.to_yaml,
|
62
|
+
:snapshots=>"",
|
63
|
+
:status=>"active",
|
64
|
+
:time_til_next_run => strategy['hours_between'],
|
65
|
+
:time_between_runs => strategy['hours_between'],
|
66
|
+
:num_to_keep => strategy['num_to_keep'],
|
67
|
+
:pre_snapshot_tasks => pre_snapshot_tasks,
|
68
|
+
:post_snapshot_tasks => post_snapshot_tasks,
|
69
|
+
:mount_point => strategy['mount_point']
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
deactivate_filter = new_strategies.map do |strategy|
|
74
|
+
"(config != '#{strategy.to_yaml}' and status == 'active')"
|
75
|
+
end.join(" and ")
|
76
|
+
@db[:strategies].filter(deactivate_filter).update(:status => 'inactive')
|
77
|
+
end
|
78
|
+
|
79
|
+
def send_email(subject, body)
|
80
|
+
host = @config['email_host']
|
81
|
+
port = @config['email_port']
|
82
|
+
domain = @config['email_domain']
|
83
|
+
username = @config['email_user']
|
84
|
+
password = @config['email_password']
|
85
|
+
|
86
|
+
msg = "Subject: #{subject}\n\n#{body}"
|
87
|
+
smtp = Net::SMTP.new(host, port)
|
88
|
+
smtp.enable_starttls
|
89
|
+
smtp.start(domain, username, password, :login) {|smtp|
|
90
|
+
smtp.send_message(msg, username, @config['email_on_exception'])
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def prune_backups(strategies)
|
95
|
+
to_prune = []
|
96
|
+
|
97
|
+
strategies.each do |strategy|
|
98
|
+
snapshots = strategy[:snapshots].split(",")
|
99
|
+
if snapshots.uniq==[nil]
|
100
|
+
@db[:strategies].filter(:config=>strategy[:config]).update(:snapshots => "")
|
101
|
+
elsif snapshots == []
|
102
|
+
elsif snapshots.count > strategy[:num_to_keep].to_i
|
103
|
+
# only remove it from EC2 if there are no other strategies with this snapshot
|
104
|
+
to_prune.push(Map.for({
|
105
|
+
:snapshot => snapshots.first,
|
106
|
+
:strategy => strategy
|
107
|
+
}))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
to_prune.each do |prune_obj|
|
112
|
+
snapshot = prune_obj.snapshot
|
113
|
+
strategy = prune_obj.strategy
|
114
|
+
|
115
|
+
# make sure that no other strategies have this snapshot
|
116
|
+
strategies_with_snapshot = @db[:strategies].all.select{|strategy| strategy[:snapshots].split(',').include?(snapshot)}
|
117
|
+
|
118
|
+
if strategies_with_snapshot.count == 1
|
119
|
+
begin
|
120
|
+
@cloud.ec2.delete_snapshot(snapshot.split(":")[0])
|
121
|
+
rescue RightAws::AwsError => e
|
122
|
+
type = e.errors.first.first rescue ''
|
123
|
+
raise unless type == "InvalidSnapshot.NotFound"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# update the strategy's snapshots to include all except given snapshot
|
128
|
+
new_snapshots = strategy[:snapshots].split(',').delete_if{|snap| snap == snapshot}.join(',')
|
129
|
+
@db[:strategies].filter(:config=>strategy[:config]).update(:snapshots => new_snapshots)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def execute
|
134
|
+
active_strategies = @db[:strategies].filter({:status=>"active"})
|
135
|
+
|
136
|
+
#Decrement time_til_next_run, save
|
137
|
+
active_strategies.each do |s|
|
138
|
+
@db[:strategies].filter(:config=>s[:config]).update(
|
139
|
+
:time_til_next_run => (s[:time_til_next_run].to_i - 1)
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
active_strategies = @db[:strategies].filter({:status=>"active"})
|
144
|
+
|
145
|
+
#backup_now = strategies where time_til_next_run <= 0
|
146
|
+
backup_now = active_strategies.to_a.select{|s| s[:time_til_next_run].to_i <= 0}
|
147
|
+
|
148
|
+
#loop over strategies grouped by mount_point
|
149
|
+
backup_now.group_by{|s| s[:mount_point]}.each do |mount_point, strategies|
|
150
|
+
pre_snapshot_tasks = strategies.map{|s| s[:pre_snapshot_tasks].split(",")}.flatten.uniq
|
151
|
+
post_snapshot_tasks = strategies.map{|s| s[:pre_snapshot_tasks].split(",")}.flatten.uniq
|
152
|
+
|
153
|
+
@log.info("creating snapshot of #{mount_point}")
|
154
|
+
begin
|
155
|
+
snapshot = @cloud.create_local_snapshot(pre_snapshot_tasks, post_snapshot_tasks, mount_point)
|
156
|
+
|
157
|
+
strategies.collect {|s|
|
158
|
+
snapshots = s[:snapshots].split(",")
|
159
|
+
snapshots.select!{|snapshot| @cloud.local_ebs_ids.include? snapshot.split(":")[1]}
|
160
|
+
snapshots.push(
|
161
|
+
s[:status]=='active' ?
|
162
|
+
"#{snapshot[:aws_id]}:#{snapshot[:aws_volume_id]}" : nil
|
163
|
+
)
|
164
|
+
@db[:strategies].filter(:config=>s[:config]).update(
|
165
|
+
:snapshots => snapshots.join(","),
|
166
|
+
:time_til_next_run => s[:time_between_runs]
|
167
|
+
)
|
168
|
+
}
|
169
|
+
|
170
|
+
rescue Exception => error
|
171
|
+
@log.error("Exception occured during backup: #{error.message}\n#{error.backtrace.join("\n")}")
|
172
|
+
send_email("DREBS Error!", "AWS Instance: #{@cloud.find_local_instance[:aws_instance_id]}\n#{error.message}\n#{error.backtrace.join("\n")}")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
prune_backups(@db[:strategies])
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require "main"
|
2
|
+
require "./lib/drebs/main.rb"
|
3
|
+
require "./lib/drebs/cloud.rb"
|
4
|
+
require "test/unit"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
EXAMPLE_CONFIG_PATH = "./config/example.yml"
|
8
|
+
TMP_TEST_DATA_PATH = "./tmp_test_data/"
|
9
|
+
|
10
|
+
class TestEC2
|
11
|
+
def initialize(*args)
|
12
|
+
@snapshots = []
|
13
|
+
@instances = [
|
14
|
+
{
|
15
|
+
private_ip_address: UDPSocket.open{|s| s.connect("8.8.8.8", 1); s.addr.last},
|
16
|
+
ip_address: "127.0.0.1",
|
17
|
+
aws_instance_id: "fake_instance",
|
18
|
+
block_device_mappings: [
|
19
|
+
{device_name: "/dev/sda1", ebs_volume_id: "fake_sda1"},
|
20
|
+
{device_name: "/dev/sdh", ebs_volume_id: "fake_sdh"}
|
21
|
+
]
|
22
|
+
}
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
def describe_snapshots()
|
27
|
+
@snapshots
|
28
|
+
end
|
29
|
+
|
30
|
+
def describe_instances()
|
31
|
+
@instances
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_snapshot(ebs_volume_id, snapshot_name)
|
35
|
+
new_snapshot = {
|
36
|
+
aws_id: "fake_snap-"+rand(36**6).to_s(36),
|
37
|
+
aws_status: 'completed',
|
38
|
+
aws_volume_id: ebs_volume_id
|
39
|
+
}
|
40
|
+
@snapshots.push(new_snapshot)
|
41
|
+
new_snapshot
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class TestCloud < Drebs::Cloud
|
46
|
+
def ec2; @ec2 ||= TestEC2.new; end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestContext
|
50
|
+
def TestContext.main_context(&block)
|
51
|
+
Main.new() do |main|
|
52
|
+
config = YAML::load(IO.read(EXAMPLE_CONFIG_PATH))
|
53
|
+
main.dotdir(TMP_TEST_DATA_PATH)
|
54
|
+
main.db() do
|
55
|
+
|
56
|
+
drop_table :strategies if table_exists? :strategies
|
57
|
+
create_table :strategies do
|
58
|
+
String :config
|
59
|
+
String :snapshots
|
60
|
+
String :status
|
61
|
+
String :time_til_next_run
|
62
|
+
String :time_between_runs
|
63
|
+
String :num_to_keep
|
64
|
+
String :pre_snapshot_tasks
|
65
|
+
String :post_snapshot_tasks
|
66
|
+
end
|
67
|
+
|
68
|
+
drop_table :snapshots if table_exists? :snapshots
|
69
|
+
create_table :snapshots do
|
70
|
+
String :aws_id
|
71
|
+
String :volume
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if block
|
76
|
+
begin
|
77
|
+
block.call(config, main.db())
|
78
|
+
ensure
|
79
|
+
begin
|
80
|
+
#fake main cleanup
|
81
|
+
main.db() do
|
82
|
+
drop_table :strategies if table_exists? :strategies
|
83
|
+
drop_table :snapshots if table_exists? :snapshots
|
84
|
+
end
|
85
|
+
rescue => e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def TestContext.cloud_context(config, &block)
|
93
|
+
cloud = TestCloud.new(config)
|
94
|
+
main_context do |main|
|
95
|
+
if block
|
96
|
+
begin
|
97
|
+
block.call(cloud)
|
98
|
+
ensure
|
99
|
+
begin
|
100
|
+
#fake cloud cleanup
|
101
|
+
rescue => e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def TestContext.drebs_context(&block)
|
109
|
+
main_context() do |config, db|
|
110
|
+
cloud_context(config) do |cloud|
|
111
|
+
drebs = Drebs::Main.new('config' => config, 'db' => db)
|
112
|
+
|
113
|
+
if block
|
114
|
+
begin
|
115
|
+
block.call(config, db, cloud, drebs)
|
116
|
+
ensure
|
117
|
+
begin
|
118
|
+
#fake drebs cleanup
|
119
|
+
rescue => e
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "./test/helper.rb"
|
2
|
+
|
3
|
+
class TestMain < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_can_check_config
|
6
|
+
TestContext.drebs_context() do |config, db, cloud, drebs|
|
7
|
+
config_errors = drebs.class.check_config(config, config)
|
8
|
+
assert(config_errors == [])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_check_config_finds_missing_keys
|
13
|
+
TestContext.drebs_context() do |config, db, cloud, drebs|
|
14
|
+
bad_config = config.clone
|
15
|
+
bad_config.delete(bad_config.keys.first)
|
16
|
+
config_errors = drebs.class.check_config(config, bad_config)
|
17
|
+
assert(config_errors.length == 1)
|
18
|
+
assert(config_errors[0].include?("Missing key/value"))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_can_save_strategies
|
23
|
+
TestContext.main_context() do |config, db|
|
24
|
+
assert(db[:strategies].all == [])
|
25
|
+
drebs = Drebs::Main.new('config' => config, 'db' => db)
|
26
|
+
assert(db[:strategies].all.length > 0)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_removed_strategies_get_deactivated
|
31
|
+
TestContext.main_context() do |config, db|
|
32
|
+
drebs = Drebs::Main.new('config' => config, 'db' => db)
|
33
|
+
num_active_strategies = db[:strategies].where(:status=>"active").count
|
34
|
+
num_inactive_strategies = db[:strategies].where(:status=>"inactive").count
|
35
|
+
config['strategies'] = config['strategies'][1..-1]
|
36
|
+
drebs = Drebs::Main.new('config' => config, 'db' => db)
|
37
|
+
num_active_strategies_dec = db[:strategies].where(:status=>"active").count
|
38
|
+
num_inactive_strategies_inc = db[:strategies].where(:status=>"inactive").count
|
39
|
+
assert(num_active_strategies == num_active_strategies_dec + 1)
|
40
|
+
assert(num_inactive_strategies == num_inactive_strategies_inc - 1)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
Binary file
|
metadata
CHANGED
@@ -1,73 +1,103 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: drebs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Garett Shulman
|
8
|
+
- Miles Matthias
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-06-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: right_aws
|
16
|
-
requirement:
|
17
|
-
none: false
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
18
17
|
requirements:
|
19
18
|
- - ! '>='
|
20
19
|
- !ruby/object:Gem::Version
|
21
|
-
version: 3.
|
20
|
+
version: 3.1.0
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
|
-
version_requirements:
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ! '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.1.0
|
25
28
|
- !ruby/object:Gem::Dependency
|
26
29
|
name: logger
|
27
|
-
requirement:
|
28
|
-
none: false
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
29
31
|
requirements:
|
30
32
|
- - ! '>='
|
31
33
|
- !ruby/object:Gem::Version
|
32
34
|
version: 1.2.8
|
33
35
|
type: :runtime
|
34
36
|
prerelease: false
|
35
|
-
version_requirements:
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.2.8
|
36
42
|
- !ruby/object:Gem::Dependency
|
37
43
|
name: main
|
38
|
-
requirement:
|
39
|
-
none: false
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
40
45
|
requirements:
|
41
46
|
- - ! '>='
|
42
47
|
- !ruby/object:Gem::Version
|
43
|
-
version: 5.
|
48
|
+
version: 5.2.0
|
44
49
|
type: :runtime
|
45
50
|
prerelease: false
|
46
|
-
version_requirements:
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 5.2.0
|
47
56
|
- !ruby/object:Gem::Dependency
|
48
57
|
name: systemu
|
49
|
-
requirement:
|
50
|
-
none: false
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
51
59
|
requirements:
|
52
60
|
- - ! '>='
|
53
61
|
- !ruby/object:Gem::Version
|
54
62
|
version: 2.4.2
|
55
63
|
type: :runtime
|
56
64
|
prerelease: false
|
57
|
-
version_requirements:
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.4.2
|
58
70
|
- !ruby/object:Gem::Dependency
|
59
71
|
name: json
|
60
|
-
requirement:
|
61
|
-
none: false
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
62
73
|
requirements:
|
63
74
|
- - ! '>='
|
64
75
|
- !ruby/object:Gem::Version
|
65
76
|
version: 1.5.1
|
66
77
|
type: :runtime
|
67
78
|
prerelease: false
|
68
|
-
version_requirements:
|
69
|
-
|
70
|
-
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.5.1
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 0.9.12.6
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 0.9.12.6
|
98
|
+
description: ! 'drebs: Disaster Recovery for Elastic Block Store. An AWS EBS backup
|
99
|
+
script.'
|
100
|
+
email: miles@dojo4.com
|
71
101
|
executables:
|
72
102
|
- drebs
|
73
103
|
extensions: []
|
@@ -76,30 +106,37 @@ files:
|
|
76
106
|
- README.md
|
77
107
|
- Rakefile
|
78
108
|
- bin/drebs
|
109
|
+
- config/example.yml
|
79
110
|
- drebs.gemspec
|
80
111
|
- lib/drebs.rb
|
112
|
+
- lib/drebs/cloud.rb
|
113
|
+
- lib/drebs/main.rb
|
114
|
+
- test/helper.rb
|
115
|
+
- test/unit/drebs/drebs_test.rb
|
116
|
+
- test/unit/drebs/main_test.rb
|
117
|
+
- tmp_test_data/db.sqlite
|
81
118
|
homepage: https://github.com/dojo4/drebs
|
82
|
-
licenses:
|
119
|
+
licenses:
|
120
|
+
- Apache-2.0
|
121
|
+
metadata: {}
|
83
122
|
post_install_message:
|
84
123
|
rdoc_options: []
|
85
124
|
require_paths:
|
86
125
|
- lib
|
87
126
|
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
-
none: false
|
89
127
|
requirements:
|
90
128
|
- - ! '>='
|
91
129
|
- !ruby/object:Gem::Version
|
92
130
|
version: '0'
|
93
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
-
none: false
|
95
132
|
requirements:
|
96
133
|
- - ! '>='
|
97
134
|
- !ruby/object:Gem::Version
|
98
135
|
version: '0'
|
99
136
|
requirements: []
|
100
137
|
rubyforge_project: DREBS
|
101
|
-
rubygems_version: 1.8
|
138
|
+
rubygems_version: 2.1.8
|
102
139
|
signing_key:
|
103
|
-
specification_version:
|
140
|
+
specification_version: 4
|
104
141
|
summary: drebs
|
105
142
|
test_files: []
|