fluent-plugin-out-http 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,771 +1,872 @@
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
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
+ expander = -> (req) {
102
+ if req["Content-Encoding"] == "gzip"
103
+ StringIO.open(req.body, 'rb'){|sio|
104
+ Zlib::GzipReader.wrap(sio).read
105
+ }
106
+ else
107
+ req.body
108
+ end
109
+ }
110
+
111
+ record = {:auth => nil}
112
+ if req.content_type == 'application/json'
113
+ record[:json] = Yajl.load(expander.call(req))
114
+ elsif req.content_type == 'text/plain'
115
+ puts req
116
+ record[:data] = expander.call(req)
117
+ elsif req.content_type == 'application/octet-stream'
118
+ record[:data] = expander.call(req)
119
+ elsif req.content_type == 'application/x-ndjson'
120
+ data = []
121
+ expander.call(req).each_line { |l|
122
+ data << Yajl.load(l)
123
+ }
124
+ record[:x_ndjson] = data
125
+ else
126
+ record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
127
+ end
128
+
129
+ instance_variable_get("@#{req.request_method.downcase}s").push(record)
130
+
131
+ res.status = 200
132
+ }
133
+ srv.mount_proc('/modified-api') { |req,res|
134
+ res.status = 303
135
+ res.body = 'See other'
136
+ }
137
+ srv.mount_proc('/') { |req,res|
138
+ res.status = 200
139
+ res.body = 'running'
140
+ }
141
+ srv.start
142
+ ensure
143
+ srv.shutdown
144
+ end
145
+ end
146
+
147
+ # to wait completion of dummy server.start()
148
+ require 'thread'
149
+ cv = ConditionVariable.new
150
+ watcher = Thread.new {
151
+ connected = false
152
+ while not connected
153
+ begin
154
+ client = self.class.test_http_client
155
+ client.request_get('/')
156
+ connected = true
157
+ rescue Errno::ECONNREFUSED
158
+ sleep 0.1
159
+ rescue StandardError => e
160
+ p e
161
+ sleep 0.1
162
+ end
163
+ end
164
+ cv.signal
165
+ }
166
+ mutex = Mutex.new
167
+ mutex.synchronize {
168
+ cv.wait(mutex)
169
+ }
170
+ end
171
+
172
+ def test_dummy_server
173
+ client = self.class.test_http_client
174
+ post_header = { 'Content-Type' => 'application/x-www-form-urlencoded' }
175
+
176
+ assert_equal '200', client.request_get('/').code
177
+ assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge', post_header).code
178
+
179
+ assert_equal 1, @posts.size
180
+
181
+ assert_equal '1', @posts[0][:form]['number']
182
+ assert_equal 'gauge', @posts[0][:form]['mode']
183
+ assert_nil @posts[0][:auth]
184
+
185
+ assert_equal '303', client.request_get('/modified-api').code
186
+
187
+ @auth = true
188
+
189
+ assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge', post_header).code
190
+
191
+ req_with_auth = lambda do |number, mode, user, pass|
192
+ req = Net::HTTP::Post.new("/api/service/metrics/pos")
193
+ req.content_type = 'application/x-www-form-urlencoded'
194
+ req.basic_auth user, pass
195
+ req.set_form_data({'number'=>number, 'mode'=>mode})
196
+ req
197
+ end
198
+
199
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
200
+
201
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
202
+
203
+ assert_equal 1, @posts.size
204
+
205
+ assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
206
+
207
+ assert_equal 2, @posts.size
208
+
209
+ end
210
+
211
+ def teardown
212
+ @dummy_server_thread.kill
213
+ @dummy_server_thread.join
214
+ end
215
+
216
+ def create_driver(conf)
217
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::HTTPOutput).configure(conf)
218
+ end
219
+ end
220
+
221
+ class HTTPOutputTest < HTTPOutputTestBase
222
+ CONFIG = %[
223
+ endpoint_url http://127.0.0.1:#{port}/api/
224
+ ]
225
+
226
+ CONFIG_QUERY_PARAM = %[
227
+ endpoint_url http://127.0.0.1:#{port}/api?foo=bar&baz=qux
228
+ ]
229
+
230
+ CONFIG_JSON = %[
231
+ endpoint_url http://127.0.0.1:#{port}/api/
232
+ serializer json
233
+ ]
234
+
235
+ CONFIG_TEXT = %[
236
+ endpoint_url http://127.0.0.1:#{port}/api/
237
+ serializer text
238
+ ]
239
+
240
+ CONFIG_RAW = %[
241
+ endpoint_url http://127.0.0.1:#{port}/api/
242
+ serializer raw
243
+ ]
244
+
245
+ CONFIG_PUT = %[
246
+ endpoint_url http://127.0.0.1:#{port}/api/
247
+ http_method put
248
+ ]
249
+
250
+ CONFIG_HTTP_ERROR = %[
251
+ endpoint_url https://127.0.0.1:#{port - 1}/api/
252
+ ]
253
+
254
+ CONFIG_HTTP_ERROR_SUPPRESSED = %[
255
+ endpoint_url https://127.0.0.1:#{port - 1}/api/
256
+ raise_on_error false
257
+ ]
258
+
259
+ RATE_LIMIT_MSEC = 1200
260
+
261
+ CONFIG_RATE_LIMIT = %[
262
+ endpoint_url http://127.0.0.1:#{port}/api/
263
+ rate_limit_msec #{RATE_LIMIT_MSEC}
264
+ ]
265
+
266
+ def test_configure
267
+ d = create_driver CONFIG
268
+ assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
269
+ assert_equal :form, d.instance.serializer
270
+ assert_equal [503], d.instance.recoverable_status_codes
271
+
272
+ d = create_driver CONFIG_JSON
273
+ assert_equal "http://127.0.0.1:#{self.class.port}/api/", d.instance.endpoint_url
274
+ assert_equal :json, d.instance.serializer
275
+ end
276
+
277
+ test 'lack of tag in chunk_keys' do
278
+ assert_raise_message(/'tag' in chunk_keys is required./) do
279
+ create_driver(Fluent::Config::Element.new(
280
+ 'ROOT', '', {
281
+ '@type' => 'http',
282
+ 'endpoint_url' => "http://127.0.0.1:#{self.class.port}/api/",
283
+ 'buffered' => true,
284
+ }, [
285
+ Fluent::Config::Element.new('buffer', 'mykey', {
286
+ 'chunk_keys' => 'mykey'
287
+ }, [])
288
+ ]
289
+ ))
290
+ end
291
+ end
292
+
293
+ def test_emit_form
294
+ d = create_driver CONFIG
295
+ d.run(default_tag: 'test.metrics') do
296
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
297
+ end
298
+
299
+ assert_equal 1, @posts.size
300
+ record = @posts[0]
301
+
302
+ assert_equal '50', record[:form]['field1']
303
+ assert_equal '20', record[:form]['field2']
304
+ assert_equal '10', record[:form]['field3']
305
+ assert_equal '1', record[:form]['otherfield']
306
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
307
+ assert_nil record[:auth]
308
+
309
+ d.run(default_tag: 'test.metrics') do
310
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
311
+ end
312
+
313
+ assert_equal 2, @posts.size
314
+ end
315
+
316
+ def test_emit_form_with_query_params
317
+ d = create_driver CONFIG_QUERY_PARAM
318
+ d.run(default_tag: 'test.metrics') do
319
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
320
+ end
321
+
322
+ assert_equal 1, @posts.size
323
+ record = @posts[0]
324
+
325
+ assert_equal '50', record[:form]['field1']
326
+ assert_equal '20', record[:form]['field2']
327
+ assert_equal '10', record[:form]['field3']
328
+ assert_equal '1', record[:form]['otherfield']
329
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
330
+ assert_nil record[:auth]
331
+
332
+ d.run(default_tag: 'test.metrics') do
333
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
334
+ end
335
+
336
+ assert_equal 2, @posts.size
337
+ end
338
+
339
+ def test_emit_form_with_custom_headers
340
+ d = create_driver CONFIG + %[custom_headers {"key":"custom","token":"arbitrary"}]
341
+ d.run(default_tag: 'test.metrics') do
342
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
343
+ end
344
+
345
+ assert_true @headers.has_key?("key")
346
+ assert_equal "custom", @headers["key"]
347
+ assert_true @headers.has_key?("token")
348
+ assert_equal "arbitrary", @headers["token"]
349
+
350
+ assert_equal 1, @posts.size
351
+ record = @posts[0]
352
+
353
+ assert_equal '50', record[:form]['field1']
354
+ assert_equal '20', record[:form]['field2']
355
+ assert_equal '10', record[:form]['field3']
356
+ assert_equal '1', record[:form]['otherfield']
357
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
358
+ assert_nil record[:auth]
359
+
360
+ d.run(default_tag: 'test.metrics') do
361
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
362
+ end
363
+
364
+ assert_equal 2, @posts.size
365
+ end
366
+
367
+ class BufferedEmitTest < self
368
+ def test_emit_form
369
+ d = create_driver CONFIG + %[buffered true]
370
+ d.run(default_tag: 'test.metrics', shutdown: false) do
371
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
372
+ end
373
+
374
+ assert_equal 1, @posts.size
375
+ record = @posts[0]
376
+
377
+ assert_equal '50', record[:form]['field1']
378
+ assert_equal '20', record[:form]['field2']
379
+ assert_equal '10', record[:form]['field3']
380
+ assert_equal '1', record[:form]['otherfield']
381
+ assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
382
+ assert_nil record[:auth]
383
+
384
+ d.run(default_tag: 'test.metrics', shutdown: false) do
385
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
386
+ end
387
+
388
+ assert_equal 2, @posts.size
389
+ end
390
+
391
+ def test_emit_form_with_placeholders
392
+ d = create_driver(Fluent::Config::Element.new(
393
+ 'ROOT', '' ,
394
+ {"endpoint_url" => "${endpoint}",
395
+ "buffered" => true},
396
+ [Fluent::Config::Element.new('buffer', 'tag, endpoint', {"@type" => "memory"} ,[])]))
397
+
398
+ d.run(default_tag: 'test.metrics', shutdown: false) do
399
+ 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/" })
400
+ end
401
+
402
+ assert_equal 0, @posts.size # post into other URI
403
+ assert_equal "http://127.0.0.1:#{self.class.port}/modified-api/", d.instance.endpoint_url
404
+ end
405
+
406
+ def test_emit_form_put
407
+ d = create_driver CONFIG_PUT + %[buffered true]
408
+ d.run(default_tag: 'test.metrics', shutdown: false) do
409
+ d.feed({ 'field1' => 50 })
410
+ end
411
+
412
+ assert_equal 0, @posts.size
413
+ assert_equal 1, @puts.size
414
+ record = @puts[0]
415
+
416
+ assert_equal '50', record[:form]['field1']
417
+ assert_nil record[:auth]
418
+
419
+ d.run(default_tag: 'test.metrics', shutdown: false) do
420
+ d.feed({ 'field1' => 50 })
421
+ end
422
+
423
+ assert_equal 0, @posts.size
424
+ assert_equal 2, @puts.size
425
+ end
426
+
427
+ def test_emit_json
428
+ binary_string = "\xe3\x81\x82"
429
+ d = create_driver CONFIG_JSON + %[buffered true]
430
+ d.run(default_tag: 'test.metrics') do
431
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
432
+ end
433
+
434
+ assert_equal 1, @posts.size
435
+ record = @posts[0]
436
+
437
+ assert_equal 50, record[:json]['field1']
438
+ assert_equal 20, record[:json]['field2']
439
+ assert_equal 10, record[:json]['field3']
440
+ assert_equal 1, record[:json]['otherfield']
441
+ assert_equal binary_string, record[:json]['binary']
442
+ assert_nil record[:auth]
443
+ end
444
+
445
+ def test_emit_json_with_compression
446
+ binary_string = "\xe3\x81\x82"
447
+ d = create_driver CONFIG_JSON + %[buffered true\ncompress_request true]
448
+ d.run(default_tag: 'test.metrics') do
449
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
450
+ end
451
+
452
+ assert_equal 1, @posts.size
453
+ record = @posts[0]
454
+
455
+ assert_equal 50, record[:json]['field1']
456
+ assert_equal 20, record[:json]['field2']
457
+ assert_equal 10, record[:json]['field3']
458
+ assert_equal 1, record[:json]['otherfield']
459
+ assert_equal binary_string, record[:json]['binary']
460
+ assert_nil record[:auth]
461
+ end
462
+
463
+ def test_emit_x_ndjson
464
+ binary_string = "\xe3\x81\x82"
465
+ d = create_driver CONFIG_JSON + %[buffered true\nbulk_request]
466
+ d.run(default_tag: 'test.metrics') do
467
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
468
+ d.feed({ 'field1' => 70, 'field2' => 30, 'field3' => 20, 'otherfield' => 2, 'binary' => binary_string })
469
+ end
470
+
471
+ assert_equal 1, @posts.size
472
+ record = @posts[0]
473
+
474
+ expected =[
475
+ {
476
+ "binary" => "\u3042",
477
+ "field1" => 50,
478
+ "field2" => 20,
479
+ "field3" => 10,
480
+ "otherfield" => 1
481
+ },
482
+ {
483
+ "binary" => "\u3042",
484
+ "field1" => 70,
485
+ "field2" => 30,
486
+ "field3" => 20,
487
+ "otherfield" => 2
488
+ }
489
+ ]
490
+
491
+ assert_equal expected, record[:x_ndjson]
492
+ assert_nil record[:auth]
493
+ end
494
+
495
+ def test_emit_x_ndjson_with_compression
496
+ binary_string = "\xe3\x81\x82"
497
+ d = create_driver CONFIG_JSON + %[buffered true\nbulk_request true\ncompress_request true]
498
+ d.run(default_tag: 'test.metrics') do
499
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
500
+ d.feed({ 'field1' => 70, 'field2' => 30, 'field3' => 20, 'otherfield' => 2, 'binary' => binary_string })
501
+ end
502
+
503
+ assert_equal 1, @posts.size
504
+ record = @posts[0]
505
+
506
+ expected =[
507
+ {
508
+ "binary" => "\u3042",
509
+ "field1" => 50,
510
+ "field2" => 20,
511
+ "field3" => 10,
512
+ "otherfield" => 1
513
+ },
514
+ {
515
+ "binary" => "\u3042",
516
+ "field1" => 70,
517
+ "field2" => 30,
518
+ "field3" => 20,
519
+ "otherfield" => 2
520
+ }
521
+ ]
522
+
523
+ assert_equal expected, record[:x_ndjson]
524
+ assert_nil record[:auth]
525
+ end
526
+ end
527
+
528
+ def test_emit_form_put
529
+ d = create_driver CONFIG_PUT
530
+ d.run(default_tag: 'test.metrics') do
531
+ d.feed({ 'field1' => 50 })
532
+ end
533
+
534
+ assert_equal 0, @posts.size
535
+ assert_equal 1, @puts.size
536
+ record = @puts[0]
537
+
538
+ assert_equal '50', record[:form]['field1']
539
+ assert_nil record[:auth]
540
+
541
+ d.run(default_tag: 'test.metrics') do
542
+ d.feed({ 'field1' => 50 })
543
+ end
544
+
545
+ assert_equal 0, @posts.size
546
+ assert_equal 2, @puts.size
547
+ end
548
+
549
+ def test_emit_json
550
+ binary_string = "\xe3\x81\x82"
551
+ d = create_driver CONFIG_JSON
552
+ d.run(default_tag: 'test.metrics') do
553
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
554
+ end
555
+
556
+ assert_equal 1, @posts.size
557
+ record = @posts[0]
558
+
559
+ assert_equal 50, record[:json]['field1']
560
+ assert_equal 20, record[:json]['field2']
561
+ assert_equal 10, record[:json]['field3']
562
+ assert_equal 1, record[:json]['otherfield']
563
+ assert_equal binary_string, record[:json]['binary']
564
+ assert_nil record[:auth]
565
+ end
566
+
567
+ def test_emit_json_with_compression
568
+ binary_string = "\xe3\x81\x82"
569
+ d = create_driver CONFIG_JSON + %[compress_request true]
570
+ d.run(default_tag: 'test.metrics') do
571
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
572
+ end
573
+
574
+ assert_equal 1, @posts.size
575
+ record = @posts[0]
576
+
577
+ assert_equal 50, record[:json]['field1']
578
+ assert_equal 20, record[:json]['field2']
579
+ assert_equal 10, record[:json]['field3']
580
+ assert_equal 1, record[:json]['otherfield']
581
+ assert_equal binary_string, record[:json]['binary']
582
+ assert_nil record[:auth]
583
+ end
584
+
585
+ def test_emit_text
586
+ binary_string = "\xe3\x81\x82"
587
+ d = create_driver CONFIG_TEXT
588
+ d.run(default_tag: 'test.metrics') do
589
+ d.feed({ "message" => "hello" })
590
+ end
591
+ assert_equal 1, @posts.size
592
+ record = @posts[0]
593
+ assert_equal 'hello', record[:data]
594
+ assert_nil record[:auth]
595
+ end
596
+
597
+ def test_emit_text_with_compression
598
+ d = create_driver CONFIG_TEXT + %[compress_request true]
599
+ d.run(default_tag: 'test.metrics') do
600
+ d.feed({ "message" => "hello" })
601
+ end
602
+ assert_equal 1, @posts.size
603
+ record = @posts[0]
604
+ assert_equal 'hello', record[:data]
605
+ assert_nil record[:auth]
606
+ end
607
+
608
+ def test_emit_raw
609
+ binary_string = "\xe3\x81\x82"
610
+ d = create_driver CONFIG_RAW + %[format msgpack]
611
+ d.run(default_tag: 'test.metrics') do
612
+ d.feed({ "message" => "hello" })
613
+ end
614
+ assert_equal 1, @posts.size
615
+ record = @posts[0]
616
+ assert_equal ({ "message" => "hello" }).to_msgpack, record[:data]
617
+ assert_nil record[:auth]
618
+ end
619
+
620
+ def test_emit_raw_with_compression
621
+ binary_string = "\xe3\x81\x82"
622
+ d = create_driver CONFIG_RAW + %[format msgpack\ncompress_request true]
623
+ d.run(default_tag: 'test.metrics') do
624
+ d.feed({ "message" => "hello" })
625
+ end
626
+ assert_equal 1, @posts.size
627
+ record = @posts[0]
628
+ assert_equal ({ "message" => "hello" }).to_msgpack, record[:data].force_encoding("ascii-8bit")
629
+ assert_nil record[:auth]
630
+ end
631
+
632
+ def test_http_error_is_raised
633
+ d = create_driver CONFIG_HTTP_ERROR
634
+ assert_raise Errno::ECONNREFUSED do
635
+ d.run(default_tag: 'test.metrics') do
636
+ d.feed({ 'field1' => 50 })
637
+ end
638
+ end
639
+ end
640
+
641
+ def test_http_error_is_suppressed_with_raise_on_error_false
642
+ d = create_driver CONFIG_HTTP_ERROR_SUPPRESSED
643
+ d.run(default_tag: 'test.metrics') do
644
+ d.feed({ 'field1' => 50 })
645
+ end
646
+ # drive asserts the next output chain is called;
647
+ # so no exception means our plugin handled the error
648
+
649
+ assert_equal 0, @requests
650
+ end
651
+
652
+ def test_rate_limiting
653
+ d = create_driver CONFIG_RATE_LIMIT
654
+ record = { :k => 1 }
655
+
656
+ last_emit = _current_msec
657
+ d.run(default_tag: 'test.metrics') do
658
+ d.feed(record)
659
+ end
660
+
661
+ assert_equal 1, @posts.size
662
+
663
+ d.run(default_tag: 'test.metrics') do
664
+ d.feed({})
665
+ end
666
+ assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
667
+ assert_equal 1, @posts.size
668
+
669
+ wait_msec = 500
670
+ sleep (last_emit + RATE_LIMIT_MSEC - _current_msec + wait_msec) * 0.001
671
+
672
+ assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
673
+ d.run(default_tag: 'test.metrics') do
674
+ d.feed(record)
675
+ end
676
+ assert_equal 2, @posts.size
677
+ end
678
+
679
+ def _current_msec
680
+ Time.now.to_f * 1000
681
+ end
682
+
683
+ def test_auth
684
+ @auth = true # enable authentication of dummy server
685
+
686
+ d = create_driver(CONFIG)
687
+ d.run(default_tag: 'test.metrics') do
688
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
689
+ end # failed in background, and output warn log
690
+
691
+ assert_equal 0, @posts.size
692
+ assert_equal 1, @prohibited
693
+
694
+ d = create_driver(CONFIG + %[
695
+ authentication basic
696
+ username alice
697
+ password wrong_password
698
+ ])
699
+ d.run(default_tag: 'test.metrics') do
700
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
701
+ end # failed in background, and output warn log
702
+
703
+ assert_equal 0, @posts.size
704
+ assert_equal 2, @prohibited
705
+
706
+ d = create_driver(CONFIG + %[
707
+ authentication basic
708
+ username alice
709
+ password secret!
710
+ ])
711
+ d.run(default_tag: 'test.metrics') do
712
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
713
+ end # failed in background, and output warn log
714
+
715
+ assert_equal 1, @posts.size
716
+ assert_equal 2, @prohibited
717
+
718
+ require 'base64'
719
+ d = create_driver(CONFIG + %[
720
+ authentication bearer
721
+ token #{Base64.encode64('secret token!')}
722
+ ])
723
+ d.run(default_tag: 'test.metrics') do
724
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
725
+ end # failed in background, and output warn log
726
+
727
+ assert_equal 2, @posts.size
728
+ assert_equal 2, @prohibited
729
+
730
+ d = create_driver(CONFIG + %[
731
+ authentication jwt
732
+ token eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJIb2dlIFB1Ymxpc2hlciIsInN1YiI6IkhvZ2UgVXNlciJ9.V2NL7YgCWNt5d3vTXFrcRLpRImO2cU2JZ4mQglqw3rE
733
+ ])
734
+ d.run(default_tag: 'test.metrics') do
735
+ d.feed({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
736
+ end # failed in background, and output warn log
737
+
738
+ assert_equal 3, @posts.size
739
+ assert_equal 2, @prohibited
740
+ end
741
+
742
+ class CustomFormatterTest < self
743
+ def test_new_config
744
+ config = Fluent::Config::Element.new(
745
+ 'ROOT', '',
746
+ {"@type" => "http",
747
+ "endpoint_url" => "http://127.0.0.1:#{self.class.port}/api/",
748
+ "serializer" => "json"}, [
749
+ Fluent::Config::Element.new('format', '', {
750
+ "@type" => "test"
751
+ }, [])
752
+ ])
753
+ d = create_driver config
754
+ payload = {"field" => 1}
755
+ d.run(default_tag: 'test.metrics') do
756
+ d.feed(payload)
757
+ end
758
+
759
+ record = @posts[0]
760
+ expected = {"wrapped" => true, "record" => payload}
761
+ assert_equal expected, record[:json]
762
+ end
763
+
764
+ def test_legacy_config
765
+ config = %[
766
+ endpoint_url http://127.0.0.1:#{self.class.port}/api/
767
+ serializer json
768
+ format test
769
+ ]
770
+
771
+ d = create_driver config
772
+ payload = {"field" => 1}
773
+ d.run(default_tag: 'test.metrics') do
774
+ d.feed(payload)
775
+ end
776
+
777
+ record = @posts[0]
778
+ expected = {"wrapped" => true, "record" => payload}
779
+ assert_equal expected, record[:json]
780
+ end
781
+ end
782
+ end
783
+
784
+ class HTTPSOutputTest < HTTPOutputTestBase
785
+ def self.port
786
+ 5127
787
+ end
788
+
789
+ def self.server_config
790
+ config = super
791
+ config[:SSLEnable] = true
792
+ config[:SSLCertName] = [["CN", WEBrick::Utils::getservername]]
793
+ config
794
+ end
795
+
796
+ def self.test_http_client
797
+ super(
798
+ use_ssl: true,
799
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
800
+ )
801
+ end
802
+
803
+ def test_configure
804
+ test_uri = URI.parse("https://127.0.0.1/")
805
+
806
+ ssl_config = %[
807
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
808
+ ]
809
+ d = create_driver ssl_config
810
+ expected_endpoint_url = "https://127.0.0.1:#{self.class.port}/api/"
811
+ assert_equal expected_endpoint_url, d.instance.endpoint_url
812
+ http_opts = d.instance.http_opts(test_uri)
813
+ assert_equal true, http_opts[:use_ssl]
814
+ assert_equal OpenSSL::SSL::VERIFY_PEER, http_opts[:verify_mode]
815
+
816
+ no_verify_config = %[
817
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
818
+ ssl_no_verify true
819
+ ]
820
+ d = create_driver no_verify_config
821
+ http_opts = d.instance.http_opts(test_uri)
822
+ assert_equal true, http_opts[:use_ssl]
823
+ assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
824
+
825
+ cacert_file_config = %[
826
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
827
+ ssl_no_verify true
828
+ cacert_file /tmp/ssl.cert
829
+ ]
830
+ d = create_driver cacert_file_config
831
+ FileUtils::touch '/tmp/ssl.cert'
832
+ http_opts = d.instance.http_opts(test_uri)
833
+ assert_equal true, http_opts[:use_ssl]
834
+ assert_equal OpenSSL::SSL::VERIFY_NONE, http_opts[:verify_mode]
835
+ assert_equal true, File.file?('/tmp/ssl.cert')
836
+ puts http_opts
837
+ assert_equal File.join('/tmp/ssl.cert'), http_opts[:ca_file]
838
+ end
839
+
840
+ def test_emit_form_ssl
841
+ config = %[
842
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
843
+ ssl_no_verify true
844
+ ]
845
+ d = create_driver config
846
+ d.run(default_tag: 'test.metrics') do
847
+ d.feed({ 'field1' => 50 })
848
+ end
849
+
850
+ assert_equal 1, @posts.size
851
+ record = @posts[0]
852
+
853
+ assert_equal '50', record[:form]['field1']
854
+ end
855
+
856
+ def test_emit_form_ssl_ca
857
+ config = %[
858
+ endpoint_url https://127.0.0.1:#{self.class.port}/api/
859
+ ssl_no_verify true
860
+ cacert_file /tmp/ssl.cert
861
+ ]
862
+ d = create_driver config
863
+ d.run(default_tag: 'test.metrics') do
864
+ d.feed({ 'field1' => 50 })
865
+ end
866
+
867
+ assert_equal 1, @posts.size
868
+ record = @posts[0]
869
+
870
+ assert_equal '50', record[:form]['field1']
871
+ end
872
+ end