drebs 0.0.1 → 0.1.0
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 +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: []
|