fluent-plugin-out-http 1.2.0 → 1.3.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.
@@ -1,42 +1,42 @@
1
- require 'rubygems'
2
- require 'bundler'
3
-
4
- begin
5
- Bundler.setup(:default, :development)
6
- rescue Bundler::BundlerError => e
7
- $stderr.puts e.message
8
- $stderr.puts "Run `bundle install` to install missing gems"
9
- exit e.status_code
10
- end
11
-
12
- require 'test/unit'
13
-
14
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
- $LOAD_PATH.unshift(File.dirname(__FILE__))
16
-
17
- require 'fluent/test'
18
-
19
- unless ENV.has_key?('VERBOSE')
20
- nulllogger = Object.new
21
- nulllogger.instance_eval {|obj|
22
- def method_missing(method, *args)
23
- # pass
24
- end
25
- }
26
- $log = nulllogger
27
- end
28
-
29
- class Test::Unit::TestCase
30
- end
31
-
32
- require 'webrick'
33
- require 'webrick/https'
34
-
35
- # to handle POST/PUT/DELETE ...
36
- module WEBrick::HTTPServlet
37
- class ProcHandler < AbstractServlet
38
- alias do_POST do_GET
39
- alias do_PUT do_GET
40
- alias do_DELETE do_GET
41
- end
42
- end
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'test/unit'
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+
17
+ require 'fluent/test'
18
+
19
+ unless ENV.has_key?('VERBOSE')
20
+ nulllogger = Object.new
21
+ nulllogger.instance_eval {|obj|
22
+ def method_missing(method, *args)
23
+ # pass
24
+ end
25
+ }
26
+ $log = nulllogger
27
+ end
28
+
29
+ class Test::Unit::TestCase
30
+ end
31
+
32
+ require 'webrick'
33
+ require 'webrick/https'
34
+
35
+ # to handle POST/PUT/DELETE ...
36
+ module WEBrick::HTTPServlet
37
+ class ProcHandler < AbstractServlet
38
+ alias do_POST do_GET
39
+ alias do_PUT do_GET
40
+ alias do_DELETE do_GET
41
+ end
42
+ end
@@ -1,21 +1,21 @@
1
- require 'fluent/plugin/formatter'
2
-
3
- module Fluent
4
- module Plugin
5
- class TestFormatter < Formatter
6
- Fluent::Plugin.register_formatter('test', self)
7
-
8
- def configure(conf)
9
- super
10
- end
11
-
12
- def format(tag, time, record)
13
- output = {
14
- "wrapped" => true,
15
- "record" => record
16
- }
17
- output
18
- end
19
- end
20
- end
21
- end
1
+ require 'fluent/plugin/formatter'
2
+
3
+ module Fluent
4
+ module Plugin
5
+ class TestFormatter < Formatter
6
+ Fluent::Plugin.register_formatter('test', self)
7
+
8
+ def configure(conf)
9
+ super
10
+ end
11
+
12
+ def format(tag, time, record)
13
+ output = {
14
+ "wrapped" => true,
15
+ "record" => record
16
+ }
17
+ output
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,732 +1,771 @@
1
- # -*- coding: utf-8 -*-
2
- require 'net/http'
3
- require 'uri'
4
- require 'yajl'
5
- require 'fluent/test/http_output_test'
6
- require 'fluent/plugin/out_http'
7
- require 'fluent/test/driver/output'
8
- require 'fluent/test/helpers'
9
- require_relative "./script/plugin/formatter_test"
10
-
11
- module OS
12
- # ref. http://stackoverflow.com/questions/170956/how-can-i-find-which-operating-system-my-ruby-program-is-running-on
13
- def OS.windows?
14
- (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
15
- end
16
-
17
- def OS.mac?
18
- (/darwin/ =~ RUBY_PLATFORM) != nil
19
- end
20
-
21
- def OS.unix?
22
- !OS.windows?
23
- end
24
-
25
- def OS.linux?
26
- OS.unix? and not OS.mac?
27
- end
28
- end
29
-
30
- class HTTPOutputTestBase < Test::Unit::TestCase
31
- include Fluent::Test::Helpers
32
-
33
- def self.port
34
- 5126
35
- end
36
-
37
- def self.server_config
38
- config = {BindAddress: '127.0.0.1', Port: port}
39
- if ENV['VERBOSE']
40
- logger = WEBrick::Log.new(STDOUT, WEBrick::BasicLog::DEBUG)
41
- config[:Logger] = logger
42
- config[:AccessLog] = []
43
- end
44
- config
45
- end
46
-
47
- def self.test_http_client(**opts)
48
- opts = opts.merge(open_timeout: 1, read_timeout: 1)
49
- Net::HTTP.start('127.0.0.1', port, **opts)
50
- end
51
-
52
- # setup / teardown for servers
53
- def setup
54
- Fluent::Test.setup
55
- @posts = []
56
- @puts = []
57
- @prohibited = 0
58
- @requests = 0
59
- @auth = false
60
- @headers = {}
61
- @dummy_server_thread = Thread.new do
62
- srv = WEBrick::HTTPServer.new(self.class.server_config)
63
- begin
64
- allowed_methods = %w(POST PUT)
65
- srv.mount_proc('/api') { |req,res|
66
- @requests += 1
67
- unless allowed_methods.include? req.request_method
68
- res.status = 405
69
- res.body = 'request method mismatch'
70
- next
71
- end
72
- req.each do |key, value|
73
- @headers[key] = value
74
- end
75
- if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
76
- # ok, authorized
77
- # pattern of bear #{Base64.encode64('secret token!')}
78
- elsif @auth and req.header['authorization'][0] == 'bearer c2VjcmV0IHRva2VuIQ=='
79
- # pattern of jwt
80
- # header: {
81
- # "alg": "HS256",
82
- # "typ": "JWT"
83
- # }
84
- # payload: {
85
- # "iss": "Hoge Publisher",
86
- # "sub": "Hoge User"
87
- # }
88
- # signature:
89
- # HS256(base64UrlEncode(header) + "." +
90
- # base64UrlEncode(payload) + "." +
91
- # secret)
92
- elsif @auth and req.header['authorization'][0] == 'jwt eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIb2dlIFB1Ymxpc2hlciIsInN1YiI6IkhvZ2UgVXNlciJ9.V2NL7YgCWNt5d3vTXFrcRLpRImO2cU2JZ4mQglqw3rE'
93
- elsif @auth
94
- res.status = 403
95
- @prohibited += 1
96
- next
97
- else
98
- # ok, authorization not required
99
- end
100
-
101
- record = {:auth => nil}
102
- if req.content_type == 'application/json'
103
- record[:json] = Yajl.load(req.body)
104
- elsif req.content_type == 'text/plain'
105
- puts req
106
- record[:data] = req.body
107
- elsif req.content_type == 'application/octet-stream'
108
- record[:data] = req.body
109
- else
110
- record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
111
- end
112
-
113
- instance_variable_get("@#{req.request_method.downcase}s").push(record)
114
-
115
- res.status = 200
116
- }
117
- srv.mount_proc('/modified-api') { |req,res|
118
- res.status = 303
119
- res.body = 'See other'
120
- }
121
- srv.mount_proc('/') { |req,res|
122
- res.status = 200
123
- res.body = 'running'
124
- }
125
- srv.start
126
- ensure
127
- srv.shutdown
128
- end
129
- end
130
-
131
- # to wait completion of dummy server.start()
132
- require 'thread'
133
- cv = ConditionVariable.new
134
- watcher = Thread.new {
135
- connected = false
136
- while not connected
137
- begin
138
- client = self.class.test_http_client
139
- client.request_get('/')
140
- connected = true
141
- rescue Errno::ECONNREFUSED
142
- sleep 0.1
143
- rescue StandardError => e
144
- p e
145
- sleep 0.1
146
- end
147
- end
148
- cv.signal
149
- }
150
- mutex = Mutex.new
151
- mutex.synchronize {
152
- cv.wait(mutex)
153
- }
154
- end
155
-
156
- def test_dummy_server
157
- client = self.class.test_http_client
158
- post_header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
159
-
160
- assert_equal '200', client.request_get('/').code
161
- assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge', post_header).code
162
-
163
- assert_equal 1, @posts.size
164
-
165
- assert_equal '1', @posts[0][:form]['number']
166
- assert_equal 'gauge', @posts[0][:form]['mode']
167
- assert_nil @posts[0][:auth]
168
-
169
- assert_equal '303', client.request_get('/modified-api').code
170
-
171
- @auth = true
172
-
173
- assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge', post_header).code
174
-
175
- req_with_auth = lambda do |number, mode, user, pass|
176
- req = Net::HTTP::Post.new("/api/service/metrics/pos")
177
- req.content_type = 'application/x-www-form-urlencoded'
178
- req.basic_auth user, pass
179
- req.set_form_data({'number'=>number, 'mode'=>mode})
180
- req
181
- end
182
-
183
- assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
184
-
185
- assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
186
-
187
- assert_equal 1, @posts.size
188
-
189
- assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
190
-
191
- assert_equal 2, @posts.size
192
-
193
- end
194
-
195
- def teardown
196
- @dummy_server_thread.kill
197
- @dummy_server_thread.join
198
- end
199
-
200
- def create_driver(conf)
201
- Fluent::Test::Driver::Output.new(Fluent::Plugin::HTTPOutput).configure(conf)
202
- end
203
- end
204
-
205
- class HTTPOutputTest < HTTPOutputTestBase
206
- CONFIG = %[
207
- endpoint_url http://127.0.0.1:#{port}/api/
208
- ]
209
-
210
- CONFIG_QUERY_PARAM = %[
211
- endpoint_url http://127.0.0.1:#{port}/api?foo=bar&baz=qux
212
- ]
213
-
214
- CONFIG_JSON = %[
215
- endpoint_url http://127.0.0.1:#{port}/api/
216
- serializer json
217
- ]
218
-
219
- CONFIG_TEXT = %[
220
- endpoint_url http://127.0.0.1:#{port}/api/
221
- serializer text
222
- ]
223
-
224
- CONFIG_RAW = %[
225
- endpoint_url http://127.0.0.1:#{port}/api/
226
- serializer raw
227
- ]
228
-
229
- CONFIG_PUT = %[
230
- endpoint_url http://127.0.0.1:#{port}/api/
231
- http_method put
232
- ]
233
-
234
- CONFIG_HTTP_ERROR = %[
235
- endpoint_url https://127.0.0.1:#{port - 1}/api/
236
- ]
237
-
238
- CONFIG_HTTP_ERROR_SUPPRESSED = %[
239
- endpoint_url https://127.0.0.1:#{port - 1}/api/
240
- raise_on_error false
241
- ]
242
-
243
- RATE_LIMIT_MSEC = 1200
244
-
245
- CONFIG_RATE_LIMIT = %[
246
- endpoint_url http://127.0.0.1:#{port}/api/
247
- rate_limit_msec #{RATE_LIMIT_MSEC}
248
- ]
249
-
250
- def test_configure
251
- d = create_driver CONFIG
252
- assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
253
- assert_equal :form, d.instance.serializer
254
-
255
- d = create_driver CONFIG_JSON
256
- assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
257
- assert_equal :json, d.instance.serializer
258
- end
259
-
260
- test 'lack of tag in chunk_keys' do
261
- assert_raise_message(/'tag' in chunk_keys is required./) do
262
- create_driver(Fluent::Config::Element.new(
263
- 'ROOT', '', {
264
- '@type' => 'http',
265
- 'endpoint_url' => "http://127.0.0.1:#{self.class.port}/api/",
266
- 'buffered' => true,
267
- }, [
268
- Fluent::Config::Element.new('buffer', 'mykey', {
269
- 'chunk_keys' => 'mykey'
270
- }, [])
271
- ]
272
- ))
273
- end
274
- end
275
-
276
- def test_emit_form
277
- d = create_driver CONFIG
278
- d.run(default_tag: 'test.metrics') do
279
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
280
- end
281
-
282
- assert_equal 1, @posts.size
283
- record = @posts[0]
284
-
285
- assert_equal '50', record[:form]['field1']
286
- assert_equal '20', record[:form]['field2']
287
- assert_equal '10', record[:form]['field3']
288
- assert_equal '1', record[:form]['otherfield']
289
- assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
290
- assert_nil record[:auth]
291
-
292
- d.run(default_tag: 'test.metrics') do
293
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
294
- end
295
-
296
- assert_equal 2, @posts.size
297
- end
298
-
299
- def test_emit_form_with_query_params
300
- d = create_driver CONFIG_QUERY_PARAM
301
- d.run(default_tag: 'test.metrics') do
302
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
303
- end
304
-
305
- assert_equal 1, @posts.size
306
- record = @posts[0]
307
-
308
- assert_equal '50', record[:form]['field1']
309
- assert_equal '20', record[:form]['field2']
310
- assert_equal '10', record[:form]['field3']
311
- assert_equal '1', record[:form]['otherfield']
312
- assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
313
- assert_nil record[:auth]
314
-
315
- d.run(default_tag: 'test.metrics') do
316
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
317
- end
318
-
319
- assert_equal 2, @posts.size
320
- end
321
-
322
- def test_emit_form_with_custom_headers
323
- d = create_driver CONFIG + %[custom_headers {"key":"custom","token":"arbitrary"}]
324
- d.run(default_tag: 'test.metrics') do
325
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
326
- end
327
-
328
- assert_true @headers.has_key?("key")
329
- assert_equal "custom", @headers["key"]
330
- assert_true @headers.has_key?("token")
331
- assert_equal "arbitrary", @headers["token"]
332
-
333
- assert_equal 1, @posts.size
334
- record = @posts[0]
335
-
336
- assert_equal '50', record[:form]['field1']
337
- assert_equal '20', record[:form]['field2']
338
- assert_equal '10', record[:form]['field3']
339
- assert_equal '1', record[:form]['otherfield']
340
- assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
341
- assert_nil record[:auth]
342
-
343
- d.run(default_tag: 'test.metrics') do
344
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
345
- end
346
-
347
- assert_equal 2, @posts.size
348
- end
349
-
350
- class BufferedEmitTest < self
351
- def test_emit_form
352
- d = create_driver CONFIG + %[buffered true]
353
- d.run(default_tag: 'test.metrics', shutdown: false) do
354
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
355
- end
356
-
357
- assert_equal 1, @posts.size
358
- record = @posts[0]
359
-
360
- assert_equal '50', record[:form]['field1']
361
- assert_equal '20', record[:form]['field2']
362
- assert_equal '10', record[:form]['field3']
363
- assert_equal '1', record[:form]['otherfield']
364
- assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
365
- assert_nil record[:auth]
366
-
367
- d.run(default_tag: 'test.metrics', shutdown: false) do
368
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
369
- end
370
-
371
- assert_equal 2, @posts.size
372
- end
373
-
374
- def test_emit_form_with_placeholders
375
- d = create_driver(Fluent::Config::Element.new(
376
- 'ROOT', '' ,
377
- {"endpoint_url" => "${endpoint}",
378
- "buffered" => true},
379
- [Fluent::Config::Element.new('buffer', 'tag, endpoint', {"@type" => "memory"} ,[])]))
380
-
381
- d.run(default_tag: 'test.metrics', shutdown: false) do
382
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit"), 'endpoint' => "http://127.0.0.1:#{self.class.port}/modified-api/" })
383
- end
384
-
385
- assert_equal 0, @posts.size # post into other URI
386
- assert_equal "http://127.0.0.1:#{self.class.port}/modified-api/", d.instance.endpoint_url
387
- end
388
-
389
- def test_emit_form_put
390
- d = create_driver CONFIG_PUT + %[buffered true]
391
- d.run(default_tag: 'test.metrics', shutdown: false) do
392
- d.feed({ 'field1' => 50 })
393
- end
394
-
395
- assert_equal 0, @posts.size
396
- assert_equal 1, @puts.size
397
- record = @puts[0]
398
-
399
- assert_equal '50', record[:form]['field1']
400
- assert_nil record[:auth]
401
-
402
- d.run(default_tag: 'test.metrics', shutdown: false) do
403
- d.feed({ 'field1' => 50 })
404
- end
405
-
406
- assert_equal 0, @posts.size
407
- assert_equal 2, @puts.size
408
- end
409
-
410
- def test_emit_json
411
- binary_string = "\xe3\x81\x82"
412
- d = create_driver CONFIG_JSON + %[buffered true]
413
- d.run(default_tag: 'test.metrics') do
414
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
415
- end
416
-
417
- assert_equal 1, @posts.size
418
- record = @posts[0]
419
-
420
- assert_equal 50, record[:json]['field1']
421
- assert_equal 20, record[:json]['field2']
422
- assert_equal 10, record[:json]['field3']
423
- assert_equal 1, record[:json]['otherfield']
424
- assert_equal binary_string, record[:json]['binary']
425
- assert_nil record[:auth]
426
- end
427
- end
428
-
429
- def test_emit_form_put
430
- d = create_driver CONFIG_PUT
431
- d.run(default_tag: 'test.metrics') do
432
- d.feed({ 'field1' => 50 })
433
- end
434
-
435
- assert_equal 0, @posts.size
436
- assert_equal 1, @puts.size
437
- record = @puts[0]
438
-
439
- assert_equal '50', record[:form]['field1']
440
- assert_nil record[:auth]
441
-
442
- d.run(default_tag: 'test.metrics') do
443
- d.feed({ 'field1' => 50 })
444
- end
445
-
446
- assert_equal 0, @posts.size
447
- assert_equal 2, @puts.size
448
- end
449
-
450
- def test_emit_json
451
- binary_string = "\xe3\x81\x82"
452
- d = create_driver CONFIG_JSON
453
- d.run(default_tag: 'test.metrics') do
454
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
455
- end
456
-
457
- assert_equal 1, @posts.size
458
- record = @posts[0]
459
-
460
- assert_equal 50, record[:json]['field1']
461
- assert_equal 20, record[:json]['field2']
462
- assert_equal 10, record[:json]['field3']
463
- assert_equal 1, record[:json]['otherfield']
464
- assert_equal binary_string, record[:json]['binary']
465
- assert_nil record[:auth]
466
- end
467
-
468
- def test_emit_text
469
- binary_string = "\xe3\x81\x82"
470
- d = create_driver CONFIG_TEXT
471
- d.run(default_tag: 'test.metrics') do
472
- d.feed({ "message" => "hello" })
473
- end
474
- assert_equal 1, @posts.size
475
- record = @posts[0]
476
- assert_equal 'hello', record[:data]
477
- assert_nil record[:auth]
478
- end
479
-
480
- def test_emit_raw
481
- binary_string = "\xe3\x81\x82"
482
- d = create_driver CONFIG_RAW + %[format msgpack]
483
- d.run(default_tag: 'test.metrics') do
484
- d.feed({ "message" => "hello" })
485
- end
486
- assert_equal 1, @posts.size
487
- record = @posts[0]
488
- assert_equal ({ "message" => "hello" }).to_msgpack, record[:data]
489
- assert_nil record[:auth]
490
- end
491
-
492
- def test_http_error_is_raised
493
- d = create_driver CONFIG_HTTP_ERROR
494
- assert_raise Errno::ECONNREFUSED do
495
- d.run(default_tag: 'test.metrics') do
496
- d.feed({ 'field1' => 50 })
497
- end
498
- end
499
- end
500
-
501
- def test_http_error_is_suppressed_with_raise_on_error_false
502
- d = create_driver CONFIG_HTTP_ERROR_SUPPRESSED
503
- d.run(default_tag: 'test.metrics') do
504
- d.feed({ 'field1' => 50 })
505
- end
506
- # drive asserts the next output chain is called;
507
- # so no exception means our plugin handled the error
508
-
509
- assert_equal 0, @requests
510
- end
511
-
512
- def test_rate_limiting
513
- d = create_driver CONFIG_RATE_LIMIT
514
- record = { :k => 1 }
515
-
516
- last_emit = _current_msec
517
- d.run(default_tag: 'test.metrics') do
518
- d.feed(record)
519
- end
520
-
521
- assert_equal 1, @posts.size
522
-
523
- d.run(default_tag: 'test.metrics') do
524
- d.feed({})
525
- end
526
- assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
527
- assert_equal 1, @posts.size
528
-
529
- wait_msec = 500
530
- sleep (last_emit + RATE_LIMIT_MSEC - _current_msec + wait_msec) * 0.001
531
-
532
- assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
533
- d.run(default_tag: 'test.metrics') do
534
- d.feed(record)
535
- end
536
- assert_equal 2, @posts.size
537
- end
538
-
539
- def _current_msec
540
- Time.now.to_f * 1000
541
- end
542
-
543
- def test_auth
544
- @auth = true # enable authentication of dummy server
545
-
546
- d = create_driver(CONFIG)
547
- d.run(default_tag: 'test.metrics') do
548
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
549
- end # failed in background, and output warn log
550
-
551
- assert_equal 0, @posts.size
552
- assert_equal 1, @prohibited
553
-
554
- d = create_driver(CONFIG + %[
555
- authentication basic
556
- username alice
557
- password wrong_password
558
- ])
559
- d.run(default_tag: 'test.metrics') do
560
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
561
- end # failed in background, and output warn log
562
-
563
- assert_equal 0, @posts.size
564
- assert_equal 2, @prohibited
565
-
566
- d = create_driver(CONFIG + %[
567
- authentication basic
568
- username alice
569
- password secret!
570
- ])
571
- d.run(default_tag: 'test.metrics') do
572
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
573
- end # failed in background, and output warn log
574
-
575
- assert_equal 1, @posts.size
576
- assert_equal 2, @prohibited
577
-
578
- require 'base64'
579
- d = create_driver(CONFIG + %[
580
- authentication bearer
581
- token #{Base64.encode64('secret token!')}
582
- ])
583
- d.run(default_tag: 'test.metrics') do
584
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
585
- end # failed in background, and output warn log
586
-
587
- assert_equal 2, @posts.size
588
- assert_equal 2, @prohibited
589
-
590
- d = create_driver(CONFIG + %[
591
- authentication jwt
592
- token eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIb2dlIFB1Ymxpc2hlciIsInN1YiI6IkhvZ2UgVXNlciJ9.V2NL7YgCWNt5d3vTXFrcRLpRImO2cU2JZ4mQglqw3rE
593
- ])
594
- d.run(default_tag: 'test.metrics') do
595
- d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
596
- end # failed in background, and output warn log
597
-
598
- assert_equal 3, @posts.size
599
- assert_equal 2, @prohibited
600
- end
601
-
602
- class CustomFormatterTest < self
603
- def test_new_config
604
- config = Fluent::Config::Element.new(
605
- 'ROOT', '',
606
- {"@type" => "http",
607
- "endpoint_url" => "http://127.0.0.1:#{self.class.port}/api/",
608
- "serializer" => "json"}, [
609
- Fluent::Config::Element.new('format', '', {
610
- "@type" => "test"
611
- }, [])
612
- ])
613
- d = create_driver config
614
- payload = {"field" => 1}
615
- d.run(default_tag: 'test.metrics') do
616
- d.feed(payload)
617
- end
618
-
619
- record = @posts[0]
620
- expected = {"wrapped" => true, "record" => payload}
621
- assert_equal expected, record[:json]
622
- end
623
-
624
- def test_legacy_config
625
- config = %[
626
- endpoint_url http://127.0.0.1:#{self.class.port}/api/
627
- serializer json
628
- format test
629
- ]
630
-
631
- d = create_driver config
632
- payload = {"field" => 1}
633
- d.run(default_tag: 'test.metrics') do
634
- d.feed(payload)
635
- end
636
-
637
- record = @posts[0]
638
- expected = {"wrapped" => true, "record" => payload}
639
- assert_equal expected, record[:json]
640
- end
641
- end
642
- end
643
-
644
- class HTTPSOutputTest < HTTPOutputTestBase
645
- def self.port
646
- 5127
647
- end
648
-
649
- def self.server_config
650
- config = super
651
- config[:SSLEnable] = true
652
- config[:SSLCertName] = [["CN", WEBrick::Utils::getservername]]
653
- config
654
- end
655
-
656
- def self.test_http_client
657
- super(
658
- use_ssl: true,
659
- verify_mode: OpenSSL::SSL::VERIFY_NONE,
660
- )
661
- end
662
-
663
- def test_configure
664
- test_uri = URI.parse("https://127.0.0.1/")
665
-
666
- ssl_config = %[
667
- endpoint_url https://127.0.0.1:#{self.class.port}/api/
668
- ]
669
- d = create_driver ssl_config
670
- expected_endpoint_url = "https://127.0.0.1:#{self.class.port}/api/"
671
- assert_equal expected_endpoint_url, d.instance.endpoint_url
672
- http_opts = d.instance.http_opts(test_uri)
673
- assert_equal true, http_opts[:use_ssl]
674
- assert_equal OpenSSL::SSL::VERIFY_PEER, http_opts[:verify_mode]
675
-
676
- no_verify_config = %[
677
- endpoint_url https://127.0.0.1:#{self.class.port}/api/
678
- ssl_no_verify true
679
- ]
680
- d = create_driver no_verify_config
681
- http_opts = d.instance.http_opts(test_uri)
682
- assert_equal true, http_opts[:use_ssl]
683
- assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
684
-
685
- cacert_file_config = %[
686
- endpoint_url https://127.0.0.1:#{self.class.port}/api/
687
- ssl_no_verify true
688
- cacert_file /tmp/ssl.cert
689
- ]
690
- d = create_driver cacert_file_config
691
- FileUtils::touch '/tmp/ssl.cert'
692
- http_opts = d.instance.http_opts(test_uri)
693
- assert_equal true, http_opts[:use_ssl]
694
- assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
695
- assert_equal true, File.file?('/tmp/ssl.cert')
696
- puts http_opts
697
- assert_equal File.join('/tmp/ssl.cert'), http_opts[:ca_file]
698
- end
699
-
700
- def test_emit_form_ssl
701
- config = %[
702
- endpoint_url https://127.0.0.1:#{self.class.port}/api/
703
- ssl_no_verify true
704
- ]
705
- d = create_driver config
706
- d.run(default_tag: 'test.metrics') do
707
- d.feed({ 'field1' => 50 })
708
- end
709
-
710
- assert_equal 1, @posts.size
711
- record = @posts[0]
712
-
713
- assert_equal '50', record[:form]['field1']
714
- end
715
-
716
- def test_emit_form_ssl_ca
717
- config = %[
718
- endpoint_url https://127.0.0.1:#{self.class.port}/api/
719
- ssl_no_verify true
720
- cacert_file /tmp/ssl.cert
721
- ]
722
- d = create_driver config
723
- d.run(default_tag: 'test.metrics') do
724
- d.feed({ 'field1' => 50 })
725
- end
726
-
727
- assert_equal 1, @posts.size
728
- record = @posts[0]
729
-
730
- assert_equal '50', record[:form]['field1']
731
- end
732
- end
1
+ # -*- coding: utf-8 -*-
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'yajl'
5
+ require 'fluent/test/http_output_test'
6
+ require 'fluent/plugin/out_http'
7
+ require 'fluent/test/driver/output'
8
+ require 'fluent/test/helpers'
9
+ require_relative "./script/plugin/formatter_test"
10
+
11
+ module OS
12
+ # ref. http://stackoverflow.com/questions/170956/how-can-i-find-which-operating-system-my-ruby-program-is-running-on
13
+ def OS.windows?
14
+ (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
15
+ end
16
+
17
+ def OS.mac?
18
+ (/darwin/ =~ RUBY_PLATFORM) != nil
19
+ end
20
+
21
+ def OS.unix?
22
+ !OS.windows?
23
+ end
24
+
25
+ def OS.linux?
26
+ OS.unix? and not OS.mac?
27
+ end
28
+ end
29
+
30
+ class HTTPOutputTestBase < Test::Unit::TestCase
31
+ include Fluent::Test::Helpers
32
+
33
+ def self.port
34
+ 5126
35
+ end
36
+
37
+ def self.server_config
38
+ config = {BindAddress: '127.0.0.1', Port: port}
39
+ if ENV['VERBOSE']
40
+ logger = WEBrick::Log.new(STDOUT, WEBrick::BasicLog::DEBUG)
41
+ config[:Logger] = logger
42
+ config[:AccessLog] = []
43
+ end
44
+ config
45
+ end
46
+
47
+ def self.test_http_client(**opts)
48
+ opts = opts.merge(open_timeout: 1, read_timeout: 1)
49
+ Net::HTTP.start('127.0.0.1', port, **opts)
50
+ end
51
+
52
+ # setup / teardown for servers
53
+ def setup
54
+ Fluent::Test.setup
55
+ @posts = []
56
+ @puts = []
57
+ @prohibited = 0
58
+ @requests = 0
59
+ @auth = false
60
+ @headers = {}
61
+ @dummy_server_thread = Thread.new do
62
+ srv = WEBrick::HTTPServer.new(self.class.server_config)
63
+ begin
64
+ allowed_methods = %w(POST PUT)
65
+ srv.mount_proc('/api') { |req,res|
66
+ @requests += 1
67
+ unless allowed_methods.include? req.request_method
68
+ res.status = 405
69
+ res.body = 'request method mismatch'
70
+ next
71
+ end
72
+ req.each do |key, value|
73
+ @headers[key] = value
74
+ end
75
+ if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
76
+ # ok, authorized
77
+ # pattern of bear #{Base64.encode64('secret token!')}
78
+ elsif @auth and req.header['authorization'][0] == 'bearer c2VjcmV0IHRva2VuIQ=='
79
+ # pattern of jwt
80
+ # header: {
81
+ # "alg": "HS256",
82
+ # "typ": "JWT"
83
+ # }
84
+ # payload: {
85
+ # "iss": "Hoge Publisher",
86
+ # "sub": "Hoge User"
87
+ # }
88
+ # signature:
89
+ # HS256(base64UrlEncode(header) + "." +
90
+ # base64UrlEncode(payload) + "." +
91
+ # secret)
92
+ elsif @auth and req.header['authorization'][0] == 'jwt eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIb2dlIFB1Ymxpc2hlciIsInN1YiI6IkhvZ2UgVXNlciJ9.V2NL7YgCWNt5d3vTXFrcRLpRImO2cU2JZ4mQglqw3rE'
93
+ elsif @auth
94
+ res.status = 403
95
+ @prohibited += 1
96
+ next
97
+ else
98
+ # ok, authorization not required
99
+ end
100
+
101
+ record = {:auth => nil}
102
+ if req.content_type == 'application/json'
103
+ record[:json] = Yajl.load(req.body)
104
+ elsif req.content_type == 'text/plain'
105
+ puts req
106
+ record[:data] = req.body
107
+ elsif req.content_type == 'application/octet-stream'
108
+ record[:data] = req.body
109
+ elsif req.content_type == 'application/x-ndjson'
110
+ data = []
111
+ req.body.each_line { |l|
112
+ data << Yajl.load(l)
113
+ }
114
+ record[:x_ndjson] = data
115
+ else
116
+ record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
117
+ end
118
+
119
+ instance_variable_get("@#{req.request_method.downcase}s").push(record)
120
+
121
+ res.status = 200
122
+ }
123
+ srv.mount_proc('/modified-api') { |req,res|
124
+ res.status = 303
125
+ res.body = 'See other'
126
+ }
127
+ srv.mount_proc('/') { |req,res|
128
+ res.status = 200
129
+ res.body = 'running'
130
+ }
131
+ srv.start
132
+ ensure
133
+ srv.shutdown
134
+ end
135
+ end
136
+
137
+ # to wait completion of dummy server.start()
138
+ require 'thread'
139
+ cv = ConditionVariable.new
140
+ watcher = Thread.new {
141
+ connected = false
142
+ while not connected
143
+ begin
144
+ client = self.class.test_http_client
145
+ client.request_get('/')
146
+ connected = true
147
+ rescue Errno::ECONNREFUSED
148
+ sleep 0.1
149
+ rescue StandardError => e
150
+ p e
151
+ sleep 0.1
152
+ end
153
+ end
154
+ cv.signal
155
+ }
156
+ mutex = Mutex.new
157
+ mutex.synchronize {
158
+ cv.wait(mutex)
159
+ }
160
+ end
161
+
162
+ def test_dummy_server
163
+ client = self.class.test_http_client
164
+ post_header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
165
+
166
+ assert_equal '200', client.request_get('/').code
167
+ assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge', post_header).code
168
+
169
+ assert_equal 1, @posts.size
170
+
171
+ assert_equal '1', @posts[0][:form]['number']
172
+ assert_equal 'gauge', @posts[0][:form]['mode']
173
+ assert_nil @posts[0][:auth]
174
+
175
+ assert_equal '303', client.request_get('/modified-api').code
176
+
177
+ @auth = true
178
+
179
+ assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge', post_header).code
180
+
181
+ req_with_auth = lambda do |number, mode, user, pass|
182
+ req = Net::HTTP::Post.new("/api/service/metrics/pos")
183
+ req.content_type = 'application/x-www-form-urlencoded'
184
+ req.basic_auth user, pass
185
+ req.set_form_data({'number'=>number, 'mode'=>mode})
186
+ req
187
+ end
188
+
189
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
190
+
191
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
192
+
193
+ assert_equal 1, @posts.size
194
+
195
+ assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
196
+
197
+ assert_equal 2, @posts.size
198
+
199
+ end
200
+
201
+ def teardown
202
+ @dummy_server_thread.kill
203
+ @dummy_server_thread.join
204
+ end
205
+
206
+ def create_driver(conf)
207
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::HTTPOutput).configure(conf)
208
+ end
209
+ end
210
+
211
+ class HTTPOutputTest < HTTPOutputTestBase
212
+ CONFIG = %[
213
+ endpoint_url http://127.0.0.1:#{port}/api/
214
+ ]
215
+
216
+ CONFIG_QUERY_PARAM = %[
217
+ endpoint_url http://127.0.0.1:#{port}/api?foo=bar&baz=qux
218
+ ]
219
+
220
+ CONFIG_JSON = %[
221
+ endpoint_url http://127.0.0.1:#{port}/api/
222
+ serializer json
223
+ ]
224
+
225
+ CONFIG_TEXT = %[
226
+ endpoint_url http://127.0.0.1:#{port}/api/
227
+ serializer text
228
+ ]
229
+
230
+ CONFIG_RAW = %[
231
+ endpoint_url http://127.0.0.1:#{port}/api/
232
+ serializer raw
233
+ ]
234
+
235
+ CONFIG_PUT = %[
236
+ endpoint_url http://127.0.0.1:#{port}/api/
237
+ http_method put
238
+ ]
239
+
240
+ CONFIG_HTTP_ERROR = %[
241
+ endpoint_url https://127.0.0.1:#{port - 1}/api/
242
+ ]
243
+
244
+ CONFIG_HTTP_ERROR_SUPPRESSED = %[
245
+ endpoint_url https://127.0.0.1:#{port - 1}/api/
246
+ raise_on_error false
247
+ ]
248
+
249
+ RATE_LIMIT_MSEC = 1200
250
+
251
+ CONFIG_RATE_LIMIT = %[
252
+ endpoint_url http://127.0.0.1:#{port}/api/
253
+ rate_limit_msec #{RATE_LIMIT_MSEC}
254
+ ]
255
+
256
+ def test_configure
257
+ d = create_driver CONFIG
258
+ assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
259
+ assert_equal :form, d.instance.serializer
260
+ assert_equal [503], d.instance.recoverable_status_codes
261
+
262
+ d = create_driver CONFIG_JSON
263
+ assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
264
+ assert_equal :json, d.instance.serializer
265
+ end
266
+
267
+ test 'lack of tag in chunk_keys' do
268
+ assert_raise_message(/'tag' in chunk_keys is required./) do
269
+ create_driver(Fluent::Config::Element.new(
270
+ 'ROOT', '', {
271
+ '@type' => 'http',
272
+ 'endpoint_url' => "http://127.0.0.1:#{self.class.port}/api/",
273
+ 'buffered' => true,
274
+ }, [
275
+ Fluent::Config::Element.new('buffer', 'mykey', {
276
+ 'chunk_keys' => 'mykey'
277
+ }, [])
278
+ ]
279
+ ))
280
+ end
281
+ end
282
+
283
+ def test_emit_form
284
+ d = create_driver CONFIG
285
+ d.run(default_tag: 'test.metrics') do
286
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
287
+ end
288
+
289
+ assert_equal 1, @posts.size
290
+ record = @posts[0]
291
+
292
+ assert_equal '50', record[:form]['field1']
293
+ assert_equal '20', record[:form]['field2']
294
+ assert_equal '10', record[:form]['field3']
295
+ assert_equal '1', record[:form]['otherfield']
296
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
297
+ assert_nil record[:auth]
298
+
299
+ d.run(default_tag: 'test.metrics') do
300
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
301
+ end
302
+
303
+ assert_equal 2, @posts.size
304
+ end
305
+
306
+ def test_emit_form_with_query_params
307
+ d = create_driver CONFIG_QUERY_PARAM
308
+ d.run(default_tag: 'test.metrics') do
309
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
310
+ end
311
+
312
+ assert_equal 1, @posts.size
313
+ record = @posts[0]
314
+
315
+ assert_equal '50', record[:form]['field1']
316
+ assert_equal '20', record[:form]['field2']
317
+ assert_equal '10', record[:form]['field3']
318
+ assert_equal '1', record[:form]['otherfield']
319
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
320
+ assert_nil record[:auth]
321
+
322
+ d.run(default_tag: 'test.metrics') do
323
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
324
+ end
325
+
326
+ assert_equal 2, @posts.size
327
+ end
328
+
329
+ def test_emit_form_with_custom_headers
330
+ d = create_driver CONFIG + %[custom_headers {"key":"custom","token":"arbitrary"}]
331
+ d.run(default_tag: 'test.metrics') do
332
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
333
+ end
334
+
335
+ assert_true @headers.has_key?("key")
336
+ assert_equal "custom", @headers["key"]
337
+ assert_true @headers.has_key?("token")
338
+ assert_equal "arbitrary", @headers["token"]
339
+
340
+ assert_equal 1, @posts.size
341
+ record = @posts[0]
342
+
343
+ assert_equal '50', record[:form]['field1']
344
+ assert_equal '20', record[:form]['field2']
345
+ assert_equal '10', record[:form]['field3']
346
+ assert_equal '1', record[:form]['otherfield']
347
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
348
+ assert_nil record[:auth]
349
+
350
+ d.run(default_tag: 'test.metrics') do
351
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
352
+ end
353
+
354
+ assert_equal 2, @posts.size
355
+ end
356
+
357
+ class BufferedEmitTest < self
358
+ def test_emit_form
359
+ d = create_driver CONFIG + %[buffered true]
360
+ d.run(default_tag: 'test.metrics', shutdown: false) do
361
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
362
+ end
363
+
364
+ assert_equal 1, @posts.size
365
+ record = @posts[0]
366
+
367
+ assert_equal '50', record[:form]['field1']
368
+ assert_equal '20', record[:form]['field2']
369
+ assert_equal '10', record[:form]['field3']
370
+ assert_equal '1', record[:form]['otherfield']
371
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
372
+ assert_nil record[:auth]
373
+
374
+ d.run(default_tag: 'test.metrics', shutdown: false) do
375
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
376
+ end
377
+
378
+ assert_equal 2, @posts.size
379
+ end
380
+
381
+ def test_emit_form_with_placeholders
382
+ d = create_driver(Fluent::Config::Element.new(
383
+ 'ROOT', '' ,
384
+ {"endpoint_url" => "${endpoint}",
385
+ "buffered" => true},
386
+ [Fluent::Config::Element.new('buffer', 'tag, endpoint', {"@type" => "memory"} ,[])]))
387
+
388
+ d.run(default_tag: 'test.metrics', shutdown: false) do
389
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit"), 'endpoint' => "http://127.0.0.1:#{self.class.port}/modified-api/" })
390
+ end
391
+
392
+ assert_equal 0, @posts.size # post into other URI
393
+ assert_equal "http://127.0.0.1:#{self.class.port}/modified-api/", d.instance.endpoint_url
394
+ end
395
+
396
+ def test_emit_form_put
397
+ d = create_driver CONFIG_PUT + %[buffered true]
398
+ d.run(default_tag: 'test.metrics', shutdown: false) do
399
+ d.feed({ 'field1' => 50 })
400
+ end
401
+
402
+ assert_equal 0, @posts.size
403
+ assert_equal 1, @puts.size
404
+ record = @puts[0]
405
+
406
+ assert_equal '50', record[:form]['field1']
407
+ assert_nil record[:auth]
408
+
409
+ d.run(default_tag: 'test.metrics', shutdown: false) do
410
+ d.feed({ 'field1' => 50 })
411
+ end
412
+
413
+ assert_equal 0, @posts.size
414
+ assert_equal 2, @puts.size
415
+ end
416
+
417
+ def test_emit_json
418
+ binary_string = "\xe3\x81\x82"
419
+ d = create_driver CONFIG_JSON + %[buffered true]
420
+ d.run(default_tag: 'test.metrics') do
421
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
422
+ end
423
+
424
+ assert_equal 1, @posts.size
425
+ record = @posts[0]
426
+
427
+ assert_equal 50, record[:json]['field1']
428
+ assert_equal 20, record[:json]['field2']
429
+ assert_equal 10, record[:json]['field3']
430
+ assert_equal 1, record[:json]['otherfield']
431
+ assert_equal binary_string, record[:json]['binary']
432
+ assert_nil record[:auth]
433
+ end
434
+
435
+ def test_emit_x_ndjson
436
+ binary_string = "\xe3\x81\x82"
437
+ d = create_driver CONFIG_JSON + %[buffered true\nbulk_request]
438
+ d.run(default_tag: 'test.metrics') do
439
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
440
+ d.feed({ 'field1' => 70, 'field2' => 30, 'field3' => 20, 'otherfield' => 2, 'binary' => binary_string })
441
+ end
442
+
443
+ assert_equal 1, @posts.size
444
+ record = @posts[0]
445
+
446
+ expected =[
447
+ {
448
+ "binary" => "\u3042",
449
+ "field1" => 50,
450
+ "field2" => 20,
451
+ "field3" => 10,
452
+ "otherfield" => 1
453
+ },
454
+ {
455
+ "binary" => "\u3042",
456
+ "field1" => 70,
457
+ "field2" => 30,
458
+ "field3" => 20,
459
+ "otherfield" => 2
460
+ }
461
+ ]
462
+
463
+ assert_equal expected, record[:x_ndjson]
464
+ assert_nil record[:auth]
465
+ end
466
+ end
467
+
468
+ def test_emit_form_put
469
+ d = create_driver CONFIG_PUT
470
+ d.run(default_tag: 'test.metrics') do
471
+ d.feed({ 'field1' => 50 })
472
+ end
473
+
474
+ assert_equal 0, @posts.size
475
+ assert_equal 1, @puts.size
476
+ record = @puts[0]
477
+
478
+ assert_equal '50', record[:form]['field1']
479
+ assert_nil record[:auth]
480
+
481
+ d.run(default_tag: 'test.metrics') do
482
+ d.feed({ 'field1' => 50 })
483
+ end
484
+
485
+ assert_equal 0, @posts.size
486
+ assert_equal 2, @puts.size
487
+ end
488
+
489
+ def test_emit_json
490
+ binary_string = "\xe3\x81\x82"
491
+ d = create_driver CONFIG_JSON
492
+ d.run(default_tag: 'test.metrics') do
493
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
494
+ end
495
+
496
+ assert_equal 1, @posts.size
497
+ record = @posts[0]
498
+
499
+ assert_equal 50, record[:json]['field1']
500
+ assert_equal 20, record[:json]['field2']
501
+ assert_equal 10, record[:json]['field3']
502
+ assert_equal 1, record[:json]['otherfield']
503
+ assert_equal binary_string, record[:json]['binary']
504
+ assert_nil record[:auth]
505
+ end
506
+
507
+ def test_emit_text
508
+ binary_string = "\xe3\x81\x82"
509
+ d = create_driver CONFIG_TEXT
510
+ d.run(default_tag: 'test.metrics') do
511
+ d.feed({ "message" => "hello" })
512
+ end
513
+ assert_equal 1, @posts.size
514
+ record = @posts[0]
515
+ assert_equal 'hello', record[:data]
516
+ assert_nil record[:auth]
517
+ end
518
+
519
+ def test_emit_raw
520
+ binary_string = "\xe3\x81\x82"
521
+ d = create_driver CONFIG_RAW + %[format msgpack]
522
+ d.run(default_tag: 'test.metrics') do
523
+ d.feed({ "message" => "hello" })
524
+ end
525
+ assert_equal 1, @posts.size
526
+ record = @posts[0]
527
+ assert_equal ({ "message" => "hello" }).to_msgpack, record[:data]
528
+ assert_nil record[:auth]
529
+ end
530
+
531
+ def test_http_error_is_raised
532
+ d = create_driver CONFIG_HTTP_ERROR
533
+ assert_raise Errno::ECONNREFUSED do
534
+ d.run(default_tag: 'test.metrics') do
535
+ d.feed({ 'field1' => 50 })
536
+ end
537
+ end
538
+ end
539
+
540
+ def test_http_error_is_suppressed_with_raise_on_error_false
541
+ d = create_driver CONFIG_HTTP_ERROR_SUPPRESSED
542
+ d.run(default_tag: 'test.metrics') do
543
+ d.feed({ 'field1' => 50 })
544
+ end
545
+ # drive asserts the next output chain is called;
546
+ # so no exception means our plugin handled the error
547
+
548
+ assert_equal 0, @requests
549
+ end
550
+
551
+ def test_rate_limiting
552
+ d = create_driver CONFIG_RATE_LIMIT
553
+ record = { :k => 1 }
554
+
555
+ last_emit = _current_msec
556
+ d.run(default_tag: 'test.metrics') do
557
+ d.feed(record)
558
+ end
559
+
560
+ assert_equal 1, @posts.size
561
+
562
+ d.run(default_tag: 'test.metrics') do
563
+ d.feed({})
564
+ end
565
+ assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
566
+ assert_equal 1, @posts.size
567
+
568
+ wait_msec = 500
569
+ sleep (last_emit + RATE_LIMIT_MSEC - _current_msec + wait_msec) * 0.001
570
+
571
+ assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
572
+ d.run(default_tag: 'test.metrics') do
573
+ d.feed(record)
574
+ end
575
+ assert_equal 2, @posts.size
576
+ end
577
+
578
+ def _current_msec
579
+ Time.now.to_f * 1000
580
+ end
581
+
582
+ def test_auth
583
+ @auth = true # enable authentication of dummy server
584
+
585
+ d = create_driver(CONFIG)
586
+ d.run(default_tag: 'test.metrics') do
587
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
588
+ end # failed in background, and output warn log
589
+
590
+ assert_equal 0, @posts.size
591
+ assert_equal 1, @prohibited
592
+
593
+ d = create_driver(CONFIG + %[
594
+ authentication basic
595
+ username alice
596
+ password wrong_password
597
+ ])
598
+ d.run(default_tag: 'test.metrics') do
599
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
600
+ end # failed in background, and output warn log
601
+
602
+ assert_equal 0, @posts.size
603
+ assert_equal 2, @prohibited
604
+
605
+ d = create_driver(CONFIG + %[
606
+ authentication basic
607
+ username alice
608
+ password secret!
609
+ ])
610
+ d.run(default_tag: 'test.metrics') do
611
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
612
+ end # failed in background, and output warn log
613
+
614
+ assert_equal 1, @posts.size
615
+ assert_equal 2, @prohibited
616
+
617
+ require 'base64'
618
+ d = create_driver(CONFIG + %[
619
+ authentication bearer
620
+ token #{Base64.encode64('secret token!')}
621
+ ])
622
+ d.run(default_tag: 'test.metrics') do
623
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
624
+ end # failed in background, and output warn log
625
+
626
+ assert_equal 2, @posts.size
627
+ assert_equal 2, @prohibited
628
+
629
+ d = create_driver(CONFIG + %[
630
+ authentication jwt
631
+ token eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIb2dlIFB1Ymxpc2hlciIsInN1YiI6IkhvZ2UgVXNlciJ9.V2NL7YgCWNt5d3vTXFrcRLpRImO2cU2JZ4mQglqw3rE
632
+ ])
633
+ d.run(default_tag: 'test.metrics') do
634
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
635
+ end # failed in background, and output warn log
636
+
637
+ assert_equal 3, @posts.size
638
+ assert_equal 2, @prohibited
639
+ end
640
+
641
+ class CustomFormatterTest < self
642
+ def test_new_config
643
+ config = Fluent::Config::Element.new(
644
+ 'ROOT', '',
645
+ {"@type" => "http",
646
+ "endpoint_url" => "http://127.0.0.1:#{self.class.port}/api/",
647
+ "serializer" => "json"}, [
648
+ Fluent::Config::Element.new('format', '', {
649
+ "@type" => "test"
650
+ }, [])
651
+ ])
652
+ d = create_driver config
653
+ payload = {"field" => 1}
654
+ d.run(default_tag: 'test.metrics') do
655
+ d.feed(payload)
656
+ end
657
+
658
+ record = @posts[0]
659
+ expected = {"wrapped" => true, "record" => payload}
660
+ assert_equal expected, record[:json]
661
+ end
662
+
663
+ def test_legacy_config
664
+ config = %[
665
+ endpoint_url http://127.0.0.1:#{self.class.port}/api/
666
+ serializer json
667
+ format test
668
+ ]
669
+
670
+ d = create_driver config
671
+ payload = {"field" => 1}
672
+ d.run(default_tag: 'test.metrics') do
673
+ d.feed(payload)
674
+ end
675
+
676
+ record = @posts[0]
677
+ expected = {"wrapped" => true, "record" => payload}
678
+ assert_equal expected, record[:json]
679
+ end
680
+ end
681
+ end
682
+
683
+ class HTTPSOutputTest < HTTPOutputTestBase
684
+ def self.port
685
+ 5127
686
+ end
687
+
688
+ def self.server_config
689
+ config = super
690
+ config[:SSLEnable] = true
691
+ config[:SSLCertName] = [["CN", WEBrick::Utils::getservername]]
692
+ config
693
+ end
694
+
695
+ def self.test_http_client
696
+ super(
697
+ use_ssl: true,
698
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
699
+ )
700
+ end
701
+
702
+ def test_configure
703
+ test_uri = URI.parse("https://127.0.0.1/")
704
+
705
+ ssl_config = %[
706
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
707
+ ]
708
+ d = create_driver ssl_config
709
+ expected_endpoint_url = "https://127.0.0.1:#{self.class.port}/api/"
710
+ assert_equal expected_endpoint_url, d.instance.endpoint_url
711
+ http_opts = d.instance.http_opts(test_uri)
712
+ assert_equal true, http_opts[:use_ssl]
713
+ assert_equal OpenSSL::SSL::VERIFY_PEER, http_opts[:verify_mode]
714
+
715
+ no_verify_config = %[
716
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
717
+ ssl_no_verify true
718
+ ]
719
+ d = create_driver no_verify_config
720
+ http_opts = d.instance.http_opts(test_uri)
721
+ assert_equal true, http_opts[:use_ssl]
722
+ assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
723
+
724
+ cacert_file_config = %[
725
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
726
+ ssl_no_verify true
727
+ cacert_file /tmp/ssl.cert
728
+ ]
729
+ d = create_driver cacert_file_config
730
+ FileUtils::touch '/tmp/ssl.cert'
731
+ http_opts = d.instance.http_opts(test_uri)
732
+ assert_equal true, http_opts[:use_ssl]
733
+ assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
734
+ assert_equal true, File.file?('/tmp/ssl.cert')
735
+ puts http_opts
736
+ assert_equal File.join('/tmp/ssl.cert'), http_opts[:ca_file]
737
+ end
738
+
739
+ def test_emit_form_ssl
740
+ config = %[
741
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
742
+ ssl_no_verify true
743
+ ]
744
+ d = create_driver config
745
+ d.run(default_tag: 'test.metrics') do
746
+ d.feed({ 'field1' => 50 })
747
+ end
748
+
749
+ assert_equal 1, @posts.size
750
+ record = @posts[0]
751
+
752
+ assert_equal '50', record[:form]['field1']
753
+ end
754
+
755
+ def test_emit_form_ssl_ca
756
+ config = %[
757
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
758
+ ssl_no_verify true
759
+ cacert_file /tmp/ssl.cert
760
+ ]
761
+ d = create_driver config
762
+ d.run(default_tag: 'test.metrics') do
763
+ d.feed({ 'field1' => 50 })
764
+ end
765
+
766
+ assert_equal 1, @posts.size
767
+ record = @posts[0]
768
+
769
+ assert_equal '50', record[:form]['field1']
770
+ end
771
+ end