logstash-output-s3 3.2.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/lib/logstash/outputs/s3.rb +188 -308
- data/lib/logstash/outputs/s3/file_repository.rb +120 -0
- data/lib/logstash/outputs/s3/patch.rb +22 -0
- data/lib/logstash/outputs/s3/path_validator.rb +18 -0
- data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
- data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/temporary_file.rb +71 -0
- data/lib/logstash/outputs/s3/temporary_file_factory.rb +123 -0
- data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/uploader.rb +59 -0
- data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
- data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +49 -0
- data/logstash-output-s3.gemspec +2 -2
- data/spec/integration/dynamic_prefix_spec.rb +92 -0
- data/spec/integration/gzip_file_spec.rb +62 -0
- data/spec/integration/gzip_size_rotation_spec.rb +63 -0
- data/spec/integration/restore_from_crash_spec.rb +39 -0
- data/spec/integration/size_rotation_spec.rb +59 -0
- data/spec/integration/stress_test_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_stale_write_spec.rb +60 -0
- data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
- data/spec/outputs/s3/file_repository_spec.rb +146 -0
- data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
- data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
- data/spec/outputs/s3/temporary_file_factory_spec.rb +85 -0
- data/spec/outputs/s3/temporary_file_spec.rb +40 -0
- data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
- data/spec/outputs/s3/uploader_spec.rb +57 -0
- data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
- data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +38 -0
- data/spec/outputs/s3_spec.rb +52 -335
- data/spec/spec_helper.rb +6 -0
- data/spec/supports/helpers.rb +33 -9
- metadata +65 -4
- data/spec/integration/s3_spec.rb +0 -97
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/outputs/s3/temporary_file"
|
4
|
+
require "stud/temporary"
|
5
|
+
require "fileutils"
|
6
|
+
require "securerandom"
|
7
|
+
|
8
|
+
describe LogStash::Outputs::S3::TemporaryFile do
|
9
|
+
let(:content) { "hello world" }
|
10
|
+
let(:key) { "foo" }
|
11
|
+
let(:uuid) { SecureRandom.uuid }
|
12
|
+
let(:temporary_file) { ::File.open(::File.join(temporary_directory, uuid, key), "w+") }
|
13
|
+
let(:temporary_directory) { Stud::Temporary.directory }
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
FileUtils.mkdir_p(::File.join(temporary_directory, uuid))
|
17
|
+
end
|
18
|
+
|
19
|
+
subject { described_class.new(key, temporary_file, temporary_directory) }
|
20
|
+
|
21
|
+
it "returns the key of the file" do
|
22
|
+
expect(subject.key).to eq(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "saves content to a file" do
|
26
|
+
subject.write(content)
|
27
|
+
subject.close
|
28
|
+
expect(File.read(subject.path).strip).to eq(content)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "deletes a file" do
|
32
|
+
expect(File.exist?(subject.path)).to be_truthy
|
33
|
+
subject.delete!
|
34
|
+
expect(File.exist?(subject.path)).to be_falsey
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the creation time" do
|
38
|
+
expect(subject.ctime).to be < Time.now + 0.5
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/outputs/s3/time_rotation_policy"
|
4
|
+
require "logstash/outputs/s3/temporary_file"
|
5
|
+
|
6
|
+
describe LogStash::Outputs::S3::TimeRotationPolicy do
|
7
|
+
subject { described_class.new(max_time) }
|
8
|
+
|
9
|
+
let(:max_time) { 1 }
|
10
|
+
let(:temporary_directory) { Stud::Temporary.directory }
|
11
|
+
let(:temporary_file) { Stud::Temporary.file }
|
12
|
+
let(:name) { "foobar" }
|
13
|
+
let(:content) { "hello" * 1000 }
|
14
|
+
let(:file) { LogStash::Outputs::S3::TemporaryFile.new(name, temporary_file, temporary_directory) }
|
15
|
+
|
16
|
+
it "raises an exception if the `file_time` is set to 0" do
|
17
|
+
expect { described_class.new(0) }.to raise_error(LogStash::ConfigurationError, /`time_file` need to be greather than 0/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises an exception if the `file_time` is < 0" do
|
21
|
+
expect { described_class.new(-100) }.to raise_error(LogStash::ConfigurationError, /`time_file` need to be greather than 0/)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when the size of the file is superior to 0" do
|
25
|
+
before :each do
|
26
|
+
file.write(content)
|
27
|
+
file.fsync
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns true if the file old enough" do
|
31
|
+
allow(file).to receive(:ctime).and_return(Time.now - (max_time * 2 * 60))
|
32
|
+
expect(subject.rotate?(file)).to be_truthy
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns false is not old enough" do
|
36
|
+
expect(subject.rotate?(file)).to be_falsey
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "When the size of the file is 0" do
|
41
|
+
it "returns false if the file old enough" do
|
42
|
+
allow(file).to receive(:ctime).and_return(Time.now - (max_time * 2 * 60))
|
43
|
+
expect(subject.rotate?(file)).to be_falsey
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns false is not old enough" do
|
47
|
+
expect(subject.rotate?(file)).to be_falsey
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "#needs_periodic?" do
|
52
|
+
it "return false" do
|
53
|
+
expect(subject.needs_periodic?).to be_truthy
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "convert minute into seconds" do
|
58
|
+
expect(subject.time_file).to eq(60)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/outputs/s3/uploader"
|
4
|
+
require "logstash/outputs/s3/temporary_file"
|
5
|
+
require "aws-sdk"
|
6
|
+
require "stud/temporary"
|
7
|
+
|
8
|
+
describe LogStash::Outputs::S3::Uploader do
|
9
|
+
let(:logger) { spy(:logger ) }
|
10
|
+
let(:max_upload_workers) { 1 }
|
11
|
+
let(:bucket_name) { "foobar-bucket" }
|
12
|
+
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
13
|
+
let(:bucket) { Aws::S3::Bucket.new(bucket_name, :client => client) }
|
14
|
+
let(:temporary_directory) { Stud::Temporary.pathname }
|
15
|
+
let(:temporary_file) { Stud::Temporary.file }
|
16
|
+
let(:key) { "foobar" }
|
17
|
+
let(:upload_options) { {} }
|
18
|
+
let(:threadpool) do
|
19
|
+
Concurrent::ThreadPoolExecutor.new({
|
20
|
+
:min_threads => 1,
|
21
|
+
:max_threads => 8,
|
22
|
+
:max_queue => 1,
|
23
|
+
:fallback_policy => :caller_runs
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:file) do
|
28
|
+
f = LogStash::Outputs::S3::TemporaryFile.new(key, temporary_file, temporary_directory)
|
29
|
+
f.write("random content")
|
30
|
+
f.fsync
|
31
|
+
f
|
32
|
+
end
|
33
|
+
|
34
|
+
subject { described_class.new(bucket, logger, threadpool) }
|
35
|
+
|
36
|
+
it "upload file to the s3 bucket" do
|
37
|
+
expect { subject.upload(file) }.not_to raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
it "execute a callback when the upload is complete" do
|
41
|
+
callback = proc { |f| }
|
42
|
+
|
43
|
+
expect(callback).to receive(:call).with(file)
|
44
|
+
subject.upload(file, { :on_complete => callback })
|
45
|
+
end
|
46
|
+
|
47
|
+
it "retries errors indefinitively" do
|
48
|
+
s3 = double("s3").as_null_object
|
49
|
+
|
50
|
+
expect(logger).to receive(:error).with(any_args).once
|
51
|
+
expect(bucket).to receive(:object).with(file.key).and_return(s3).twice
|
52
|
+
expect(s3).to receive(:upload_file).with(any_args).and_raise(StandardError)
|
53
|
+
expect(s3).to receive(:upload_file).with(any_args).and_return(true)
|
54
|
+
|
55
|
+
subject.upload(file)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/outputs/s3/writable_directory_validator"
|
4
|
+
require "stud/temporary"
|
5
|
+
|
6
|
+
describe LogStash::Outputs::S3::WritableDirectoryValidator do
|
7
|
+
let(:temporary_directory) { File.join(Stud::Temporary.directory, Time.now.to_i.to_s) }
|
8
|
+
|
9
|
+
subject { described_class }
|
10
|
+
|
11
|
+
context "when the directory doesn't exists" do
|
12
|
+
it "creates the directory" do
|
13
|
+
expect(Dir.exist?(temporary_directory)).to be_falsey
|
14
|
+
expect(subject.valid?(temporary_directory)).to be_truthy
|
15
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when the directory exist" do
|
20
|
+
before do
|
21
|
+
FileUtils.mkdir_p(temporary_directory)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't change the directory" do
|
25
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
26
|
+
expect(subject.valid?(temporary_directory)).to be_truthy
|
27
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "return false if the directory is not writable" do
|
32
|
+
expect(::File).to receive(:writable?).with(temporary_directory).and_return(false)
|
33
|
+
expect(subject.valid?(temporary_directory)).to be_falsey
|
34
|
+
end
|
35
|
+
|
36
|
+
it "return true if the directory is writable" do
|
37
|
+
expect(::File).to receive(:writable?).with(temporary_directory).and_return(true)
|
38
|
+
expect(subject.valid?(temporary_directory)).to be_truthy
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/outputs/s3/write_bucket_permission_validator"
|
4
|
+
require "aws-sdk"
|
5
|
+
|
6
|
+
describe LogStash::Outputs::S3::WriteBucketPermissionValidator do
|
7
|
+
let(:bucket_name) { "foobar" }
|
8
|
+
let(:obj) { double("s3_object") }
|
9
|
+
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
10
|
+
let(:bucket) { Aws::S3::Bucket.new(bucket_name, :client => client) }
|
11
|
+
|
12
|
+
subject { described_class }
|
13
|
+
|
14
|
+
before do
|
15
|
+
expect(bucket).to receive(:object).with(any_args).and_return(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when permissions are sufficient" do
|
19
|
+
it "returns true" do
|
20
|
+
expect(obj).to receive(:upload_file).with(any_args).and_return(true)
|
21
|
+
expect(obj).to receive(:delete).and_return(true)
|
22
|
+
expect(subject.valid?(bucket)).to be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
it "hides delete errors" do
|
26
|
+
expect(obj).to receive(:upload_file).with(any_args).and_return(true)
|
27
|
+
expect(obj).to receive(:delete).and_raise(StandardError)
|
28
|
+
expect(subject.valid?(bucket)).to be_truthy
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when permission aren't sufficient" do
|
33
|
+
it "returns false" do
|
34
|
+
expect(obj).to receive(:upload_file).with(any_args).and_raise(StandardError)
|
35
|
+
expect(subject.valid?(bucket)).to be_falsey
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/outputs/s3_spec.rb
CHANGED
@@ -1,371 +1,88 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require "logstash/devutils/rspec/spec_helper"
|
3
2
|
require "logstash/outputs/s3"
|
3
|
+
require "logstash/event"
|
4
4
|
require "logstash/codecs/line"
|
5
|
-
require "
|
6
|
-
require "aws-sdk"
|
7
|
-
require "fileutils"
|
8
|
-
require_relative "../supports/helpers"
|
5
|
+
require "stud/temporary"
|
9
6
|
|
10
7
|
describe LogStash::Outputs::S3 do
|
8
|
+
let(:prefix) { "super/%{server}" }
|
9
|
+
let(:region) { "us-east-1" }
|
10
|
+
let(:bucket_name) { "mybucket" }
|
11
|
+
let(:options) { { "region" => region,
|
12
|
+
"bucket" => bucket_name,
|
13
|
+
"prefix" => prefix,
|
14
|
+
"restore" => false,
|
15
|
+
"access_key_id" => "access_key_id",
|
16
|
+
"secret_access_key" => "secret_access_key"
|
17
|
+
} }
|
18
|
+
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
19
|
+
let(:mock_bucket) { Aws::S3::Bucket.new(:name => bucket_name, :stub_responses => true, :client => client) }
|
20
|
+
let(:event) { LogStash::Event.new({ "server" => "overwatch" }) }
|
21
|
+
let(:event_encoded) { "super hype" }
|
22
|
+
let(:events_and_encoded) { { event => event_encoded } }
|
23
|
+
|
24
|
+
subject { described_class.new(options) }
|
25
|
+
|
11
26
|
before do
|
12
|
-
|
13
|
-
|
14
|
-
AWS.stub!
|
15
|
-
Thread.abort_on_exception = true
|
27
|
+
allow(subject).to receive(:bucket_resource).and_return(mock_bucket)
|
28
|
+
allow(LogStash::Outputs::S3::WriteBucketPermissionValidator).to receive(:valid?).with(mock_bucket).and_return(true)
|
16
29
|
end
|
17
30
|
|
18
|
-
|
19
|
-
"secret_access_key" => "secret",
|
20
|
-
"bucket" => "my-bucket" } }
|
21
|
-
|
22
|
-
describe "configuration" do
|
23
|
-
let!(:config) { { "region" => "sa-east-1" } }
|
24
|
-
|
31
|
+
context "#register configuration validation" do
|
25
32
|
describe "signature version" do
|
26
33
|
it "should set the signature version if specified" do
|
27
|
-
|
28
|
-
|
34
|
+
["v2", "v4"].each do |version|
|
35
|
+
s3 = described_class.new(options.merge({ "signature_version" => version }))
|
36
|
+
expect(s3.full_options).to include(:s3_signature_version => version)
|
37
|
+
end
|
29
38
|
end
|
30
39
|
|
31
40
|
it "should omit the option completely if not specified" do
|
32
|
-
s3 =
|
41
|
+
s3 = described_class.new(options)
|
33
42
|
expect(s3.full_options.has_key?(:s3_signature_version)).to eql(false)
|
34
43
|
end
|
35
44
|
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe "#register" do
|
39
|
-
it "should create the tmp directory if it doesn't exist" do
|
40
|
-
temporary_directory = Stud::Temporary.pathname("temporary_directory")
|
41
|
-
|
42
|
-
config = {
|
43
|
-
"access_key_id" => "1234",
|
44
|
-
"secret_access_key" => "secret",
|
45
|
-
"bucket" => "logstash",
|
46
|
-
"size_file" => 10,
|
47
|
-
"temporary_directory" => temporary_directory
|
48
|
-
}
|
49
|
-
|
50
|
-
s3 = LogStash::Outputs::S3.new(config)
|
51
|
-
allow(s3).to receive(:test_s3_write)
|
52
|
-
s3.register
|
53
|
-
|
54
|
-
expect(Dir.exist?(temporary_directory)).to eq(true)
|
55
|
-
s3.close
|
56
|
-
FileUtils.rm_r(temporary_directory)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should raise a ConfigurationError if the prefix contains one or more '\^`><' characters" do
|
60
|
-
config = {
|
61
|
-
"prefix" => "`no\><^"
|
62
|
-
}
|
63
|
-
|
64
|
-
s3 = LogStash::Outputs::S3.new(config)
|
65
|
-
|
66
|
-
expect {
|
67
|
-
s3.register
|
68
|
-
}.to raise_error(LogStash::ConfigurationError)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "#generate_temporary_filename" do
|
73
|
-
before do
|
74
|
-
allow(Socket).to receive(:gethostname) { "logstash.local" }
|
75
|
-
end
|
76
|
-
|
77
|
-
it "should add tags to the filename if present" do
|
78
|
-
config = minimal_settings.merge({ "tags" => ["elasticsearch", "logstash", "kibana"], "temporary_directory" => "/tmp/logstash"})
|
79
|
-
s3 = LogStash::Outputs::S3.new(config)
|
80
|
-
expect(s3.get_temporary_filename).to match(/^ls\.s3\.logstash\.local\.\d{4}-\d{2}\-\d{2}T\d{2}\.\d{2}\.tag_#{config["tags"].join("\.")}\.part0\.txt\Z/)
|
81
|
-
end
|
82
|
-
|
83
|
-
it "should not add the tags to the filename" do
|
84
|
-
config = minimal_settings.merge({ "tags" => [], "temporary_directory" => "/tmp/logstash" })
|
85
|
-
s3 = LogStash::Outputs::S3.new(config)
|
86
|
-
expect(s3.get_temporary_filename(3)).to match(/^ls\.s3\.logstash\.local\.\d{4}-\d{2}\-\d{2}T\d{2}\.\d{2}\.part3\.txt\Z/)
|
87
|
-
end
|
88
|
-
|
89
|
-
it "normalized the temp directory to include the trailing slash if missing" do
|
90
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings.merge({ "temporary_directory" => "/tmp/logstash" }))
|
91
|
-
expect(s3.get_temporary_filename).to match(/^ls\.s3\.logstash\.local\.\d{4}-\d{2}\-\d{2}T\d{2}\.\d{2}\.part0\.txt\Z/)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
describe "#write_on_bucket" do
|
96
|
-
let!(:fake_data) { Stud::Temporary.file }
|
97
|
-
|
98
|
-
let(:fake_bucket) do
|
99
|
-
s3 = double('S3Object')
|
100
|
-
allow(s3).to receive(:write)
|
101
|
-
s3
|
102
|
-
end
|
103
|
-
|
104
|
-
it "should prefix the file on the bucket if a prefix is specified" do
|
105
|
-
prefix = "my-prefix"
|
106
|
-
|
107
|
-
config = minimal_settings.merge({
|
108
|
-
"prefix" => prefix,
|
109
|
-
"bucket" => "my-bucket"
|
110
|
-
})
|
111
|
-
|
112
|
-
expect_any_instance_of(AWS::S3::ObjectCollection).to receive(:[]).with("#{prefix}#{File.basename(fake_data)}") { fake_bucket }
|
113
|
-
|
114
|
-
s3 = LogStash::Outputs::S3.new(config)
|
115
|
-
allow(s3).to receive(:test_s3_write)
|
116
|
-
s3.register
|
117
|
-
s3.write_on_bucket(fake_data)
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'should use the same local filename if no prefix is specified' do
|
121
|
-
config = minimal_settings.merge({
|
122
|
-
"bucket" => "my-bucket"
|
123
|
-
})
|
124
|
-
|
125
|
-
expect_any_instance_of(AWS::S3::ObjectCollection).to receive(:[]).with(File.basename(fake_data)) { fake_bucket }
|
126
|
-
|
127
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings)
|
128
|
-
allow(s3).to receive(:test_s3_write)
|
129
|
-
s3.register
|
130
|
-
s3.write_on_bucket(fake_data)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
describe "#write_events_to_multiple_files?" do
|
135
|
-
it 'returns true if the size_file is != 0 ' do
|
136
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings.merge({ "size_file" => 200 }))
|
137
|
-
expect(s3.write_events_to_multiple_files?).to eq(true)
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'returns false if size_file is zero or not set' do
|
141
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings)
|
142
|
-
expect(s3.write_events_to_multiple_files?).to eq(false)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
describe "#write_to_tempfile" do
|
147
|
-
it "should append the event to a file" do
|
148
|
-
Stud::Temporary.file("logstash", "a+") do |tmp|
|
149
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings)
|
150
|
-
allow(s3).to receive(:test_s3_write)
|
151
|
-
s3.register
|
152
|
-
s3.tempfile = tmp
|
153
|
-
s3.write_to_tempfile("test-write")
|
154
|
-
tmp.rewind
|
155
|
-
expect(tmp.read).to eq("test-write")
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
describe "#rotate_events_log" do
|
161
|
-
|
162
|
-
context "having a single worker" do
|
163
|
-
let(:s3) { LogStash::Outputs::S3.new(minimal_settings.merge({ "size_file" => 1024 })) }
|
164
|
-
|
165
|
-
before(:each) do
|
166
|
-
s3.register
|
167
|
-
end
|
168
|
-
|
169
|
-
it "returns true if the tempfile is over the file_size limit" do
|
170
|
-
Stud::Temporary.file do |tmp|
|
171
|
-
allow(tmp).to receive(:size) { 2024001 }
|
172
|
-
|
173
|
-
s3.tempfile = tmp
|
174
|
-
expect(s3.rotate_events_log?).to be(true)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
it "returns false if the tempfile is under the file_size limit" do
|
179
|
-
Stud::Temporary.file do |tmp|
|
180
|
-
allow(tmp).to receive(:size) { 100 }
|
181
|
-
|
182
|
-
s3.tempfile = tmp
|
183
|
-
expect(s3.rotate_events_log?).to eq(false)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
45
|
|
188
|
-
|
189
|
-
let(:
|
190
|
-
let(:
|
191
|
-
|
192
|
-
before(:each) do
|
193
|
-
s3.tempfile = tmp
|
194
|
-
s3.register
|
195
|
-
end
|
46
|
+
describe "temporary directory" do
|
47
|
+
let(:temporary_directory) { Stud::Temporary.pathname }
|
48
|
+
let(:options) { super.merge({ "temporary_directory" => temporary_directory }) }
|
196
49
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
50
|
+
it "creates the directory when it doesn't exist" do
|
51
|
+
expect(Dir.exist?(temporary_directory)).to be_falsey
|
52
|
+
subject.register
|
53
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
201
54
|
end
|
202
55
|
|
203
|
-
it "raises
|
204
|
-
|
205
|
-
|
206
|
-
end
|
56
|
+
it "raises an error if we cannot write to the directory" do
|
57
|
+
expect(LogStash::Outputs::S3::WritableDirectoryValidator).to receive(:valid?).with(temporary_directory).and_return(false)
|
58
|
+
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
|
207
59
|
end
|
208
60
|
end
|
209
|
-
end
|
210
|
-
|
211
|
-
describe "#move_file_to_bucket" do
|
212
|
-
subject { LogStash::Outputs::S3.new(minimal_settings) }
|
213
61
|
|
214
|
-
it "
|
215
|
-
|
216
|
-
|
217
|
-
allow(File).to receive(:zero?).and_return(true)
|
218
|
-
expect(File).to receive(:delete).with(tmp)
|
219
|
-
|
220
|
-
subject.move_file_to_bucket(tmp)
|
62
|
+
it "validates the prefix" do
|
63
|
+
s3 = described_class.new(options.merge({ "prefix" => "`no\><^" }))
|
64
|
+
expect { s3.register }.to raise_error(LogStash::ConfigurationError)
|
221
65
|
end
|
222
66
|
|
223
|
-
it
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
expect(subject).not_to receive(:write_on_bucket)
|
228
|
-
subject.move_file_to_bucket(temp_file)
|
229
|
-
end
|
230
|
-
|
231
|
-
it "should upload the file if the size > 0" do
|
232
|
-
tmp = Stud::Temporary.file
|
233
|
-
|
234
|
-
allow(File).to receive(:zero?).and_return(false)
|
235
|
-
expect(subject).to receive(:write_on_bucket)
|
236
|
-
|
237
|
-
subject.move_file_to_bucket(tmp)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
describe "#restore_from_crashes" do
|
242
|
-
it "read the temp directory and upload the matching file to s3" do
|
243
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings.merge({ "temporary_directory" => "/tmp/logstash/" }))
|
244
|
-
|
245
|
-
expect(Dir).to receive(:[]).with("/tmp/logstash/*.txt").and_return(["/tmp/logstash/01.txt"])
|
246
|
-
expect(s3).to receive(:move_file_to_bucket_async).with("/tmp/logstash/01.txt")
|
247
|
-
|
248
|
-
|
249
|
-
s3.restore_from_crashes
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
describe "#receive" do
|
254
|
-
it "should send the event through the codecs" do
|
255
|
-
data = {"foo" => "bar", "baz" => {"bah" => ["a","b","c"]}, "@timestamp" => "2014-05-30T02:52:17.929Z"}
|
256
|
-
event = LogStash::Event.new(data)
|
257
|
-
|
258
|
-
expect_any_instance_of(LogStash::Codecs::Line).to receive(:encode).with(event)
|
259
|
-
|
260
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings)
|
261
|
-
allow(s3).to receive(:test_s3_write)
|
67
|
+
it "allow to not validate credentials" do
|
68
|
+
s3 = described_class.new(options.merge({"validate_credentials_on_root_bucket" => false}))
|
69
|
+
expect(LogStash::Outputs::S3::WriteBucketPermissionValidator).not_to receive(:valid?).with(any_args)
|
262
70
|
s3.register
|
263
|
-
|
264
|
-
s3.receive(event)
|
265
71
|
end
|
266
72
|
end
|
267
73
|
|
268
|
-
|
269
|
-
before
|
270
|
-
|
271
|
-
it "doesn't skip events if using the size_file option" do
|
272
|
-
Stud::Temporary.directory do |temporary_directory|
|
273
|
-
size_file = rand(200..20000)
|
274
|
-
event_count = rand(300..15000)
|
275
|
-
|
276
|
-
config = %Q[
|
277
|
-
input {
|
278
|
-
generator {
|
279
|
-
count => #{event_count}
|
280
|
-
}
|
281
|
-
}
|
282
|
-
output {
|
283
|
-
s3 {
|
284
|
-
access_key_id => "1234"
|
285
|
-
secret_access_key => "secret"
|
286
|
-
size_file => #{size_file}
|
287
|
-
codec => line
|
288
|
-
temporary_directory => '#{temporary_directory}'
|
289
|
-
bucket => 'testing'
|
290
|
-
}
|
291
|
-
}
|
292
|
-
]
|
293
|
-
|
294
|
-
pipeline = LogStash::Pipeline.new(config)
|
295
|
-
|
296
|
-
pipeline_thread = Thread.new { pipeline.run }
|
297
|
-
sleep 0.1 while !pipeline.ready?
|
298
|
-
pipeline_thread.join
|
299
|
-
|
300
|
-
events_written_count = events_in_files(Dir[File.join(temporary_directory, 'ls.*.txt')])
|
301
|
-
expect(events_written_count).to eq(event_count)
|
302
|
-
end
|
74
|
+
context "receiving events" do
|
75
|
+
before do
|
76
|
+
subject.register
|
303
77
|
end
|
304
78
|
|
305
|
-
|
306
|
-
|
307
|
-
{
|
308
|
-
"access_key_id" => 1234,
|
309
|
-
"secret_access_key" => "secret",
|
310
|
-
"bucket" => "mahbucket"
|
311
|
-
}
|
312
|
-
end
|
313
|
-
subject do
|
314
|
-
::LogStash::Outputs::S3.new(options)
|
315
|
-
end
|
316
|
-
|
317
|
-
before do
|
318
|
-
subject.register
|
319
|
-
end
|
320
|
-
|
321
|
-
it "should be clean" do
|
322
|
-
subject.do_close
|
323
|
-
end
|
324
|
-
|
325
|
-
it "should remove all worker threads" do
|
326
|
-
subject.do_close
|
327
|
-
sleep 1
|
328
|
-
expect(subject.upload_workers.map(&:thread).any?(&:alive?)).to be false
|
329
|
-
end
|
79
|
+
after do
|
80
|
+
subject.close
|
330
81
|
end
|
331
82
|
|
332
|
-
it "
|
333
|
-
|
334
|
-
|
335
|
-
number_of_rotation = rand(2..5)
|
336
|
-
|
337
|
-
config = {
|
338
|
-
"time_file" => time_file,
|
339
|
-
"codec" => "line",
|
340
|
-
"temporary_directory" => temporary_directory,
|
341
|
-
"bucket" => "testing"
|
342
|
-
}
|
343
|
-
|
344
|
-
s3 = LogStash::Outputs::S3.new(minimal_settings.merge(config))
|
345
|
-
# Make the test run in seconds intead of minutes..
|
346
|
-
expect(s3).to receive(:periodic_interval).and_return(time_file)
|
347
|
-
s3.register
|
348
|
-
|
349
|
-
# Force to have a few files rotation
|
350
|
-
stop_time = Time.now + (number_of_rotation * time_file)
|
351
|
-
event_count = 0
|
352
|
-
|
353
|
-
event = LogStash::Event.new("message" => "Hello World")
|
354
|
-
|
355
|
-
until Time.now > stop_time do
|
356
|
-
s3.receive(event)
|
357
|
-
event_count += 1
|
358
|
-
end
|
359
|
-
s3.close
|
360
|
-
|
361
|
-
generated_files = Dir[File.join(temporary_directory, 'ls.*.txt')]
|
362
|
-
|
363
|
-
events_written_count = events_in_files(generated_files)
|
364
|
-
|
365
|
-
# Skew times can affect the number of rotation..
|
366
|
-
expect(generated_files.count).to be_within(number_of_rotation).of(number_of_rotation + 1)
|
367
|
-
expect(events_written_count).to eq(event_count)
|
368
|
-
end
|
83
|
+
it "uses `Event#sprintf` for the prefix" do
|
84
|
+
expect(event).to receive(:sprintf).with(prefix).and_return("super/overwatch")
|
85
|
+
subject.multi_receive_encoded(events_and_encoded)
|
369
86
|
end
|
370
87
|
end
|
371
88
|
end
|