braavos 0.0.7
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 +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
|