logstash-output-azure 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.
@@ -0,0 +1,143 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/Logstash_Azure_Blob_Output"
3
+ require "stud/temporary"
4
+ require "fileutils"
5
+ require_relative "../../spec_helper"
6
+
7
+ describe LogStash::Outputs::LogstashAzureBlobOutput::FileRepository do
8
+ let(:tags) { [] }
9
+ let(:encoding) { "none" }
10
+ let(:temporary_directory) { Stud::Temporary.pathname }
11
+ let(:prefix_key) { "a-key" }
12
+
13
+ before do
14
+ FileUtils.mkdir_p(temporary_directory)
15
+ end
16
+
17
+ subject { described_class.new(tags, encoding, temporary_directory) }
18
+
19
+ it "returns a temporary file" do
20
+ subject.get_file(prefix_key) do |file|
21
+ expect(file).to be_kind_of(LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFile)
22
+ end
23
+ end
24
+
25
+ it "returns the same file for the same prefix key" do
26
+ file_path = nil
27
+
28
+ subject.get_file(prefix_key) do |file|
29
+ file_path = file.path
30
+ end
31
+
32
+ subject.get_file(prefix_key) do |file|
33
+ expect(file.path).to eq(file_path)
34
+ end
35
+ end
36
+
37
+ it "returns the same file for the same dynamic prefix key" do
38
+ prefix = "%{type}/%{+YYYY}/%{+MM}/%{+dd}/"
39
+ event = LogStash::Event.new({ "type" => "syslog"})
40
+ key = event.sprintf(prefix)
41
+ file_path = nil
42
+
43
+
44
+ subject.get_file(key) do |file|
45
+ file_path = file.path
46
+ end
47
+
48
+ subject.get_file(key) do |file|
49
+ expect(file.path).to eq(file_path)
50
+ end
51
+ end
52
+
53
+ it "returns different file for different prefix keys" do
54
+ file_path = nil
55
+
56
+ subject.get_file(prefix_key) do |file|
57
+ file_path = file.path
58
+ end
59
+
60
+ subject.get_file("another_prefix_key") do |file|
61
+ expect(file.path).not_to eq(file_path)
62
+ end
63
+ end
64
+
65
+ it "allows to get the file factory for a specific prefix" do
66
+ subject.get_factory(prefix_key) do |factory|
67
+ expect(factory).to be_kind_of(LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFileFactory)
68
+ end
69
+ end
70
+
71
+ it "returns a different file factory for a different prefix keys" do
72
+ factory = nil
73
+
74
+ subject.get_factory(prefix_key) do |f|
75
+ factory = f
76
+ end
77
+
78
+ subject.get_factory("another_prefix_key") do |f|
79
+ expect(factory).not_to eq(f)
80
+ end
81
+ end
82
+
83
+ it "returns the number of prefix keys" do
84
+ expect(subject.size).to eq(0)
85
+ subject.get_file(prefix_key) { |file| file.write("something") }
86
+ expect(subject.size).to eq(1)
87
+ end
88
+
89
+ it "returns all available keys" do
90
+ subject.get_file(prefix_key) { |file| file.write("something") }
91
+ expect(subject.keys.toArray).to include(prefix_key)
92
+ expect(subject.keys.toArray.size).to eq(1)
93
+ end
94
+
95
+ it "clean stale factories" do
96
+ @file_repository = described_class.new(tags, encoding, temporary_directory, 1, 1)
97
+ expect(@file_repository.size).to eq(0)
98
+ path = ""
99
+ @file_repository.get_factory(prefix_key) do |factory|
100
+ factory.current.write("hello")
101
+ # force a rotation so we get an empty file that will get stale.
102
+ factory.rotate!
103
+ path = factory.current.temp_path
104
+ end
105
+
106
+ @file_repository.get_file("another-prefix") { |file| file.write("hello") }
107
+ expect(@file_repository.size).to eq(2)
108
+ try(10) { expect(@file_repository.size).to eq(1) }
109
+ expect(File.directory?(path)).to be_falsey
110
+ end
111
+ end
112
+
113
+
114
+ describe LogStash::Outputs::LogstashAzureBlobOutput::FileRepository::PrefixedValue do
115
+ let(:factory) { spy("factory", :current => file) }
116
+ subject { described_class.new(factory, 1) }
117
+
118
+ context "#stale?" do
119
+ context "the file is empty and older than stale time" do
120
+ let(:file) { double("file", :size => 0, :ctime => Time.now - 5) }
121
+
122
+ it "returns true" do
123
+ expect(subject.stale?).to be_truthy
124
+ end
125
+ end
126
+
127
+ context "when the file has data in it" do
128
+ let(:file) { double("file", :size => 200, :ctime => Time.now - 5) }
129
+
130
+ it "returns false" do
131
+ expect(subject.stale?).to be_falsey
132
+ end
133
+ end
134
+
135
+ context "when the file is not old enough" do
136
+ let(:file) { double("file", :size => 0, :ctime => Time.now + 100) }
137
+
138
+ it "returns false" do
139
+ expect(subject.stale?).to be_falsey
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/blob/size_and_time_rotation_policy"
4
+ require "logstash/outputs/blob/temporary_file"
5
+
6
+ describe LogStash::Outputs::LogstashAzureBlobOutput::SizeAndTimeRotationPolicy do
7
+ let(:file_size) { 10 }
8
+ let(:time_file) { 1 }
9
+ subject { described_class.new(file_size, time_file) }
10
+
11
+ let(:temporary_directory) { Stud::Temporary.pathname }
12
+ let(:temporary_file) { Stud::Temporary.file }
13
+ let(:name) { "foobar" }
14
+ let(:content) { "hello" * 1000 }
15
+ let(:file) { LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFile.new(name, temporary_file, temporary_directory) }
16
+
17
+ it "raises an exception if the `time_file` is set to 0" do
18
+ expect { described_class.new(100, 0) }.to raise_error(LogStash::ConfigurationError, /time_file/)
19
+ end
20
+
21
+ it "raises an exception if the `time_file` is < 0" do
22
+ expect { described_class.new(100, -100) }.to raise_error(LogStash::ConfigurationError, /time_file/)
23
+ end
24
+
25
+ it "raises an exception if the `size_file` is 0" do
26
+ expect { described_class.new(0, 100) }.to raise_error(LogStash::ConfigurationError, /size_file/)
27
+ end
28
+
29
+ it "raises an exception if the `size_file` is < 0" do
30
+ expect { described_class.new(-100, 100) }.to raise_error(LogStash::ConfigurationError, /size_file/)
31
+ end
32
+
33
+ it "returns true if the size on disk is higher than the `file_size`" do
34
+ file.write(content)
35
+ file.fsync
36
+ expect(subject.rotate?(file)).to be_truthy
37
+ end
38
+
39
+ it "returns false if the size is inferior than the `file_size`" do
40
+ expect(subject.rotate?(file)).to be_falsey
41
+ end
42
+
43
+ context "when the size of the file is superior to 0" do
44
+ let(:file_size) { 10000 }
45
+
46
+ before :each do
47
+ file.write(content)
48
+ file.fsync
49
+ end
50
+
51
+ it "returns true if the file old enough" do
52
+ allow(file).to receive(:ctime).and_return(Time.now - (time_file * 2 * 60) )
53
+ expect(subject.rotate?(file)).to be_truthy
54
+ end
55
+
56
+ it "returns false is not old enough" do
57
+ allow(file).to receive(:ctime).and_return(Time.now + time_file * 10)
58
+ expect(subject.rotate?(file)).to be_falsey
59
+ end
60
+ end
61
+
62
+ context "When the size of the file is 0" do
63
+ it "returns false if the file old enough" do
64
+ expect(subject.rotate?(file)).to be_falsey
65
+ end
66
+
67
+ it "returns false is not old enough" do
68
+ expect(subject.rotate?(file)).to be_falsey
69
+ end
70
+ end
71
+
72
+ context "#needs_periodic?" do
73
+ it "return true" do
74
+ expect(subject.needs_periodic?).to be_truthy
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/blob/size_rotation_policy"
4
+ require "logstash/outputs/blob/temporary_file"
5
+ require "fileutils"
6
+
7
+ describe LogStash::Outputs::LogstashAzureBlobOutput::SizeRotationPolicy do
8
+ subject { described_class.new(size_file) }
9
+
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(:size_file) { 10 } # in bytes
15
+ let(:file) { LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFile.new(name, temporary_file, temporary_directory) }
16
+
17
+ it "returns true if the size on disk is higher than the `size_file`" do
18
+ file.write(content)
19
+ file.fsync
20
+ expect(subject.rotate?(file)).to be_truthy
21
+ end
22
+
23
+ it "returns false if the size is inferior than the `size_file`" do
24
+ expect(subject.rotate?(file)).to be_falsey
25
+ end
26
+
27
+ it "raises an exception if the `size_file` is 0" do
28
+ expect { described_class.new(0) }.to raise_error(LogStash::ConfigurationError, /need to be greather than 0/)
29
+ end
30
+
31
+ it "raises an exception if the `size_file` is < 0" do
32
+ expect { described_class.new(-100) }.to raise_error(LogStash::ConfigurationError, /need to be greather than 0/)
33
+ end
34
+
35
+ context "#needs_periodic?" do
36
+ it "return false" do
37
+ expect(subject.needs_periodic?).to be_falsey
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/blob/temporary_file_factory"
3
+ require "logstash/outputs/blob/temporary_file"
4
+ require "stud/temporary"
5
+ require "fileutils"
6
+
7
+ describe LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFileFactory do
8
+ let(:prefix) { "foobar" }
9
+ let(:tags) { [] }
10
+ let(:temporary_directory) { Stud::Temporary.pathname }
11
+
12
+ before do
13
+ FileUtils.mkdir_p(temporary_directory)
14
+ end
15
+
16
+ subject { described_class.new(prefix, tags, encoding, temporary_directory) }
17
+
18
+ shared_examples "file factory" do
19
+ it "creates the file on disk" do
20
+ expect(File.exist?(subject.current.path)).to be_truthy
21
+ end
22
+
23
+ it "returns a size equal to zero after file creation" do
24
+ expect(subject.current.size).to eq(0)
25
+ end
26
+
27
+ it "create a temporary file when initialized" do
28
+ expect(subject.current).to be_kind_of(LogStash::Outputs::LogstashAzureBlobOutput::TemporaryFile)
29
+ end
30
+
31
+ it "create a file in the right format" do
32
+ expect(subject.current.path).to match(extension)
33
+ end
34
+
35
+ it "allow to rotate the file" do
36
+ file_path = subject.current.path
37
+ expect(subject.rotate!.path).not_to eq(file_path)
38
+ end
39
+
40
+ it "increments the part name on rotation" do
41
+ expect(subject.current.path).to match(/part0/)
42
+ expect(subject.rotate!.path).to match(/part1/)
43
+ end
44
+
45
+ it "includes the date" do
46
+ n = Time.now
47
+ expect(subject.current.path).to include(n.strftime("%Y-%m-%dT"))
48
+ end
49
+
50
+ it "include the file key in the path" do
51
+ file = subject.current
52
+ expect(file.path).to match(/#{file.key}/)
53
+ end
54
+
55
+ it "create a unique directory in the temporary directory for each file" do
56
+ uuid = "hola"
57
+ expect(SecureRandom).to receive(:uuid).and_return(uuid).twice
58
+ expect(subject.current.path).to include(uuid)
59
+ end
60
+
61
+ context "with tags supplied" do
62
+ let(:tags) { ["secret", "service"] }
63
+
64
+ it "adds tags to the filename" do
65
+ expect(subject.current.path).to match(/tag_#{tags.join('.')}.part/)
66
+ end
67
+ end
68
+
69
+ context "without tags" do
70
+ it "doesn't add tags to the filename" do
71
+ expect(subject.current.path).not_to match(/tag_/)
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when gzip" do
77
+ let(:encoding) { "gzip" }
78
+ let(:extension) { /\.txt.gz$/ }
79
+
80
+ include_examples "file factory"
81
+ end
82
+
83
+ context "when encoding set to `none`" do
84
+ let(:encoding) { "none" }
85
+ let(:extension) { /\.txt$/ }
86
+
87
+ include_examples "file factory"
88
+ end
89
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/blob/temporary_file"
4
+ require "stud/temporary"
5
+ require "fileutils"
6
+ require "securerandom"
7
+
8
+ describe LogStash::Outputs::LogstashAzureBlobOutput::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 "successfully delete a file already closed" do
38
+ subject.close
39
+ expect(File.exist?(subject.path)).to be_truthy
40
+ subject.delete!
41
+ expect(File.exist?(subject.path)).to be_falsey
42
+ end
43
+
44
+ it "returns the creation time" do
45
+ expect(subject.ctime).to be < Time.now + 0.5
46
+ end
47
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/blob/time_rotation_policy"
4
+ require "logstash/outputs/blob/temporary_file"
5
+
6
+ describe LogStash::Outputs::LogstashAzureBlobOutput::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::LogstashAzureBlobOutput::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