braavos 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +44 -0
- data/README.rdoc +113 -0
- data/Rakefile +23 -0
- data/bin/braavos +20 -0
- data/lib/braavos.rb +179 -0
- data/lib/braavos/cli.rb +82 -0
- data/lib/braavos/command.rb +30 -0
- data/lib/braavos/config.rb +48 -0
- data/lib/braavos/parallel.rb +104 -0
- data/lib/braavos/service.rb +48 -0
- data/lib/braavos/service/cassandra.rb +187 -0
- data/lib/braavos/service/elasticsearch.rb +15 -0
- data/lib/braavos/storage.rb +7 -0
- data/lib/braavos/storage/file.rb +48 -0
- data/lib/braavos/storage/s3.rb +41 -0
- data/lib/braavos/storage/storage_base.rb +48 -0
- data/lib/braavos/template.rb +49 -0
- data/lib/braavos/version.rb +3 -0
- data/template/cassandra/clear_snapshot.sh.erb +1 -0
- data/template/cassandra/create_snapshot.sh.erb +1 -0
- data/template/cassandra/dump_schema.sh.erb +4 -0
- data/template/cassandra/system_bundle.sh.erb +18 -0
- data/template/cassandra/table_bundle_restore.sh.erb +44 -0
- data/template/cassandra/table_bundle_upload.sh.erb +27 -0
- data/test/braavos/command_test.rb +34 -0
- data/test/braavos/config_test.rb +66 -0
- data/test/braavos/parallel_test.rb +29 -0
- data/test/braavos/template_test.rb +36 -0
- data/test/template/nested/inner.sh.erb +1 -0
- data/test/template/simple.txt.erb +1 -0
- data/test/test_helper.rb +17 -0
- metadata +125 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
class Braavos::Storage::File < Braavos::Storage::StorageBase
|
2
|
+
|
3
|
+
def load_file(location)
|
4
|
+
Braavos.logger.debug("load_file: #{location}")
|
5
|
+
File.read(location)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns a recursive listing of {"name" => true} if directory and {"name" => *file size*} if file
|
9
|
+
def list_dir(location)
|
10
|
+
Braavos.logger.debug("list_dir: #{location}")
|
11
|
+
listing = Dir["#{location}/**/*"].inject({}) do |h,i|
|
12
|
+
h[i] = File.size?(i) if not File.directory?(i)
|
13
|
+
h
|
14
|
+
end
|
15
|
+
Braavos.logger.debug("\t#{listing}")
|
16
|
+
listing
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear_result(location)
|
20
|
+
FileUtils.rm_f File.join(location, "_FAILED")
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_success?(location)
|
24
|
+
path = File.join(location, "_COMPLETED")
|
25
|
+
list_dir(location).size > 0 &&
|
26
|
+
File.exists?(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def script_path(location)
|
30
|
+
@mkdir_cache ||= Hash.new do |h, key|
|
31
|
+
FileUtils.mkdir_p(key)
|
32
|
+
h[key] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
@mkdir_cache[File.dirname(location)]
|
36
|
+
location
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_file(location, contents)
|
40
|
+
script_path(location) # for mkdir -p
|
41
|
+
if contents.is_a?(Hash) && contents.include?(:file)
|
42
|
+
FileUtils.cp(contents[:file], location)
|
43
|
+
else
|
44
|
+
File.open(location, 'w') do |f| f.write contents end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Braavos::Storage::S3 < Braavos::Storage::StorageBase
|
2
|
+
|
3
|
+
attr_reader :s3
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@s3 = AWS::S3.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_file(location)
|
10
|
+
s3.buckets[Braavos.config.bucket_name].objects[location].read
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_dir(location)
|
14
|
+
Braavos.logger.debug("S3 list_dir: #{Braavos.config.bucket_name}/#{location}")
|
15
|
+
listing = s3.buckets[Braavos.config.bucket_name].objects.with_prefix("#{location}/").inject({}) do |h,i|
|
16
|
+
h[i.key] = 0 if not i.key.end_with?('_$folder$') # disable i.content_length due to performance
|
17
|
+
h
|
18
|
+
end
|
19
|
+
Braavos.logger.debug("\t#{listing}")
|
20
|
+
listing
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_success?(location)
|
24
|
+
path = File.join(location, "_COMPLETED")
|
25
|
+
list_dir(location).size > 0 &&
|
26
|
+
s3.buckets[Braavos.config.bucket_name].objects[path].exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear_result(location)
|
30
|
+
path = File.join(location, "_FAILED")
|
31
|
+
s3.buckets[Braavos.config.bucket_name].objects[path].delete
|
32
|
+
end
|
33
|
+
|
34
|
+
def script_path(location)
|
35
|
+
File.join(Braavos.config.bucket_name, location)
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_file(location, contents)
|
39
|
+
s3.buckets[Braavos.config.bucket_name].objects[location].write(contents)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Braavos::Storage::StorageBase
|
2
|
+
|
3
|
+
|
4
|
+
def get_cluster
|
5
|
+
@cluster ||= JSON.parse(load_file(File.join(Braavos.config.backup_path, 'cluster.json')))
|
6
|
+
end
|
7
|
+
|
8
|
+
def verify_cluster(write_mode)
|
9
|
+
cluster = get_cluster
|
10
|
+
if write_mode
|
11
|
+
if Braavos.config.name != cluster['name']
|
12
|
+
raise "Mismatching name (expecting: #{Braavos.config.name}, found: #{cluster['name']})"
|
13
|
+
end
|
14
|
+
if Braavos.config.environment != cluster['environment']
|
15
|
+
raise "Mismatching environment (expecting: #{Braavos.config.environment}, found: #{cluster['environment']})"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_node_id(force_node_id=nil)
|
21
|
+
@node_id = force_node_id if force_node_id
|
22
|
+
@node_id ||= -> do
|
23
|
+
cluster = get_cluster
|
24
|
+
instance_id = Braavos::Service.instance_id
|
25
|
+
cluster['nodes'].each do |node_id, node_info|
|
26
|
+
if node_info['instance_id'] == instance_id
|
27
|
+
return node_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
raise "Node not found: #{instance_id}"
|
31
|
+
end.call
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: option for --completed --pending, maybe return size here
|
35
|
+
def list_backups(node_id)
|
36
|
+
result = {full: [], incr: []}
|
37
|
+
listing = list_dir(Braavos.config.backup_path)
|
38
|
+
listing.each do |l, z|
|
39
|
+
if match = l.match(/\/(full|incr)\/(\w+)\/#{node_id}\/_COMPLETED\Z/)
|
40
|
+
path, name = match.captures
|
41
|
+
result[path.to_sym] << name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class Braavos::Template
|
4
|
+
|
5
|
+
attr_accessor :template_paths
|
6
|
+
|
7
|
+
def initialize(template_paths)
|
8
|
+
@template_paths = template_paths
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_template(template, params={})
|
12
|
+
content = find_template(template)
|
13
|
+
raise "Unable to find template: #{template} in path: #{template_paths}" unless content
|
14
|
+
result_template(content, params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def result_template(template, params)
|
18
|
+
ns = Namespace.new(params.merge(config: Braavos.config))
|
19
|
+
ERB.new(template).result(ns.get_binding)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# http://stackoverflow.com/questions/1338960/ruby-templates-how-to-pass-variables-into-inlined-erb
|
25
|
+
class Namespace
|
26
|
+
def initialize(hash)
|
27
|
+
hash.each do |key, value|
|
28
|
+
singleton_class.send(:define_method, key) { value }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_binding
|
33
|
+
binding
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_template(template)
|
38
|
+
content = nil
|
39
|
+
template_paths.each do |path|
|
40
|
+
file_path = File.join(path, template)
|
41
|
+
if File.exists?(file_path)
|
42
|
+
content = File.read(file_path)
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
content
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
exec nodetool clearsnapshot $1 -t $2
|
@@ -0,0 +1 @@
|
|
1
|
+
exec nodetool snapshot $1 -t $2
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
DEST_DIR=$1
|
6
|
+
shift
|
7
|
+
DATA_DIR=$1
|
8
|
+
shift
|
9
|
+
|
10
|
+
cd ${DATA_DIR} && tar zcf ${DEST_DIR}/system.tgz $*
|
11
|
+
|
12
|
+
nodetool ring > ${DEST_DIR}/ring.txt
|
13
|
+
nodetool info > ${DEST_DIR}/info.txt
|
14
|
+
nodetool cfstats > ${DEST_DIR}/cfstats.txt
|
15
|
+
nodetool status > ${DEST_DIR}/status.txt
|
16
|
+
nodetool compactionstats > ${DEST_DIR}/compactionstats.txt
|
17
|
+
nodetool netstats > ${DEST_DIR}/netstats.txt
|
18
|
+
nodetool gossipinfo > ${DEST_DIR}/gossipinfo.txt
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
FILE_LOC=$1
|
6
|
+
DEST_LOC=$2
|
7
|
+
PROCESS_LOC=${FILE_LOC}
|
8
|
+
|
9
|
+
<% if config.storage_backing == 's3' %>
|
10
|
+
|
11
|
+
PROCESS_LOC=<%= config.temp_loc %>/braavos-cassdown-$$.${RANDOM}.tgz
|
12
|
+
|
13
|
+
function getTimeoutCmd() {
|
14
|
+
if hash timeout &> /dev/null
|
15
|
+
then
|
16
|
+
echo timeout
|
17
|
+
else
|
18
|
+
echo gtimeout
|
19
|
+
fi
|
20
|
+
}
|
21
|
+
|
22
|
+
trap "echo Caught kill for ${FILE_LOC}; rm ${PROCESS_LOC}; exit 1" SIGHUP SIGINT SIGTERM
|
23
|
+
|
24
|
+
# Install GNU coreutils for `timeout`
|
25
|
+
# s3cmd can hang indefinitely due to network constraints
|
26
|
+
# So kill after timeout of 2 minutes, an ideal timeout when run on EC2
|
27
|
+
$(getTimeoutCmd) 120 s3cmd get s3://${FILE_LOC} ${PROCESS_LOC}
|
28
|
+
if [ $? -eq 124 ]; then
|
29
|
+
# Timeout occurred, try one more time.
|
30
|
+
echo Timed Out! Retrying download of ${FILE_LOC}
|
31
|
+
s3cmd get s3://${FILE_LOC} ${PROCESS_LOC}
|
32
|
+
fi
|
33
|
+
|
34
|
+
<% end %>
|
35
|
+
|
36
|
+
echo Expanding ${FILE_LOC} to ${DEST_LOC}
|
37
|
+
mkdir -p ${DEST_LOC}
|
38
|
+
zcat -q ${PROCESS_LOC} | tar -xC ${DEST_LOC}
|
39
|
+
|
40
|
+
<% if config.storage_backing == 's3' %>
|
41
|
+
|
42
|
+
rm ${PROCESS_LOC}
|
43
|
+
|
44
|
+
<% end %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
FILE_LOC=$1
|
6
|
+
START_LOC=$(pwd)
|
7
|
+
DEST_LOC=$2
|
8
|
+
TMP_FILE=<%= config.temp_loc %>/braavos-cassup-$$.${RANDOM}.tgz
|
9
|
+
|
10
|
+
trap "echo Caught kill for ${FILE_LOC}; rm ${TMP_FILE}; exit 1" SIGHUP SIGINT SIGTERM
|
11
|
+
|
12
|
+
BASE=$(dirname $FILE_LOC)
|
13
|
+
FILE_PART=$(basename $FILE_LOC)
|
14
|
+
|
15
|
+
echo Processing $FILE_PART in $BASE to ${DEST_LOC}
|
16
|
+
cd $BASE && tar zcf $TMP_FILE ${FILE_PART}-*
|
17
|
+
|
18
|
+
<% if config.storage_backing == 'file' %>
|
19
|
+
|
20
|
+
mv $TMP_FILE $START_LOC/$DEST_LOC
|
21
|
+
|
22
|
+
<% elsif config.storage_backing == 's3' %>
|
23
|
+
|
24
|
+
s3cmd put ${TMP_FILE} s3://${DEST_LOC}
|
25
|
+
rm ${TMP_FILE}
|
26
|
+
|
27
|
+
<% end %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Braavos::CommandTest < MiniTest::Spec
|
4
|
+
attr_accessor :command, :file
|
5
|
+
|
6
|
+
before do
|
7
|
+
Braavos.logger.level = Logger::WARN
|
8
|
+
@file = Tempfile.new "braavos_command_test"
|
9
|
+
@command = Braavos::Command.new
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
file.close
|
14
|
+
file.unlink
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_simple
|
18
|
+
command.execute("echo $* >> #{file.path}", ['a', 'b', 'c'])
|
19
|
+
result = file.read
|
20
|
+
assert_equal "a b c\n", result
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_arguments
|
24
|
+
command.execute("echo $1 >> #{file.path}", ['a', 'b', 'c'])
|
25
|
+
result = file.read
|
26
|
+
assert_equal "a\n", result
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_system
|
30
|
+
command.execute("echo $* >> #{file.path}", ['a', 'b', 'c'])
|
31
|
+
result = file.read
|
32
|
+
assert result
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Braavos::ConfigTest < MiniTest::Spec
|
4
|
+
|
5
|
+
def default_config
|
6
|
+
{temp_loc: '/tmp'}
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_regular_backup_path
|
10
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra')
|
11
|
+
config = Braavos::Config.new(c)
|
12
|
+
|
13
|
+
assert_equal "neville/env/cassandra", config.backup_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_prefixed_backup_path
|
17
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra', backup_prefix: '/Users/nevillek/backup/')
|
18
|
+
config = Braavos::Config.new(c)
|
19
|
+
|
20
|
+
assert_equal "/Users/nevillek/backup/neville/env/cassandra", config.backup_path
|
21
|
+
|
22
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra', backup_prefix: 'Users/nevillek/backup')
|
23
|
+
config = Braavos::Config.new(c)
|
24
|
+
|
25
|
+
assert_equal "Users/nevillek/backup/neville/env/cassandra", config.backup_path
|
26
|
+
|
27
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra', backup_prefix: '')
|
28
|
+
config = Braavos::Config.new(c)
|
29
|
+
|
30
|
+
assert_equal "neville/env/cassandra", config.backup_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_service_cassandra
|
34
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra')
|
35
|
+
config = Braavos::Config.new(c)
|
36
|
+
|
37
|
+
assert config.service_class == Braavos::Service::Cassandra
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_service_es
|
41
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'elasticsearch')
|
42
|
+
config = Braavos::Config.new(c)
|
43
|
+
|
44
|
+
assert config.service_class == Braavos::Service::Elasticsearch
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_storage_s3
|
48
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra')
|
49
|
+
config = Braavos::Config.new(c)
|
50
|
+
|
51
|
+
assert config.storage_class == Braavos::Storage::S3
|
52
|
+
|
53
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra', storage_backing: 's3')
|
54
|
+
config = Braavos::Config.new(c)
|
55
|
+
|
56
|
+
assert config.storage_class == Braavos::Storage::S3
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_storage_file
|
60
|
+
c = default_config.merge(name: 'neville', environment: 'env', service: 'cassandra', storage_backing: 'file')
|
61
|
+
config = Braavos::Config.new(c)
|
62
|
+
|
63
|
+
assert config.storage_class == Braavos::Storage::File
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Braavos::ParallelTest < MiniTest::Spec
|
4
|
+
attr_accessor :parallel, :file
|
5
|
+
|
6
|
+
before do
|
7
|
+
Braavos.logger.level = Logger::WARN
|
8
|
+
@file = Tempfile.new "braavos_parallel_test"
|
9
|
+
@parallel = Braavos::Parallel.new
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
file.close
|
14
|
+
file.unlink
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_simple
|
18
|
+
result = parallel.execute("echo $1 >> #{file.path}", ['a', 'b', 'c'])
|
19
|
+
result = file.read
|
20
|
+
assert_equal ['a', 'b', 'c'], result.lines.map(&:chomp).sort
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_multiple_inputs
|
24
|
+
parallel.execute("echo $* >> #{file.path}", [['a', 'apple'], ['b', 'ball'], ['c', 'crawl']])
|
25
|
+
result = file.read
|
26
|
+
assert_equal ['a apple', 'b ball', 'c crawl'], result.lines.map(&:chomp).sort
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Braavos::TemplateTest < MiniTest::Spec
|
4
|
+
attr_accessor :template
|
5
|
+
|
6
|
+
before do
|
7
|
+
@template = Braavos::Template.new(['test/template'])
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_result_simple
|
11
|
+
result = template.result_template('Hello <%= name %>, Welcome to <%= place %>!', name: 'Neville', place: 'Home')
|
12
|
+
|
13
|
+
assert_equal 'Hello Neville, Welcome to Home!', result
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_result_missing
|
17
|
+
|
18
|
+
assert_raises NameError do
|
19
|
+
result = template.result_template('Hello <%= name %>, Welcome to <%= place %>!', place: 'Home')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_simple_template
|
25
|
+
result = template.load_template('simple.txt.erb', name: 'Neville', place: 'Home')
|
26
|
+
|
27
|
+
assert_equal 'Hello Neville, Welcome to Home!', result
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_nested_dir
|
31
|
+
result = template.load_template('nested/inner.sh.erb')
|
32
|
+
|
33
|
+
assert_equal "#!/bin/bash", result
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|