logstash-input-http_poller 5.0.1 → 5.2.1

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.
@@ -1,9 +1,11 @@
1
1
  require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/devutils/rspec/shared_examples"
2
3
  require 'logstash/inputs/http_poller'
3
4
  require 'flores/random'
4
5
  require "timecop"
5
6
  # Workaround for the bug reported in https://github.com/jruby/jruby/issues/4637
6
7
  require 'rspec/matchers/built_in/raise_error.rb'
8
+ require 'logstash/plugin_mixins/ecs_compatibility_support/spec_helper'
7
9
 
8
10
  describe LogStash::Inputs::HTTP_Poller do
9
11
  let(:metadata_target) { "_http_poller_metadata" }
@@ -23,17 +25,19 @@ describe LogStash::Inputs::HTTP_Poller do
23
25
  "schedule" => default_schedule,
24
26
  "urls" => default_urls,
25
27
  "codec" => "json",
26
- "metadata_target" => metadata_target
28
+ "metadata_target" => metadata_target,
29
+ "pool_max" => 3, "pool_max_per_route" => 1, 'keepalive' => false
27
30
  }
28
31
  }
29
- let(:klass) { LogStash::Inputs::HTTP_Poller }
32
+ let(:opts) { default_opts }
33
+
34
+ subject(:plugin) { described_class.new(opts) }
30
35
 
31
36
  describe "instances" do
32
- subject { klass.new(default_opts) }
37
+ subject { described_class.new(default_opts) }
33
38
 
34
- before do
35
- subject.register
36
- end
39
+ before { subject.register }
40
+ after { subject.stop }
37
41
 
38
42
  describe "#run" do
39
43
  it "should setup a scheduler" do
@@ -132,10 +136,10 @@ describe LogStash::Inputs::HTTP_Poller do
132
136
 
133
137
  it "should properly set the auth parameter" do
134
138
  expect(normalized[2][:auth]).to eq({
135
- :user => auth["user"],
136
- :pass => auth["password"],
137
- :eager => true
138
- })
139
+ :user => auth["user"],
140
+ :pass => auth["password"],
141
+ :eager => true
142
+ })
139
143
  end
140
144
  end
141
145
  end
@@ -143,14 +147,14 @@ describe LogStash::Inputs::HTTP_Poller do
143
147
  # Legacy way of doing things, kept for backwards compat.
144
148
  describe "auth with nested auth hash" do
145
149
  let(:url) { {"url" => "http://localhost", "method" => "get", "auth" => auth} }
146
-
150
+
147
151
  include_examples("auth")
148
152
  end
149
153
 
150
154
  # The new 'right' way to do things
151
155
  describe "auth with direct auth options" do
152
156
  let(:url) { {"url" => "http://localhost", "method" => "get", "user" => auth["user"], "password" => auth["password"]} }
153
-
157
+
154
158
  include_examples("auth")
155
159
  end
156
160
  end
@@ -186,22 +190,21 @@ describe LogStash::Inputs::HTTP_Poller do
186
190
  "metadata_target" => metadata_target
187
191
  }
188
192
  }
189
- it "should run at the schedule" do
190
- instance = klass.new(opts)
191
- instance.register
193
+
194
+ before do
192
195
  Timecop.travel(Time.new(2000,1,1,0,0,0,'+00:00'))
193
196
  Timecop.scale(60)
194
- queue = Queue.new
195
- runner = Thread.new do
196
- instance.run(queue)
197
- end
198
- sleep 3
199
- instance.stop
200
- runner.kill
201
- runner.join
202
- expect(queue.size).to eq(2)
197
+ end
198
+
199
+ after do
203
200
  Timecop.return
204
201
  end
202
+
203
+ it "should run at the schedule" do
204
+ run_plugin_and_yield_queue(plugin, sleep: 3) do |queue|
205
+ try(5) { expect(queue.size).to be >= 2 }
206
+ end
207
+ end
205
208
  end
206
209
 
207
210
  context "given 'at' expression" do
@@ -213,22 +216,21 @@ describe LogStash::Inputs::HTTP_Poller do
213
216
  "metadata_target" => metadata_target
214
217
  }
215
218
  }
216
- it "should run at the schedule" do
217
- instance = klass.new(opts)
218
- instance.register
219
+
220
+ before do
219
221
  Timecop.travel(Time.new(2000,1,1,0,0,0,'+00:00'))
220
- Timecop.scale(60 * 5)
221
- queue = Queue.new
222
- runner = Thread.new do
223
- instance.run(queue)
224
- end
225
- sleep 2
226
- instance.stop
227
- runner.kill
228
- runner.join
229
- expect(queue.size).to eq(1)
222
+ Timecop.scale (60 * 5) / 2
223
+ end
224
+
225
+ after do
230
226
  Timecop.return
231
227
  end
228
+
229
+ it "should run at the schedule" do
230
+ run_plugin_and_yield_queue(plugin, sleep: 2) do |queue|
231
+ try(5) { expect(queue.size).to eq(1) }
232
+ end
233
+ end
232
234
  end
233
235
 
234
236
  context "given 'every' expression" do
@@ -241,20 +243,12 @@ describe LogStash::Inputs::HTTP_Poller do
241
243
  }
242
244
  }
243
245
  it "should run at the schedule" do
244
- instance = klass.new(opts)
245
- instance.register
246
- queue = Queue.new
247
- runner = Thread.new do
248
- instance.run(queue)
246
+ run_plugin_and_yield_queue(plugin, sleep: 5) do |queue|
247
+ #T 0123456
248
+ #events x x x x
249
+ #expects 3 events at T=5
250
+ try(5) { expect(queue.size).to be_between(2, 3) }
249
251
  end
250
- #T 0123456
251
- #events x x x x
252
- #expects 3 events at T=5
253
- sleep 5
254
- instance.stop
255
- runner.kill
256
- runner.join
257
- expect(queue.size).to eq(3)
258
252
  end
259
253
  end
260
254
 
@@ -268,231 +262,288 @@ describe LogStash::Inputs::HTTP_Poller do
268
262
  }
269
263
  }
270
264
  it "should run at the schedule" do
271
- instance = klass.new(opts)
272
- instance.register
273
- queue = Queue.new
274
- runner = Thread.new do
275
- instance.run(queue)
265
+ run_plugin_and_yield_queue(plugin, sleep: 2.5) do |queue|
266
+ try(5) { expect(queue.size).to eq(1) }
276
267
  end
277
- sleep 3
278
- instance.stop
279
- runner.kill
280
- runner.join
281
- expect(queue.size).to eq(1)
282
268
  end
283
269
  end
284
270
  end
285
271
 
286
- describe "events" do
287
- shared_examples("matching metadata") {
288
- let(:metadata) { event.get(metadata_target) }
272
+ def run_plugin_and_yield_queue(plugin, sleep: nil)
273
+ plugin.register
274
+ queue = Queue.new
275
+ begin
276
+ runner = Thread.new do
277
+ plugin.run(queue)
278
+ end
279
+ sleep(sleep) if sleep
280
+ yield(queue)
281
+ ensure
282
+ plugin.stop
283
+ runner.join if runner
284
+ end
285
+ end
289
286
 
290
- it "should have the correct name" do
291
- expect(metadata["name"]).to eql(name)
287
+ describe "events", :ecs_compatibility_support, :aggregate_failures do
288
+ ecs_compatibility_matrix(:disabled, :v1, :v8 => :v1) do |ecs_select|
289
+ before do
290
+ allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
292
291
  end
293
292
 
294
- it "should have the correct request url" do
295
- if url.is_a?(Hash) # If the url was specified as a complex test the whole thing
296
- expect(metadata["request"]).to eql(url)
297
- else # Otherwise we have to make some assumptions
298
- expect(metadata["request"]["url"]).to eql(url)
293
+ shared_examples("matching metadata") {
294
+ let(:metadata) { event.get(metadata_target) }
295
+
296
+ it "should have the correct name" do
297
+ field = ecs_select[disabled: "[#{metadata_target}][name]", v1: "[#{metadata_target}][input][http_poller][request][name]"]
298
+ expect(event.get(field)).to eql(name)
299
299
  end
300
- end
301
300
 
302
- it "should have the correct code" do
303
- expect(metadata["code"]).to eql(code)
304
- end
305
- }
301
+ it "should have the correct request url" do
302
+ if url.is_a?(Hash) # If the url was specified as a complex test the whole thing
303
+ http_client_field = ecs_select[disabled: "[#{metadata_target}][request]",
304
+ v1: "[#{metadata_target}][input][http_poller][request][original]"]
305
+ expect(event.get(http_client_field)).to eql(url)
306
+ else # Otherwise we have to make some assumptions
307
+ url_field = ecs_select[disabled: "[#{metadata_target}][request][url]",
308
+ v1: "[#{metadata_target}][input][http_poller][request][original][url]"]
309
+ expect(event.get(url_field)).to eql(url)
310
+ end
311
+ end
306
312
 
307
- shared_examples "unprocessable_requests" do
308
- let(:poller) { LogStash::Inputs::HTTP_Poller.new(settings) }
309
- subject(:event) {
310
- poller.send(:run_once, queue)
311
- queue.pop(true)
312
- }
313
+ it "should have the correct code" do
314
+ expect(event.get(ecs_select[disabled: "[#{metadata_target}][code]",
315
+ v1: "[#{metadata_target}][input][http_poller][response][status_code]"]))
316
+ .to eql(code)
317
+ end
313
318
 
314
- before do
315
- poller.register
316
- allow(poller).to receive(:handle_failure).and_call_original
317
- allow(poller).to receive(:handle_success)
318
- event # materialize the subject
319
- end
319
+ it "should have the correct host" do
320
+ expect(event.get(ecs_select[disabled: "[#{metadata_target}][host]",
321
+ v1: "[#{metadata_target}][input][http_poller][request][host][hostname]"]))
322
+ .not_to be_nil
323
+ end
324
+ }
320
325
 
321
- it "should enqueue a message" do
322
- expect(event).to be_a(LogStash::Event)
323
- end
326
+ shared_examples "unprocessable_requests" do
327
+ let(:poller) { LogStash::Inputs::HTTP_Poller.new(settings) }
328
+ subject(:event) {
329
+ poller.send(:run_once, queue)
330
+ queue.pop(true)
331
+ }
324
332
 
325
- it "should enqueue a message with 'http_request_failure' set" do
326
- expect(event.get("http_request_failure")).to be_a(Hash)
327
- end
333
+ before do
334
+ poller.register
335
+ allow(poller).to receive(:handle_failure).and_call_original
336
+ allow(poller).to receive(:handle_success)
337
+ event # materialize the subject
338
+ end
328
339
 
329
- it "should tag the event with '_http_request_failure'" do
330
- expect(event.get("tags")).to include('_http_request_failure')
331
- end
340
+ after { poller.stop }
332
341
 
333
- it "should invoke handle failure exactly once" do
334
- expect(poller).to have_received(:handle_failure).once
335
- end
342
+ it "should enqueue a message" do
343
+ expect(event).to be_a(LogStash::Event)
344
+ end
336
345
 
337
- it "should not invoke handle success at all" do
338
- expect(poller).not_to have_received(:handle_success)
339
- end
346
+ it "should enqueue a message with 'http_request_failure' set" do
347
+ if ecs_compatibility == :disabled
348
+ expect(event.get("http_request_failure")).to be_a(Hash)
349
+ expect(event.get("[http_request_failure][runtime_seconds]")).to be_a(Float)
350
+ else
351
+ expect(event.get("http_request_failure")).to be_nil
352
+ expect(event.get("error")).to be_a(Hash)
353
+ expect(event.get("[event][duration]")).to be_a(Integer)
354
+ expect(event.get("[url][full]")).to eq(url)
355
+ expect(event.get("[http][request][method]")).to be_a(String)
356
+ expect(event.get("[host][hostname]")).to be_a(String)
357
+ end
358
+ end
340
359
 
341
- include_examples("matching metadata")
342
- end
360
+ it "should tag the event with '_http_request_failure'" do
361
+ expect(event.get("tags")).to include('_http_request_failure')
362
+ end
343
363
 
344
- context "with a non responsive server" do
345
- context "due to a non-existant host" do # Fail with handlers
346
- let(:name) { default_name }
347
- let(:url) { "http://thouetnhoeu89ueoueohtueohtneuohn" }
348
- let(:code) { nil } # no response expected
364
+ it "should invoke handle failure exactly once" do
365
+ expect(poller).to have_received(:handle_failure).once
366
+ end
349
367
 
350
- let(:settings) { default_opts.merge("urls" => { name => url}) }
368
+ it "should not invoke handle success at all" do
369
+ expect(poller).not_to have_received(:handle_success)
370
+ end
351
371
 
352
- include_examples("unprocessable_requests")
372
+ include_examples("matching metadata")
353
373
  end
354
374
 
355
- context "due to a bogus port number" do # fail with return?
356
- let(:invalid_port) { Flores::Random.integer(65536..1000000) }
375
+ context "with a non responsive server" do
376
+ context "due to a non-existant host" do # Fail with handlers
377
+ let(:name) { default_name }
378
+ let(:url) { "http://thouetnhoeu89ueoueohtueohtneuohn" }
379
+ let(:code) { nil } # no response expected
357
380
 
358
- let(:name) { default_name }
359
- let(:url) { "http://127.0.0.1:#{invalid_port}" }
360
- let(:settings) { default_opts.merge("urls" => {name => url}) }
361
- let(:code) { nil } # No response expected
381
+ let(:settings) { default_opts.merge("urls" => { name => url}) }
362
382
 
363
- include_examples("unprocessable_requests")
364
- end
365
- end
383
+ include_examples("unprocessable_requests")
384
+ end
366
385
 
367
- describe "a valid request and decoded response" do
368
- let(:payload) { {"a" => 2, "hello" => ["a", "b", "c"]} }
369
- let(:response_body) { LogStash::Json.dump(payload) }
370
- let(:opts) { default_opts }
371
- let(:instance) {
372
- klass.new(opts)
373
- }
374
- let(:name) { default_name }
375
- let(:url) { default_url }
376
- let(:code) { 202 }
386
+ context "due to a bogus port number" do # fail with return?
387
+ let(:invalid_port) { Flores::Random.integer(65536..1000000) }
377
388
 
378
- subject(:event) {
379
- queue.pop(true)
380
- }
389
+ let(:name) { default_name }
390
+ let(:url) { "http://127.0.0.1:#{invalid_port}" }
391
+ let(:settings) { default_opts.merge("urls" => {name => url}) }
392
+ let(:code) { nil } # No response expected
381
393
 
382
- before do
383
- instance.register
384
- u = url.is_a?(Hash) ? url["url"] : url # handle both complex specs and simple string URLs
385
- instance.client.stub(u,
386
- :body => response_body,
387
- :code => code
388
- )
389
- allow(instance).to receive(:decorate)
390
- instance.send(:run_once, queue)
394
+ include_examples("unprocessable_requests")
395
+ end
391
396
  end
392
397
 
393
- it "should have a matching message" do
394
- expect(event.to_hash).to include(payload)
395
- end
398
+ describe "a valid request and decoded response" do
399
+ let(:payload) { {"a" => 2, "hello" => ["a", "b", "c"]} }
400
+ let(:response_body) { LogStash::Json.dump(payload) }
401
+ let(:opts) { default_opts }
402
+ let(:name) { default_name }
403
+ let(:url) { default_url }
404
+ let(:code) { 202 }
396
405
 
397
- it "should decorate the event" do
398
- expect(instance).to have_received(:decorate).once
399
- end
406
+ subject(:event) {
407
+ queue.pop(true)
408
+ }
400
409
 
401
- include_examples("matching metadata")
402
-
403
- context "with an empty body" do
404
- let(:response_body) { "" }
405
- it "should return an empty event" do
406
- instance.send(:run_once, queue)
407
- expect(event.get("[_http_poller_metadata][response_headers][content-length]")).to eql("0")
410
+ before do
411
+ plugin.register
412
+ u = url.is_a?(Hash) ? url["url"] : url # handle both complex specs and simple string URLs
413
+ plugin.client.stub(u,
414
+ :body => response_body,
415
+ :code => code
416
+ )
417
+ allow(plugin).to receive(:decorate)
418
+ plugin.send(:run_once, queue)
408
419
  end
409
- end
410
420
 
411
- context "with metadata omitted" do
412
- let(:opts) {
413
- opts = default_opts.clone
414
- opts.delete("metadata_target")
415
- opts
416
- }
421
+ it "should have a matching message" do
422
+ expect(event.to_hash).to include(payload)
423
+ end
417
424
 
418
- it "should not have any metadata on the event" do
419
- instance.send(:run_once, queue)
420
- expect(event.get(metadata_target)).to be_nil
425
+ it "should decorate the event" do
426
+ expect(plugin).to have_received(:decorate).once
421
427
  end
422
- end
423
428
 
424
- context "with a complex URL spec" do
425
- let(:url) {
426
- {
427
- "method" => "get",
428
- "url" => default_url,
429
- "headers" => {
430
- "X-Fry" => "I'm having one of those things, like a headache, with pictures..."
429
+ include_examples("matching metadata")
430
+
431
+ context "with an empty body" do
432
+ let(:response_body) { "" }
433
+ it "should return an empty event" do
434
+ plugin.send(:run_once, queue)
435
+ headers_field = ecs_select[disabled: "[#{metadata_target}][response_headers]",
436
+ v1: "[#{metadata_target}][input][http_poller][response][headers]"]
437
+ expect(event.get("#{headers_field}[content-length]")).to eql("0")
438
+ end
439
+ end
440
+
441
+ context "with metadata omitted" do
442
+ let(:opts) {
443
+ opts = default_opts.clone
444
+ opts.delete("metadata_target")
445
+ opts
446
+ }
447
+
448
+ it "should not have any metadata on the event" do
449
+ plugin.send(:run_once, queue)
450
+ expect(event.get(metadata_target)).to be_nil
451
+ end
452
+ end
453
+
454
+ context "with a complex URL spec" do
455
+ let(:url) {
456
+ {
457
+ "method" => "get",
458
+ "url" => default_url,
459
+ "headers" => {
460
+ "X-Fry" => "I'm having one of those things, like a headache, with pictures..."
461
+ }
431
462
  }
432
463
  }
433
- }
434
- let(:opts) {
435
- {
436
- "schedule" => {
437
- "cron" => "* * * * * UTC"
464
+ let(:opts) {
465
+ {
466
+ "schedule" => {
467
+ "cron" => "* * * * * UTC"
468
+ },
469
+ "urls" => {
470
+ default_name => url
438
471
  },
439
- "urls" => {
440
- default_name => url
441
- },
442
- "codec" => "json",
443
- "metadata_target" => metadata_target
472
+ "codec" => "json",
473
+ "metadata_target" => metadata_target
474
+ }
444
475
  }
445
- }
446
476
 
447
- include_examples("matching metadata")
477
+ include_examples("matching metadata")
448
478
 
449
- it "should have a matching message" do
450
- expect(event.to_hash).to include(payload)
479
+ it "should have a matching message" do
480
+ expect(event.to_hash).to include(payload)
481
+ end
451
482
  end
452
- end
453
483
 
454
- context "with a specified target" do
455
- let(:target) { "mytarget" }
456
- let(:opts) { default_opts.merge("target" => target) }
484
+ context "with a specified target" do
485
+ let(:target) { "mytarget" }
486
+ let(:opts) { default_opts.merge("target" => target) }
457
487
 
458
- it "should store the event info in the target" do
459
- # When events go through the pipeline they are java-ified
460
- # this normalizes the payload to java types
461
- payload_normalized = LogStash::Json.load(LogStash::Json.dump(payload))
462
- expect(event.get(target)).to include(payload_normalized)
488
+ it "should store the event info in the target" do
489
+ # When events go through the pipeline they are java-ified
490
+ # this normalizes the payload to java types
491
+ payload_normalized = LogStash::Json.load(LogStash::Json.dump(payload))
492
+ expect(event.get(target)).to include(payload_normalized)
493
+ end
463
494
  end
464
- end
465
495
 
466
- context 'using a line codec' do
467
- let(:opts) do
468
- default_opts.merge({"codec" => "line"})
469
- end
470
- subject(:events) do
471
- [].tap do |events|
472
- events << queue.pop until queue.empty?
496
+ context "with default metadata target" do
497
+ let(:metadata_target) { "@metadata" }
498
+
499
+ it "should store the metadata info in @metadata" do
500
+ if ecs_compatibility == :disabled
501
+ expect(event.get("[@metadata][response_headers]")).to be_a(Hash)
502
+ expect(event.get("[@metadata][runtime_seconds]")).to be_a(Float)
503
+ expect(event.get("[@metadata][times_retried]")).to eq(0)
504
+ expect(event.get("[@metadata][name]")).to eq(default_name)
505
+ expect(event.get("[@metadata][request]")).to be_a(Hash)
506
+ else
507
+ expect(event.get("[@metadata][input][http_poller][response][headers]")).to be_a(Hash)
508
+ expect(event.get("[@metadata][input][http_poller][response][elapsed_time_ns]")).to be_a(Integer)
509
+ expect(event.get("[@metadata][input][http_poller][request][retry_count]")).to eq(0)
510
+ expect(event.get("[@metadata][input][http_poller][request][name]")).to eq(default_name)
511
+ expect(event.get("[@metadata][input][http_poller][request][original]")).to be_a(Hash)
512
+ end
473
513
  end
474
514
  end
475
515
 
476
- context 'when response has a trailing newline' do
477
- let(:response_body) { "one\ntwo\nthree\nfour\n" }
478
- it 'emits all events' do
479
- expect(events.size).to equal(4)
480
- messages = events.map{|e| e.get('message')}
481
- expect(messages).to include('one')
482
- expect(messages).to include('two')
483
- expect(messages).to include('three')
484
- expect(messages).to include('four')
516
+ context 'using a line codec' do
517
+ let(:opts) do
518
+ default_opts.merge({"codec" => "line"})
485
519
  end
486
- end
487
- context 'when response has no trailing newline' do
488
- let(:response_body) { "one\ntwo\nthree\nfour" }
489
- it 'emits all events' do
490
- expect(events.size).to equal(4)
491
- messages = events.map{|e| e.get('message')}
492
- expect(messages).to include('one')
493
- expect(messages).to include('two')
494
- expect(messages).to include('three')
495
- expect(messages).to include('four')
520
+ subject(:events) do
521
+ [].tap do |events|
522
+ events << queue.pop until queue.empty?
523
+ end
524
+ end
525
+
526
+ context 'when response has a trailing newline' do
527
+ let(:response_body) { "one\ntwo\nthree\nfour\n" }
528
+ it 'emits all events' do
529
+ expect(events.size).to equal(4)
530
+ messages = events.map{|e| e.get('message')}
531
+ expect(messages).to include('one')
532
+ expect(messages).to include('two')
533
+ expect(messages).to include('three')
534
+ expect(messages).to include('four')
535
+ end
536
+ end
537
+ context 'when response has no trailing newline' do
538
+ let(:response_body) { "one\ntwo\nthree\nfour" }
539
+ it 'emits all events' do
540
+ expect(events.size).to equal(4)
541
+ messages = events.map{|e| e.get('message')}
542
+ expect(messages).to include('one')
543
+ expect(messages).to include('two')
544
+ expect(messages).to include('three')
545
+ expect(messages).to include('four')
546
+ end
496
547
  end
497
548
  end
498
549
  end
@@ -501,6 +552,8 @@ describe LogStash::Inputs::HTTP_Poller do
501
552
 
502
553
  describe "stopping" do
503
554
  let(:config) { default_opts }
504
- it_behaves_like "an interruptible input plugin"
555
+ it_behaves_like "an interruptible input plugin" do
556
+ let(:allowed_lag) { 10 } # CI: wait till scheduler shuts down
557
+ end
505
558
  end
506
559
  end