logstash-input-http_poller 5.0.1 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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