logstash-integration-aws 0.1.0.pre

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.PRE.MERGE.md +658 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTORS +40 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE +202 -0
  7. data/NOTICE.TXT +5 -0
  8. data/README.md +205 -0
  9. data/docs/codec-cloudfront.asciidoc +53 -0
  10. data/docs/codec-cloudtrail.asciidoc +45 -0
  11. data/docs/index.asciidoc +38 -0
  12. data/docs/input-cloudwatch.asciidoc +320 -0
  13. data/docs/input-s3.asciidoc +346 -0
  14. data/docs/input-sqs.asciidoc +287 -0
  15. data/docs/output-cloudwatch.asciidoc +321 -0
  16. data/docs/output-s3.asciidoc +442 -0
  17. data/docs/output-sns.asciidoc +166 -0
  18. data/docs/output-sqs.asciidoc +242 -0
  19. data/lib/logstash/codecs/cloudfront.rb +84 -0
  20. data/lib/logstash/codecs/cloudtrail.rb +47 -0
  21. data/lib/logstash/inputs/cloudwatch.rb +338 -0
  22. data/lib/logstash/inputs/s3.rb +466 -0
  23. data/lib/logstash/inputs/sqs.rb +196 -0
  24. data/lib/logstash/outputs/cloudwatch.rb +346 -0
  25. data/lib/logstash/outputs/s3/file_repository.rb +121 -0
  26. data/lib/logstash/outputs/s3/path_validator.rb +18 -0
  27. data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
  28. data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
  29. data/lib/logstash/outputs/s3/temporary_file.rb +71 -0
  30. data/lib/logstash/outputs/s3/temporary_file_factory.rb +129 -0
  31. data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
  32. data/lib/logstash/outputs/s3/uploader.rb +74 -0
  33. data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
  34. data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
  35. data/lib/logstash/outputs/s3.rb +405 -0
  36. data/lib/logstash/outputs/sns.rb +133 -0
  37. data/lib/logstash/outputs/sqs.rb +167 -0
  38. data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
  39. data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
  40. data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
  41. data/logstash-integration-aws.gemspec +52 -0
  42. data/spec/codecs/cloudfront_spec.rb +92 -0
  43. data/spec/codecs/cloudtrail_spec.rb +56 -0
  44. data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
  45. data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
  46. data/spec/fixtures/cloudfront.log +4 -0
  47. data/spec/fixtures/compressed.log.gee.zip +0 -0
  48. data/spec/fixtures/compressed.log.gz +0 -0
  49. data/spec/fixtures/compressed.log.gzip +0 -0
  50. data/spec/fixtures/invalid_utf8.gbk.log +2 -0
  51. data/spec/fixtures/json.log +2 -0
  52. data/spec/fixtures/json_with_message.log +2 -0
  53. data/spec/fixtures/multiline.log +6 -0
  54. data/spec/fixtures/multiple_compressed_streams.gz +0 -0
  55. data/spec/fixtures/uncompressed.log +2 -0
  56. data/spec/inputs/cloudwatch_spec.rb +85 -0
  57. data/spec/inputs/s3_spec.rb +610 -0
  58. data/spec/inputs/sincedb_spec.rb +17 -0
  59. data/spec/inputs/sqs_spec.rb +324 -0
  60. data/spec/integration/cloudwatch_spec.rb +25 -0
  61. data/spec/integration/dynamic_prefix_spec.rb +92 -0
  62. data/spec/integration/gzip_file_spec.rb +62 -0
  63. data/spec/integration/gzip_size_rotation_spec.rb +63 -0
  64. data/spec/integration/outputs/sqs_spec.rb +98 -0
  65. data/spec/integration/restore_from_crash_spec.rb +67 -0
  66. data/spec/integration/s3_spec.rb +66 -0
  67. data/spec/integration/size_rotation_spec.rb +59 -0
  68. data/spec/integration/sqs_spec.rb +110 -0
  69. data/spec/integration/stress_test_spec.rb +60 -0
  70. data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
  71. data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
  72. data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
  73. data/spec/outputs/cloudwatch_spec.rb +38 -0
  74. data/spec/outputs/s3/file_repository_spec.rb +143 -0
  75. data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
  76. data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
  77. data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
  78. data/spec/outputs/s3/temporary_file_spec.rb +47 -0
  79. data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
  80. data/spec/outputs/s3/uploader_spec.rb +69 -0
  81. data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
  82. data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
  83. data/spec/outputs/s3_spec.rb +232 -0
  84. data/spec/outputs/sns_spec.rb +160 -0
  85. data/spec/plugin_mixin/aws_config_spec.rb +217 -0
  86. data/spec/spec_helper.rb +8 -0
  87. data/spec/support/helpers.rb +119 -0
  88. data/spec/unit/outputs/sqs_spec.rb +247 -0
  89. metadata +467 -0
@@ -0,0 +1,324 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "logstash/devutils/rspec/shared_examples"
4
+ require "logstash/inputs/sqs"
5
+ require "logstash/errors"
6
+ require "logstash/event"
7
+ require "logstash/json"
8
+ require "aws-sdk-sqs"
9
+ require "ostruct"
10
+
11
+ describe LogStash::Inputs::SQS do
12
+ let(:queue_name) { "the-infinite-pandora-box" }
13
+ let(:queue_url) { "https://sqs.test.local/#{queue_name}" }
14
+ let(:config) do
15
+ {
16
+ "region" => "us-east-1",
17
+ "access_key_id" => "123",
18
+ "secret_access_key" => "secret",
19
+ "queue" => queue_name
20
+ }
21
+ end
22
+
23
+ let(:input) { LogStash::Inputs::SQS.new(config) }
24
+ let(:decoded_message) { { "bonjour" => "awesome" } }
25
+ let(:encoded_message) { double("sqs_message", :body => LogStash::Json::dump(decoded_message)) }
26
+
27
+ subject { input }
28
+
29
+ let(:mock_sqs) { Aws::SQS::Client.new({ :stub_responses => true }) }
30
+
31
+
32
+ context "with invalid credentials" do
33
+ before do
34
+ expect(Aws::SQS::Client).to receive(:new).and_return(mock_sqs)
35
+ expect(mock_sqs).to receive(:get_queue_url).with({ :queue_name => queue_name }) { raise Aws::SQS::Errors::ServiceError.new("bad-something", "bad token") }
36
+ end
37
+
38
+ it "raises a Configuration error if the credentials are bad" do
39
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
40
+ end
41
+ end
42
+
43
+ context "valid credentials" do
44
+ let(:queue) { [] }
45
+
46
+ it "doesn't raise an error with valid credentials" do
47
+ expect(Aws::SQS::Client).to receive(:new).and_return(mock_sqs)
48
+ expect(mock_sqs).to receive(:get_queue_url).with({ :queue_name => queue_name }).and_return({:queue_url => queue_url })
49
+ expect { subject.register }.not_to raise_error
50
+ end
51
+
52
+ context "when queue_aws_account_id option is specified" do
53
+ let(:queue_account_id) { "123456789012" }
54
+ let(:config) do
55
+ {
56
+ "region" => "us-east-1",
57
+ "access_key_id" => "123",
58
+ "secret_access_key" => "secret",
59
+ "queue" => queue_name,
60
+ "queue_owner_aws_account_id" => queue_account_id
61
+ }
62
+ end
63
+ it "passes the option to sqs client" do
64
+ expect(Aws::SQS::Client).to receive(:new).and_return(mock_sqs)
65
+ expect(mock_sqs).to receive(:get_queue_url).with({ :queue_name => queue_name, :queue_owner_aws_account_id => queue_account_id }).and_return({:queue_url => queue_url })
66
+ expect { subject.register }.not_to raise_error
67
+ end
68
+ end
69
+
70
+ describe "additional_settings" do
71
+ context "supported settings" do
72
+ let(:config) {
73
+ {
74
+ "additional_settings" => { "force_path_style" => 'true', "ssl_verify_peer" => 'false', "profile" => 'logstash' },
75
+ "queue" => queue_name
76
+ }
77
+ }
78
+
79
+ it 'should instantiate Aws::SQS clients with force_path_style set' do
80
+ expect(Aws::SQS::Client).to receive(:new).and_return(mock_sqs)
81
+ # mock a remote call to retrieve the queue URL
82
+ expect(mock_sqs).to receive(:get_queue_url).with({ :queue_name => queue_name }).and_return({:queue_url => queue_url })
83
+
84
+ expect(subject.aws_options_hash).to include({:force_path_style => true, :ssl_verify_peer => false, :profile => 'logstash'})
85
+
86
+ expect { subject.register }.not_to raise_error
87
+ end
88
+ end
89
+
90
+ context "unsupported settings" do
91
+ let(:config) {
92
+ {
93
+ "additional_settings" => { "stub_responses" => 'true', "invalid_option" => "invalid" },
94
+ "queue" => queue_name
95
+ }
96
+ }
97
+
98
+ it 'must fail with ArgumentError' do
99
+ expect {subject.register}.to raise_error(ArgumentError, /invalid_option/)
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ context "when interrupting the plugin" do
106
+ before do
107
+ expect(Aws::SQS::Client).to receive(:new).and_return(mock_sqs)
108
+ expect(mock_sqs).to receive(:get_queue_url).with({ :queue_name => queue_name }).and_return({:queue_url => queue_url })
109
+ expect(subject).to receive(:poller).and_return(mock_sqs).at_least(:once)
110
+
111
+ # We have to make sure we create a bunch of events
112
+ # so we actually really try to stop the plugin.
113
+ #
114
+ # rspec's `and_yield` allow you to define a fix amount of possible
115
+ # yielded values and doesn't allow you to create infinite loop.
116
+ # And since we are actually creating thread we need to make sure
117
+ # we have enough work to keep the thread working until we kill it..
118
+ #
119
+ # I haven't found a way to make it rspec friendly
120
+ mock_sqs.instance_eval do
121
+ def poll(polling_options = {})
122
+ loop do
123
+ yield [OpenStruct.new(:body => LogStash::Json::dump({ "message" => "hello world"}))], OpenStruct.new
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ it_behaves_like "an interruptible input plugin"
130
+ end
131
+
132
+ context "enrich event" do
133
+ let(:event) { LogStash::Event.new }
134
+
135
+ let(:message_id) { "123" }
136
+ let(:md5_of_body) { "dr strange" }
137
+ let(:sent_timestamp) { LogStash::Timestamp.new }
138
+ let(:epoch_timestamp) { (sent_timestamp.utc.to_f * 1000).to_i }
139
+
140
+ let(:id_field) { "my_id_field" }
141
+ let(:md5_field) { "my_md5_field" }
142
+ let(:sent_timestamp_field) { "my_sent_timestamp_field" }
143
+
144
+ let(:message) do
145
+ double("message", :message_id => message_id, :md5_of_body => md5_of_body, :attributes => { LogStash::Inputs::SQS::SENT_TIMESTAMP => epoch_timestamp } )
146
+ end
147
+
148
+ subject { input.add_sqs_data(event, message) }
149
+
150
+ context "when the option is specified" do
151
+ let(:config) do
152
+ {
153
+ "region" => "us-east-1",
154
+ "access_key_id" => "123",
155
+ "secret_access_key" => "secret",
156
+ "queue" => queue_name,
157
+ "id_field" => id_field,
158
+ "md5_field" => md5_field,
159
+ "sent_timestamp_field" => sent_timestamp_field
160
+ }
161
+ end
162
+
163
+ it "add the `message_id`" do
164
+ expect(subject.get(id_field)).to eq(message_id)
165
+ end
166
+
167
+ it "add the `md5_of_body`" do
168
+ expect(subject.get(md5_field)).to eq(md5_of_body)
169
+ end
170
+
171
+ it "add the `sent_timestamp`" do
172
+ expect(subject.get(sent_timestamp_field).to_i).to eq(sent_timestamp.to_i)
173
+ end
174
+ end
175
+
176
+ context "when the option isn't specified" do
177
+ it "doesnt add the `message_id`" do
178
+ expect(subject).not_to include(id_field)
179
+ end
180
+
181
+ it "doesnt add the `md5_of_body`" do
182
+ expect(subject).not_to include(md5_field)
183
+ end
184
+
185
+ it "doesnt add the `sent_timestamp`" do
186
+ expect(subject).not_to include(sent_timestamp_field)
187
+ end
188
+ end
189
+ end
190
+
191
+ context "when decoding body" do
192
+ subject { LogStash::Inputs::SQS::new(config.merge({ "codec" => "json" })) }
193
+
194
+ it "uses the specified codec" do
195
+ subject.handle_message(encoded_message, queue)
196
+ expect(queue.pop.get("bonjour")).to eq(decoded_message["bonjour"])
197
+ end
198
+ end
199
+
200
+ context "receiving messages" do
201
+ before do
202
+ expect(subject).to receive(:poller).and_return(mock_sqs).at_least(:once)
203
+ end
204
+
205
+ it "creates logstash event" do
206
+ expect(mock_sqs).to receive(:poll).with(anything()).and_yield([encoded_message], double("stats"))
207
+ subject.run(queue)
208
+ expect(queue.pop.get("bonjour")).to eq(decoded_message["bonjour"])
209
+ end
210
+
211
+ context 'can create multiple events' do
212
+ require "logstash/codecs/json_lines"
213
+ let(:config) { super().merge({ "codec" => "json_lines" }) }
214
+ let(:first_message) { { "sequence" => "first" } }
215
+ let(:second_message) { { "sequence" => "second" } }
216
+ let(:encoded_message) { double("sqs_message", :body => "#{LogStash::Json::dump(first_message)}\n#{LogStash::Json::dump(second_message)}\n") }
217
+
218
+ it 'creates multiple events' do
219
+ expect(mock_sqs).to receive(:poll).with(anything()).and_yield([encoded_message], double("stats"))
220
+ subject.run(queue)
221
+ events = queue.map{ |e|e.get('sequence')}
222
+ expect(events).to match_array([first_message['sequence'], second_message['sequence']])
223
+ end
224
+ end
225
+ end
226
+
227
+ context "on errors" do
228
+ let(:payload) { "Hello world" }
229
+
230
+ before do
231
+ expect(subject).to receive(:poller).and_return(mock_sqs).at_least(:once)
232
+ end
233
+
234
+ context "SQS error" do
235
+ it "retry to fetch messages" do
236
+ # change the poller implementation to raise SQS errors.
237
+ had_error = false
238
+
239
+ # actually using the child of `Object` to do an expectation of `#sleep`
240
+ expect(subject).to receive(:sleep).with(LogStash::Inputs::SQS::BACKOFF_SLEEP_TIME)
241
+ expect(mock_sqs).to receive(:poll).with(anything()).at_most(2) do
242
+ unless had_error
243
+ had_error = true
244
+ raise Aws::SQS::Errors::ServiceError.new("testing", "testing exception")
245
+ end
246
+
247
+ queue << payload
248
+ end
249
+
250
+ subject.run(queue)
251
+
252
+ expect(queue.size).to eq(1)
253
+ expect(queue.pop).to eq(payload)
254
+ end
255
+ end
256
+
257
+ context "SQS error (retries)" do
258
+
259
+ it "retry to fetch messages" do
260
+ sleep_time = LogStash::Inputs::SQS::BACKOFF_SLEEP_TIME
261
+ expect(subject).to receive(:sleep).with(sleep_time)
262
+ expect(subject).to receive(:sleep).with(sleep_time * 2)
263
+ expect(subject).to receive(:sleep).with(sleep_time * 4)
264
+
265
+ error_count = 0
266
+ expect(mock_sqs).to receive(:poll).with(anything()).at_most(4) do
267
+ error_count += 1
268
+ if error_count <= 3
269
+ raise Aws::SQS::Errors::QueueDoesNotExist.new("testing", "testing exception (#{error_count})")
270
+ end
271
+
272
+ queue << payload
273
+ end
274
+
275
+ subject.run(queue)
276
+
277
+ expect(queue.size).to eq(1)
278
+ expect(queue.pop).to eq(payload)
279
+ end
280
+
281
+ end
282
+
283
+ context "networking error" do
284
+
285
+ before(:all) { require 'seahorse/client/networking_error' }
286
+
287
+ it "retry to fetch messages" do
288
+ sleep_time = LogStash::Inputs::SQS::BACKOFF_SLEEP_TIME
289
+ expect(subject).to receive(:sleep).with(sleep_time).twice
290
+
291
+ error_count = 0
292
+ expect(mock_sqs).to receive(:poll).with(anything()).at_most(5) do
293
+ error_count += 1
294
+ if error_count == 1
295
+ raise Seahorse::Client::NetworkingError.new(Net::OpenTimeout.new, 'timeout')
296
+ end
297
+ if error_count == 3
298
+ raise Seahorse::Client::NetworkingError.new(SocketError.new('spec-error'))
299
+ end
300
+
301
+ queue << payload
302
+ end
303
+
304
+ subject.run(queue)
305
+ subject.run(queue)
306
+
307
+ expect(queue.size).to eq(2)
308
+ expect(queue.pop).to eq(payload)
309
+ end
310
+
311
+ end
312
+
313
+ context "other error" do
314
+ it "stops executing the code and raise the exception" do
315
+ expect(mock_sqs).to receive(:poll).with(anything()).at_most(2) do
316
+ raise RuntimeError
317
+ end
318
+
319
+ expect { subject.run(queue) }.to raise_error(RuntimeError)
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,25 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/inputs/cloudwatch"
3
+
4
+ describe LogStash::Inputs::CloudWatch, :integration => true do
5
+
6
+ let(:settings) { { "access_key_id" => ENV['AWS_ACCESS_KEY_ID'],
7
+ "secret_access_key" => LogStash::Util::Password.new(ENV['AWS_SECRET_ACCESS_KEY']),
8
+ "region" => ENV["AWS_REGION"] || "us-east-1",
9
+ "namespace" => "AWS/S3",
10
+ 'filters' => { "BucketName" => "*"},
11
+ 'metrics' => ["BucketSizeBytes","NumberOfObjects"]
12
+
13
+ }}
14
+
15
+ def metrics_for(settings)
16
+ cw = LogStash::Inputs::CloudWatch.new(settings)
17
+ cw.register
18
+ cw.send('metrics_for', settings['namespace'])
19
+ end
20
+
21
+ #
22
+ it "should not raise a type error when using a password" do
23
+ expect{metrics_for(settings)}.not_to raise_error
24
+ end
25
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ require_relative "../spec_helper"
3
+ require "logstash/outputs/s3"
4
+ require "logstash/codecs/line"
5
+ require "stud/temporary"
6
+
7
+ describe "Dynamic Prefix", :integration => true do
8
+ include_context "setup plugin"
9
+
10
+ let(:options) { main_options.merge({ "rotation_strategy" => "size" }) }
11
+ let(:sandbox) { "test" }
12
+
13
+ before do
14
+ clean_remote_files(sandbox)
15
+ subject.register
16
+ subject.multi_receive_encoded(batch)
17
+ subject.close
18
+ end
19
+
20
+ context "With field string" do
21
+ let(:prefix) { "/#{sandbox}/%{server}/%{language}" }
22
+ let(:batch) do
23
+ b = {}
24
+ e1 = LogStash::Event.new({ "server" => "es1", "language" => "ruby"})
25
+ b[e1] = "es1-ruby"
26
+ e2 = LogStash::Event.new({ "server" => "es2", "language" => "java"})
27
+ b[e2] = "es2-ruby"
28
+ b
29
+ end
30
+
31
+ it "creates a specific quantity of files" do
32
+ expect(bucket_resource.objects(:prefix => sandbox).count).to eq(batch.size)
33
+ end
34
+
35
+ it "creates specific keys" do
36
+ re = Regexp.union(/^es1\/ruby\/ls.s3.sashimi/, /^es2\/java\/ls.s3.sashimi/)
37
+
38
+ bucket_resource.objects(:prefix => sandbox) do |obj|
39
+ expect(obj.key).to match(re)
40
+ end
41
+ end
42
+
43
+ it "Persists all events" do
44
+ download_directory = Stud::Temporary.pathname
45
+
46
+ FileUtils.rm_rf(download_directory)
47
+ FileUtils.mkdir_p(download_directory)
48
+
49
+ counter = 0
50
+ bucket_resource.objects(:prefix => sandbox).each do |object|
51
+ target = File.join(download_directory, "#{counter}.txt")
52
+ object.get(:response_target => target)
53
+ counter += 1
54
+ end
55
+ expect(Dir.glob(File.join(download_directory, "**", "*.txt")).inject(0) { |sum, f| sum + IO.readlines(f).size }).to eq(batch.size)
56
+ end
57
+ end
58
+
59
+ context "with unsupported char" do
60
+ let(:prefix) { "/#{sandbox}/%{server}/%{language}" }
61
+ let(:batch) do
62
+ b = {}
63
+ e1 = LogStash::Event.new({ "server" => "e>s1", "language" => "ruby"})
64
+ b[e1] = "es2-ruby"
65
+ b
66
+ end
67
+
68
+ it "convert them to underscore" do
69
+ re = Regexp.union(/^e_s1\/ruby\/ls.s3.sashimi/)
70
+
71
+ bucket_resource.objects(:prefix => sandbox) do |obj|
72
+ expect(obj.key).to match(re)
73
+ end
74
+ end
75
+ end
76
+
77
+ context "with dates" do
78
+ let(:prefix) { "/#{sandbox}/%{+YYYY-MM-d}" }
79
+
80
+ let(:batch) do
81
+ b = {}
82
+ e1 = LogStash::Event.new({ "server" => "e>s1", "language" => "ruby"})
83
+ b[e1] = "es2-ruby"
84
+ b
85
+ end
86
+
87
+ it "creates dated path" do
88
+ re = /^#{sandbox}\/\d{4}-\d{2}-\d{1,2}\/ls\.s3\./
89
+ expect(bucket_resource.objects(:prefix => sandbox).first.key).to match(re)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ require_relative "../spec_helper"
3
+ require "logstash/outputs/s3"
4
+ require "logstash/codecs/line"
5
+ require "stud/temporary"
6
+
7
+ describe "Gzip File Time rotation with constant write", :integration => true do
8
+ include_context "setup plugin"
9
+
10
+ let(:time_file) { 0.004 }
11
+ let(:options) { main_options.merge({ "encoding" => "gzip",
12
+ "rotation_strategy" => "time" }) }
13
+ let(:number_of_events) { 5000 }
14
+ let(:batch_size) { 125 }
15
+ let(:event_encoded) { "Hello world" }
16
+ let(:batch) do
17
+ b = {}
18
+ number_of_events.times do
19
+ event = LogStash::Event.new({ "message" => event_encoded })
20
+ b[event] = "#{event_encoded}\n"
21
+ end
22
+ b
23
+ end
24
+ let(:minimum_number_of_time_rotation) { 3 }
25
+ let(:batch_step) { (number_of_events / minimum_number_of_time_rotation).ceil }
26
+
27
+ before do
28
+ clean_remote_files(prefix)
29
+ subject.register
30
+
31
+ # simulate batch read/write
32
+ batch.each_slice(batch_step) do |batch_time|
33
+ batch_time.each_slice(batch_size) do |smaller_batch|
34
+ subject.multi_receive_encoded(smaller_batch)
35
+ end
36
+ sleep(1)
37
+ end
38
+
39
+ subject.close
40
+ end
41
+
42
+ it "creates multiples files" do
43
+ # using close will upload the current file
44
+ expect(bucket_resource.objects(:prefix => prefix).count).to be_between(minimum_number_of_time_rotation, minimum_number_of_time_rotation + 1).inclusive
45
+ end
46
+
47
+ it "Persists all events" do
48
+ download_directory = Stud::Temporary.pathname
49
+
50
+ FileUtils.rm_rf(download_directory)
51
+ FileUtils.mkdir_p(download_directory)
52
+
53
+ counter = 0
54
+ bucket_resource.objects(:prefix => prefix).each do |object|
55
+ target = File.join(download_directory, "#{counter}.gz")
56
+ object.get(:response_target => target)
57
+ counter += 1
58
+ end
59
+
60
+ expect(Dir.glob(File.join(download_directory, "**", "*.gz")).inject(0) { |sum, f| sum + Zlib::GzipReader.new(File.open(f)).readlines.size }).to eq(number_of_events)
61
+ end
62
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ require_relative "../spec_helper"
3
+ require "logstash/outputs/s3"
4
+ require "logstash/codecs/line"
5
+ require "stud/temporary"
6
+
7
+ describe "Gzip Size rotation", :integration => true do
8
+ include_context "setup plugin"
9
+
10
+ let(:document_size) { 20 * 1024 } # in bytes
11
+
12
+ let(:options) do
13
+ main_options.merge({
14
+ "encoding" => "gzip",
15
+ "size_file" => document_size,
16
+ "rotation_strategy" => "size" })
17
+ end
18
+
19
+ let(:number_of_events) { 1_000_000 }
20
+ let(:batch_size) { 125 }
21
+ let(:event_encoded) { "Hello world" * 20 }
22
+ let(:batch) do
23
+ b = {}
24
+ batch_size.times do
25
+ event = LogStash::Event.new({ "message" => event_encoded })
26
+ b[event] = "#{event_encoded}\n"
27
+ end
28
+ b
29
+ end
30
+ let(:number_of_files) { number_of_events / 50000 }
31
+
32
+ before do
33
+ clean_remote_files(prefix)
34
+ subject.register
35
+ (number_of_events/batch_size).times do
36
+ subject.multi_receive_encoded(batch)
37
+ end
38
+ subject.close
39
+ end
40
+
41
+ it "Rotates the files based on size" do
42
+ f = bucket_resource.objects(:prefix => prefix).first
43
+ expect(f.size).to be_between(document_size, document_size * 2).inclusive
44
+ end
45
+
46
+ it "Persists all events" do
47
+ download_directory = Stud::Temporary.pathname
48
+
49
+ FileUtils.rm_rf(download_directory)
50
+ FileUtils.mkdir_p(download_directory)
51
+
52
+ counter = 0
53
+ bucket_resource.objects(:prefix => prefix).each do |object|
54
+ target = File.join(download_directory, "#{counter}.txt.gz")
55
+ object.get(:response_target => target)
56
+ counter += 1
57
+ end
58
+
59
+ expect(Dir.glob(File.join(download_directory, "**", "*.gz")).inject(0) do |sum, f|
60
+ sum + Zlib::GzipReader.new(File.open(f)).readlines.size
61
+ end).to eq(number_of_events)
62
+ end
63
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../../spec_helper'
4
+ require 'logstash/event'
5
+ require 'logstash/json'
6
+ require 'securerandom'
7
+
8
+ describe LogStash::Outputs::SQS, :integration => true do
9
+ let(:config) do
10
+ {
11
+ 'queue' => @queue_name,
12
+ }
13
+ end
14
+ subject { described_class.new(config) }
15
+
16
+ # Create an SQS queue with a random name.
17
+ before(:all) do
18
+ @sqs = Aws::SQS::Client.new
19
+ @queue_name = "logstash-output-sqs-#{SecureRandom.hex}"
20
+ @queue_url = @sqs.create_queue(:queue_name => @queue_name)[:queue_url]
21
+ end
22
+
23
+ # Destroy the SQS queue which was created in `before(:all)`.
24
+ after(:all) do
25
+ @sqs.delete_queue(:queue_url => @queue_url)
26
+ end
27
+
28
+ describe '#register' do
29
+ context 'with invalid credentials' do
30
+ let(:config) do
31
+ super().merge({
32
+ 'access_key_id' => 'bad_access',
33
+ 'secret_access_key' => 'bad_secret_key',
34
+ })
35
+ end
36
+
37
+ it 'raises a configuration error' do
38
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
39
+ end
40
+ end
41
+
42
+ context 'with a nonexistent queue' do
43
+ let(:config) { super().merge('queue' => 'nonexistent-queue') }
44
+
45
+ it 'raises a configuration error' do
46
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
47
+ end
48
+ end
49
+
50
+ context 'with a nonpermissive account id' do
51
+ let(:config) { super().merge('queue_owner_aws_account_id' => '123456789012')}
52
+
53
+ it 'raises a configuration error' do
54
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
55
+ end
56
+ end
57
+
58
+ context 'with valid configuration' do
59
+ it 'does not raise an error' do
60
+ expect { subject.register }.not_to raise_error
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#multi_receive_encoded' do
66
+ let(:sample_count) { 10 }
67
+ let(:sample_event) { LogStash::Event.new('message' => 'This is a message') }
68
+ let(:sample_events) do
69
+ sample_count.times.map do
70
+ [sample_event, LogStash::Json.dump(sample_event)]
71
+ end
72
+ end
73
+
74
+ before do
75
+ subject.register
76
+ end
77
+
78
+ after do
79
+ subject.close
80
+ end
81
+
82
+ context 'with batching disabled' do
83
+ let(:config) { super().merge('batch_events' => 1) }
84
+
85
+ it 'publishes to SQS' do
86
+ subject.multi_receive_encoded(sample_events)
87
+ expect(receive_all_messages(@queue_url).count).to eq(sample_events.count)
88
+ end
89
+ end
90
+
91
+ context 'with batching enabled (default)' do
92
+ it 'publishes to SQS' do
93
+ subject.multi_receive_encoded(sample_events)
94
+ expect(receive_all_messages(@queue_url).count).to eq(sample_events.count)
95
+ end
96
+ end
97
+ end
98
+ end