fluent-plugin-out-http-ext-ignore-ssl-errors 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44998e4900ae688e02a1c43658ff89405b0c4c67
4
+ data.tar.gz: 9f27da2e6130e79d1323563fe5ac1ab3e23e8361
5
+ SHA512:
6
+ metadata.gz: 6c4bd94668ce17d69136af53c59c2ec54283f3ceb44d849d1bc0093e72189352bcd531f0e77194b5aee34ad255e9c7cdfa16274735052d5afa1ff41f52ce8aff
7
+ data.tar.gz: fcc40c34c6115f4cbd9548c2cc1ab0235a09797ef097eedb999dd1a825df0fa1380866541df5686611d3fda1adcb78b9e9b001ee03d14266207aad493e58e6e5
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: 9tG18WMwC1xpPdDBEGznon01RCp9NusHL
@@ -0,0 +1,9 @@
1
+ *~
2
+ \#*
3
+ .\#*
4
+ *.gem
5
+ .bundle
6
+ .ruby-version
7
+ Gemfile.lock
8
+ vendor
9
+ coverage/
@@ -0,0 +1,18 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1
5
+ - 2.2
6
+ - ruby-head
7
+
8
+ os:
9
+ - linux
10
+
11
+ gemfile:
12
+ - Gemfile
13
+
14
+ script: bundle exec rake test
15
+
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: ruby-head
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.1.5
4
+ * Add headers directive
5
+ * Add use_ssl directive
6
+
7
+ ## 0.1.4
8
+ * #11 Updated Fluentd dependency to: [">= 0.10.0", "< 2"]
9
+ * #10 `password` is now marked as a [secret option](https://github.com/fluent/fluentd/pull/604)
10
+
11
+ ## 0.1.3
12
+ * Added a new configuration option: `raise_on_error` (default: true)
13
+ * In order to let the plugin raise exceptions like it did in 0.1.1: keep using your configuration as-is
14
+ * In order to suppress all exceptions: add `raise_on_error false` to your configuration
15
+
16
+ ## 0.1.2
17
+ * #6 Catch all `StandardError`s during HTTP request to prevent td-agent from freezing
18
+
19
+ ## 0.1.1
20
+ * #2 Use yajl instead of json as json serializer
21
+ * #1 Fix a bug where a nil HTTP response caused the plugin to stop working
22
+
23
+ ## 0.1.0
24
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-out-http.gemspec
4
+ gemspec
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
@@ -0,0 +1,43 @@
1
+ # fluent-plugin-out-http-ext, a plugin for [Fluentd](http://fluentd.org)
2
+
3
+ [![Build Status](https://travis-ci.org/kawasakitoshiya/fluent-plugin-out-http-ext.svg)](https://travis-ci.org/kawasakitoshiya/fluent-plugin-out-http-ext)
4
+ [![Coverage Status](https://coveralls.io/repos/kawasakitoshiya/fluent-plugin-out-http-ext/badge.svg?branch=master&service=github)](https://coveralls.io/github/kawasakitoshiya/fluent-plugin-out-http-ext?branch=master)
5
+
6
+ **This is a fork of [ento / fluent-plugin-out-http](https://github.com/ento/fluent-plugin-out-http)**
7
+
8
+ A generic [fluentd][1] output plugin for sending logs to an HTTP endpoint
9
+
10
+ ## Configuration options
11
+
12
+ <match *>
13
+ type http_ext
14
+ endpoint_url http://localhost.local/api/<data.id> # <data.id> refres to data.id in the record like {"data"=> {"id"=> 1, "name"=> "foo"}}
15
+ http_method put # default: post
16
+ serializer json # default: form
17
+ rate_limit_msec 100 # default: 0 = no rate limiting
18
+ open_timeout 5 # default: nil = no timeout
19
+ read_timeout 10 # default: 60
20
+ raise_on_error false # default: true
21
+ raise_on_http_failure true # default: false
22
+ ignore_http_status_code 300,400..499 # default: nil # do not raise on these http_hstatus codes
23
+ authentication basic # default: none
24
+ username alice # default: ''
25
+ password bobpop # default: '', secret: true
26
+ use_ssl true # default: false
27
+ <headers>
28
+ HeaderExample1 header1
29
+ HeaderExample2 header2
30
+ </headers>
31
+ </match>
32
+
33
+ ## Usage notes
34
+
35
+ If you'd like to retry failed requests, consider using [fluent-plugin-bufferize][3].
36
+
37
+ ----
38
+
39
+ Heavily based on [fluent-plugin-growthforecast][2]
40
+
41
+ [1]: http://fluentd.org/
42
+ [2]: https://github.com/tagomoris/fluent-plugin-growthforecast
43
+ [3]: https://github.com/sabottenda/fluent-plugin-bufferize
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "fluent-plugin-out-http-ext-ignore-ssl-errors"
5
+ gem.version = "0.0.1"
6
+ gem.authors = ["Joyce Chan"]
7
+ gem.email = ["none@none.com"]
8
+ gem.summary = %q{A generic Fluentd output plugin to send logs to an HTTP endpoint with SSL and Header option, extended from kawasakitoshiya@gmail.com's similarily named gem'}
9
+ gem.description = gem.summary
10
+ gem.licenses = ["Apache-2.0"]
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ["lib"]
16
+
17
+ gem.add_runtime_dependency "yajl-ruby", "~> 1.0"
18
+ gem.add_runtime_dependency "fluentd", [">= 0.10.0", "< 2"]
19
+ gem.add_development_dependency "bundler"
20
+ gem.add_development_dependency "rake"
21
+ gem.add_development_dependency "test-unit", ">= 3.1.0"
22
+ gem.add_development_dependency "coveralls"
23
+ end
@@ -0,0 +1,254 @@
1
+ require 'set'
2
+
3
+ class Array
4
+ def to_set
5
+ Set.new(self)
6
+ end
7
+ end
8
+
9
+
10
+ class Hash
11
+ """
12
+ each traverse in hash
13
+ """
14
+ def each_deep(&proc)
15
+ self.each_deep_detail([], &proc)
16
+ end
17
+
18
+ def each_deep_detail(directory, &proc)
19
+ self.each do |k, v|
20
+ current = directory + [k]
21
+ if v.kind_of?(Hash)
22
+ v.each_deep_detail(current, &proc)
23
+ else
24
+ yield(current, v)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ class StatusCodeParser
32
+ """
33
+ parse status code string to array of codes
34
+ """
35
+ def self.range?(str)
36
+ # i.e. 200..399 => return true
37
+ return /^\d{3}..\d{3}$/ =~ str ? true : false
38
+ end
39
+
40
+ def self.number?(str)
41
+ return /^\d{3}$/ =~ str ? true : false
42
+ end
43
+
44
+ def self.get_array(str)
45
+ if self.range?(str)
46
+ ends = str.split('..').map{|d| Integer(d)}
47
+ return (ends[0]..ends[1]).to_a
48
+ elsif self.number?(str)
49
+ return [str.to_i]
50
+ else
51
+ raise "invalid status code range format"
52
+ end
53
+ end
54
+
55
+ def self.convert(range_str)
56
+ elems = range_str.split(',')
57
+ status_codes = elems.flat_map do |elem|
58
+ self.get_array(elem)
59
+ end
60
+ return status_codes.to_set
61
+ end
62
+ end
63
+
64
+ class Fluent::HTTPOutput < Fluent::Output
65
+ Fluent::Plugin.register_output('http_ext', self)
66
+
67
+ def initialize
68
+ super
69
+ require 'net/http'
70
+ require 'uri'
71
+ require 'yajl'
72
+ require 'set'
73
+ end
74
+
75
+ # Endpoint URL ex. localhost.local/api/
76
+ config_param :endpoint_url, :string
77
+
78
+ # HTTP method
79
+ config_param :http_method, :string, :default => :post
80
+
81
+ # form | json
82
+ config_param :serializer, :string, :default => :form
83
+
84
+ # true | false
85
+ config_param :use_ssl, :bool, :default => false
86
+
87
+ config_param :open_timeout, :integer, :default => nil
88
+ config_param :read_timeout, :integer, :default => 60
89
+
90
+ # Simple rate limiting: ignore any records within `rate_limit_msec`
91
+ # since the last one.
92
+ config_param :rate_limit_msec, :integer, :default => 0
93
+
94
+ # Raise errors that were rescued during HTTP requests?
95
+ config_param :raise_on_error, :bool, :default => true
96
+
97
+ # Raise errors when HTTP response code was not successful.
98
+ config_param :raise_on_http_failure, :bool, :default => false
99
+ config_param :ignore_http_status_code, :string, :default => nil
100
+ # nil | 'none' | 'basic'
101
+ config_param :authentication, :string, :default => nil
102
+ config_param :username, :string, :default => ''
103
+ config_param :password, :string, :default => '', :secret => true
104
+
105
+ def configure(conf)
106
+ super
107
+
108
+ serializers = [:json, :form]
109
+ @serializer = if serializers.include? @serializer.intern
110
+ @serializer.intern
111
+ else
112
+ :form
113
+ end
114
+
115
+ http_methods = [:get, :put, :post, :delete]
116
+ @http_method = if http_methods.include? @http_method.intern
117
+ @http_method.intern
118
+ else
119
+ :post
120
+ end
121
+
122
+ @ignore_http_status_code = if @ignore_http_status_code.nil?
123
+ [].to_set
124
+ else
125
+ StatusCodeParser.convert(@ignore_http_status_code)
126
+ end
127
+
128
+ @auth = case @authentication
129
+ when 'basic' then :basic
130
+ else
131
+ :none
132
+ end
133
+ @headers = {}
134
+ conf.elements.each do |element|
135
+ if element.name == 'headers'
136
+ @headers = element.to_hash
137
+ end
138
+ end
139
+ end
140
+
141
+ def start
142
+ super
143
+ end
144
+
145
+ def shutdown
146
+ super
147
+ end
148
+
149
+ def format_url(tag, time, record)
150
+ '''
151
+ replace format string to value
152
+ example
153
+ /test/<data> =(use {data: 1})> /test/1
154
+ /test/<hash.data> =(use {hash:{data:2}})> /test/2
155
+ '''
156
+ result_url = @endpoint_url
157
+ record.each_deep do |key_dir, value|
158
+ result_url = result_url.gsub(/<#{key_dir.join(".")}>/, value.to_s)
159
+ end
160
+ return result_url
161
+ end
162
+
163
+ def set_body(req, tag, time, record)
164
+ if @serializer == :json
165
+ set_json_body(req, record)
166
+ else
167
+ req.set_form_data(record)
168
+ end
169
+ req
170
+ end
171
+
172
+ def set_header(req, tag, time, record)
173
+ @headers.each do |key, value|
174
+ req[key] = value
175
+ end
176
+ req
177
+ end
178
+
179
+ def set_json_body(req, data)
180
+ req.body = Yajl.dump(data)
181
+ req['Content-Type'] = 'application/json'
182
+ end
183
+
184
+ def create_request(tag, time, record)
185
+ url = format_url(tag, time, record)
186
+ uri = URI.parse(url)
187
+ req = Net::HTTP.const_get(@http_method.to_s.capitalize).new(uri.path)
188
+ set_body(req, tag, time, record)
189
+ set_header(req, tag, time, record)
190
+ return req, uri
191
+ end
192
+
193
+ def send_request(req, uri)
194
+ is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
195
+ if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
196
+ $log.info('Dropped request due to rate limiting')
197
+ return
198
+ end
199
+
200
+ res = nil
201
+
202
+ begin
203
+ if @auth and @auth == :basic
204
+ req.basic_auth(@username, @password)
205
+ end
206
+ @last_request_time = Time.now.to_f
207
+ client = Net::HTTP.new(uri.host, uri.port)
208
+ if @use_ssl
209
+ client.use_ssl = true
210
+ client.ca_file = OpenSSL::X509::DEFAULT_CERT_FILE
211
+ client.verify_mode = OpenSSL::SSL::VERIFY_NONE
212
+ end
213
+ res = client.start {|http|
214
+ http.open_timeout = @open_timeout
215
+ http.read_timeout = @read_timeout
216
+ http.request(req)
217
+ }
218
+ rescue => e # rescue all StandardErrors
219
+ # server didn't respond
220
+ $log.warn "Net::HTTP.#{req.method.capitalize} raises exception: #{e.class}, '#{e.message}'"
221
+ raise e if @raise_on_error
222
+ else
223
+ unless res and res.is_a?(Net::HTTPSuccess)
224
+ res_summary = if res
225
+ "#{res.code} #{res.message} #{res.body}"
226
+ else
227
+ "res=nil"
228
+ end
229
+ warning = "failed to #{req.method} #{uri} (#{res_summary})"
230
+ $log.warn warning
231
+ if @raise_on_http_failure
232
+ unless @ignore_http_status_code.include?(res.code.to_i)
233
+ raise warning
234
+ else
235
+ $log.debug "ignore http status code #{req.method}"
236
+ end
237
+ end
238
+
239
+ end #end unless
240
+ end # end begin
241
+ end # end send_request
242
+
243
+ def handle_record(tag, time, record)
244
+ req, uri = create_request(tag, time, record)
245
+ send_request(req, uri)
246
+ end
247
+
248
+ def emit(tag, es, chain)
249
+ es.each do |time, record|
250
+ handle_record(tag, time, record)
251
+ end
252
+ chain.next
253
+ end
254
+ end
@@ -0,0 +1,54 @@
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
+
34
+ # to handle POST/PUT/DELETE ...
35
+ module WEBrick::HTTPServlet
36
+ class ProcHandler < AbstractServlet
37
+ alias do_POST do_GET
38
+ alias do_PUT do_GET
39
+ alias do_DELETE do_GET
40
+ end
41
+ end
42
+
43
+ def get_code(server, port, path, headers={})
44
+ require 'net/http'
45
+ Net::HTTP.start(server, port){|http|
46
+ http.get(path, headers).code
47
+ }
48
+ end
49
+ def get_content(server, port, path, headers={})
50
+ require 'net/http'
51
+ Net::HTTP.start(server, port){|http|
52
+ http.get(path, headers).body
53
+ }
54
+ end
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'coveralls'
3
+ Coveralls.wear!
@@ -0,0 +1,511 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'uri'
3
+ require 'yajl'
4
+ require 'fluent/test/http_output_test'
5
+ require 'fluent/plugin/out_http_ext'
6
+
7
+
8
+ TEST_LISTEN_PORT = 5126
9
+
10
+
11
+ class HTTPOutputTestBase < Test::Unit::TestCase
12
+ # setup / teardown for servers
13
+ def setup
14
+ Fluent::Test.setup
15
+ @posts = []
16
+ @puts = []
17
+ @prohibited = 0
18
+ @requests = 0
19
+ @auth = false
20
+ @status = 200
21
+ @dummy_server_thread = Thread.new do
22
+ srv = if ENV['VERBOSE']
23
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT})
24
+ else
25
+ logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
26
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT, :Logger => logger, :AccessLog => []})
27
+ end
28
+ begin
29
+ allowed_methods = %w(POST PUT)
30
+ srv.mount_proc('/api/') { |req,res|
31
+ @requests += 1
32
+ unless allowed_methods.include? req.request_method
33
+ res.status = 405
34
+ res.body = 'request method mismatch'
35
+ next
36
+ end
37
+ if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
38
+ # ok, authorized
39
+ elsif @auth
40
+ res.status = 403
41
+ @prohibited += 1
42
+ next
43
+ else
44
+ # ok, authorization not required
45
+ end
46
+
47
+ record = {:auth => nil}
48
+ if req.content_type == 'application/json'
49
+ record[:json] = Yajl.load(req.body)
50
+ else
51
+ record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
52
+ end
53
+
54
+ instance_variable_get("@#{req.request_method.downcase}s").push(record)
55
+
56
+ res.status = @status
57
+ }
58
+ srv.mount_proc('/') { |req,res|
59
+ res.status = 200
60
+ res.body = 'running'
61
+ }
62
+ srv.mount_proc('/slow_5') { |req,res|
63
+ sleep 5
64
+ res.status = 200
65
+ res.body = 'slow_5'
66
+ }
67
+ srv.mount_proc('/slow_10') { |req,res|
68
+ sleep 10
69
+ res.status = 200
70
+ res.body = 'slow_10'
71
+ }
72
+ srv.mount_proc('/status_code') { |req,res|
73
+ r = Yajl.load(req.body)
74
+ code = r["code"]
75
+ res.status = code.to_s
76
+ res.body = ''
77
+ }
78
+
79
+ srv.start
80
+ ensure
81
+ srv.shutdown
82
+ end
83
+ end
84
+
85
+ # to wait completion of dummy server.start()
86
+ require 'thread'
87
+ cv = ConditionVariable.new
88
+ watcher = Thread.new {
89
+ connected = false
90
+ while not connected
91
+ begin
92
+ get_content('localhost', TEST_LISTEN_PORT, '/')
93
+ connected = true
94
+ rescue Errno::ECONNREFUSED
95
+ sleep 0.1
96
+ rescue StandardError => e
97
+ p e
98
+ sleep 0.1
99
+ end
100
+ end
101
+ cv.signal
102
+ }
103
+ mutex = Mutex.new
104
+ mutex.synchronize {
105
+ cv.wait(mutex)
106
+ }
107
+ end
108
+
109
+ def test_dummy_server
110
+ host = '127.0.0.1'
111
+ port = TEST_LISTEN_PORT
112
+ client = Net::HTTP.start(host, port)
113
+
114
+ assert_equal '200', client.request_get('/').code
115
+ assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge').code
116
+
117
+ assert_equal 1, @posts.size
118
+
119
+ assert_equal '1', @posts[0][:form]['number']
120
+ assert_equal 'gauge', @posts[0][:form]['mode']
121
+ assert_nil @posts[0][:auth]
122
+
123
+ @auth = true
124
+
125
+ assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge').code
126
+
127
+ req_with_auth = lambda do |number, mode, user, pass|
128
+ url = URI.parse("http://#{host}:#{port}/api/service/metrics/pos")
129
+ req = Net::HTTP::Post.new(url.path)
130
+ req.basic_auth user, pass
131
+ req.set_form_data({'number'=>number, 'mode'=>mode})
132
+ req
133
+ end
134
+
135
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
136
+
137
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
138
+
139
+ assert_equal 1, @posts.size
140
+
141
+ assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
142
+
143
+ assert_equal 2, @posts.size
144
+
145
+ end
146
+
147
+ def teardown
148
+ @dummy_server_thread.kill
149
+ @dummy_server_thread.join
150
+ end
151
+ end
152
+
153
+ class HTTPOutputTest < HTTPOutputTestBase
154
+ CONFIG = %[
155
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
156
+ ]
157
+
158
+ CONFIG_JSON = %[
159
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
160
+ serializer json
161
+ ]
162
+
163
+ CONFIG_PUT = %[
164
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
165
+ http_method put
166
+ ]
167
+
168
+ CONFIG_HTTP_ERROR = %[
169
+ endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT + 1}/api/
170
+ ]
171
+
172
+ CONFIG_HTTP_ERROR_SUPPRESSED = %[
173
+ endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT + 1}/api/
174
+ raise_on_error false
175
+ ]
176
+
177
+ CONFIG_RAISE_ON_HTTP_FAILURE = %[
178
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
179
+ raise_on_http_failure true
180
+ ]
181
+
182
+ RATE_LIMIT_MSEC = 1200
183
+
184
+ CONFIG_RATE_LIMIT = %[
185
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
186
+ rate_limit_msec #{RATE_LIMIT_MSEC}
187
+ ]
188
+
189
+ CONFIG_NOT_READ_TIMEOUT = %[
190
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/slow_5/
191
+ read_timeout 7
192
+ ]
193
+ CONFIG_READ_TIMEOUT = %[
194
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/slow_10/
195
+ read_timeout 7
196
+ ]
197
+ CONFIG_IGNORE_NONE = %[
198
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/status_code/
199
+ serializer json
200
+ raise_on_http_failure true
201
+ ]
202
+ CONFIG_IGNORE_409 = %[
203
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/status_code/
204
+ serializer json
205
+ raise_on_http_failure true
206
+ ignore_http_status_code 409
207
+ ]
208
+ CONFIG_IGNORE_4XX = %[
209
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/status_code/
210
+ serializer json
211
+ raise_on_http_failure true
212
+ ignore_http_status_code 400..499
213
+ ]
214
+ CONFIG_IGNORE_4XX_5XX = %[
215
+ endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/status_code/
216
+ serializer json
217
+ raise_on_http_failure true
218
+ ignore_http_status_code 400..599
219
+ ]
220
+
221
+ def create_driver(conf=CONFIG, tag='test.metrics')
222
+ Fluent::Test::OutputTestDriver.new(Fluent::HTTPOutput, tag).configure(conf)
223
+ end
224
+
225
+ def test_configure
226
+ d = create_driver
227
+ assert_equal "http://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
228
+ assert_equal :form, d.instance.serializer
229
+
230
+ d = create_driver CONFIG_JSON
231
+ assert_equal "http://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
232
+ assert_equal :json, d.instance.serializer
233
+ end
234
+
235
+ def test_emit_form
236
+ d = create_driver
237
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
238
+ d.run
239
+
240
+ assert_equal 1, @posts.size
241
+ record = @posts[0]
242
+
243
+ assert_equal '50', record[:form]['field1']
244
+ assert_equal '20', record[:form]['field2']
245
+ assert_equal '10', record[:form]['field3']
246
+ assert_equal '1', record[:form]['otherfield']
247
+ assert_nil record[:auth]
248
+
249
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
250
+ d.run
251
+
252
+ assert_equal 2, @posts.size
253
+ end
254
+
255
+ def test_emit_form_put
256
+ d = create_driver CONFIG_PUT
257
+ d.emit({ 'field1' => 50 })
258
+ d.run
259
+
260
+ assert_equal 0, @posts.size
261
+ assert_equal 1, @puts.size
262
+ record = @puts[0]
263
+
264
+ assert_equal '50', record[:form]['field1']
265
+ assert_nil record[:auth]
266
+
267
+ d.emit({ 'field1' => 50 })
268
+ d.run
269
+
270
+ assert_equal 0, @posts.size
271
+ assert_equal 2, @puts.size
272
+ end
273
+
274
+ def test_emit_json
275
+ binary_string = "\xe3\x81\x82".force_encoding("ascii-8bit")
276
+ d = create_driver CONFIG_JSON
277
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
278
+ d.run
279
+
280
+ assert_equal 1, @posts.size
281
+ record = @posts[0]
282
+
283
+ assert_equal 50, record[:json]['field1']
284
+ assert_equal 20, record[:json]['field2']
285
+ assert_equal 10, record[:json]['field3']
286
+ assert_equal 1, record[:json]['otherfield']
287
+ assert_equal binary_string, record[:json]['binary']
288
+ assert_nil record[:auth]
289
+ end
290
+
291
+ def test_http_error_is_raised
292
+ d = create_driver CONFIG_HTTP_ERROR
293
+ assert_raise Errno::ECONNREFUSED do
294
+ d.emit({ 'field1' => 50 })
295
+ end
296
+ end
297
+
298
+ def test_http_error_is_suppressed_with_raise_on_error_false
299
+ d = create_driver CONFIG_HTTP_ERROR_SUPPRESSED
300
+ d.emit({ 'field1' => 50 })
301
+ d.run
302
+ # drive asserts the next output chain is called;
303
+ # so no exception means our plugin handled the error
304
+
305
+ assert_equal 0, @requests
306
+ end
307
+
308
+ def test_http_failure_is_not_raised_on_http_failure_true_and_status_201
309
+ @status = 201
310
+
311
+ d = create_driver CONFIG_RAISE_ON_HTTP_FAILURE
312
+ assert_nothing_raised do
313
+ d.emit({ 'field1' => 50 })
314
+ end
315
+
316
+ @status = 200
317
+ end
318
+
319
+ def test_http_failure_is_raised_on_http_failure_true
320
+ @status = 500
321
+
322
+ d = create_driver CONFIG_RAISE_ON_HTTP_FAILURE
323
+ assert_raise RuntimeError do
324
+ d.emit({ 'field1' => 50 })
325
+ end
326
+
327
+ @status = 200
328
+ end
329
+
330
+ def test_rate_limiting
331
+ d = create_driver CONFIG_RATE_LIMIT
332
+ record = { :k => 1 }
333
+
334
+ last_emit = _current_msec
335
+ d.emit(record)
336
+ d.run
337
+
338
+ assert_equal 1, @posts.size
339
+
340
+ d.emit({})
341
+ d.run
342
+ assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
343
+ assert_equal 1, @posts.size
344
+
345
+ wait_msec = 500
346
+ sleep (last_emit + RATE_LIMIT_MSEC - _current_msec + wait_msec) * 0.001
347
+
348
+ assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
349
+ d.emit(record)
350
+ d.run
351
+ assert_equal 2, @posts.size
352
+ end
353
+
354
+ def test_read_timeout
355
+ d = create_driver CONFIG_READ_TIMEOUT
356
+ assert_equal 7, d.instance.read_timeout
357
+ err = Net.const_defined?(:ReadTimeout) ? Net::ReadTimeout : Timeout::Error
358
+ assert_raise err do
359
+ d.emit({})
360
+ d.run
361
+ end
362
+ end
363
+
364
+ def test_not_read_timeout
365
+ d = create_driver CONFIG_NOT_READ_TIMEOUT
366
+ assert_equal 7, d.instance.read_timeout
367
+ assert_nothing_raised do
368
+ d.emit({})
369
+ d.run
370
+ end
371
+ end
372
+
373
+ def test_ignore_none
374
+ d = create_driver CONFIG_IGNORE_NONE
375
+ assert_equal [].to_set, d.instance.ignore_http_status_code
376
+
377
+ assert_raise do
378
+ d.emit({:code=> 409})
379
+ d.run
380
+ end
381
+
382
+ assert_raise do
383
+ d.emit({:code => 500})
384
+ d.run
385
+ end
386
+ end
387
+
388
+ def test_ignore_409
389
+ d = create_driver CONFIG_IGNORE_409
390
+ assert_equal [409].to_set, d.instance.ignore_http_status_code
391
+
392
+ assert_nothing_raised do
393
+ d.emit({:code => 409})
394
+ d.run
395
+ end
396
+ assert_raise do
397
+ d.emit({:code => 404})
398
+ d.run
399
+ end
400
+ assert_raise do
401
+ d.emit({:code => 500})
402
+ d.run
403
+ end
404
+ end
405
+
406
+ def test_ignore_4XX
407
+ d = create_driver CONFIG_IGNORE_4XX
408
+ assert_equal (400..499).to_a.to_set, d.instance.ignore_http_status_code
409
+
410
+ assert_nothing_raised do
411
+ d.emit({:code => 409})
412
+ d.run
413
+ end
414
+ assert_nothing_raised do
415
+ d.emit({:code => 404})
416
+ d.run
417
+ end
418
+ assert_raise do
419
+ d.emit({:code => 500})
420
+ d.run
421
+ end
422
+ end
423
+
424
+ def test_ignore_4XX_5XX
425
+ d = create_driver CONFIG_IGNORE_4XX_5XX
426
+ assert_equal (400..599).to_a.to_set, d.instance.ignore_http_status_code
427
+ assert_nothing_raised do
428
+ d.emit({:code => 409})
429
+ d.run
430
+ end
431
+ assert_nothing_raised do
432
+ d.emit({:code => 404})
433
+ d.run
434
+ end
435
+ assert_nothing_raised do
436
+ d.emit({:code => 500})
437
+ d.run
438
+ end
439
+ end
440
+
441
+ def _current_msec
442
+ Time.now.to_f * 1000
443
+ end
444
+
445
+ def test_auth
446
+ @auth = true # enable authentication of dummy server
447
+
448
+ d = create_driver(CONFIG, 'test.metrics')
449
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
450
+ d.run # failed in background, and output warn log
451
+
452
+ assert_equal 0, @posts.size
453
+ assert_equal 1, @prohibited
454
+
455
+ d = create_driver(CONFIG + %[
456
+ authentication basic
457
+ username alice
458
+ password wrong_password
459
+ ], 'test.metrics')
460
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
461
+ d.run # failed in background, and output warn log
462
+
463
+ assert_equal 0, @posts.size
464
+ assert_equal 2, @prohibited
465
+
466
+ d = create_driver(CONFIG + %[
467
+ authentication basic
468
+ username alice
469
+ password secret!
470
+ ], 'test.metrics')
471
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
472
+ d.run # failed in background, and output warn log
473
+
474
+ assert_equal 1, @posts.size
475
+ assert_equal 2, @prohibited
476
+ end
477
+
478
+ def test_status_code_parser()
479
+ assert_equal (400..409).to_a.to_set, StatusCodeParser.convert("400..409")
480
+ assert_equal ((400..409).to_a + [300]).to_set, StatusCodeParser.convert("400..409,300")
481
+ assert_equal ((400..409).to_a + [300]).to_set, StatusCodeParser.convert("300,400..409")
482
+ assert_equal [404, 409].to_set, StatusCodeParser.convert("404,409")
483
+ assert_equal [404, 409, 300, 301, 302, 303].to_set, StatusCodeParser.convert("404,409,300..303")
484
+ assert_equal [409].to_set, StatusCodeParser.convert("409")
485
+ assert_equal [].to_set, StatusCodeParser.convert("")
486
+ assert_raise do
487
+ StatusCodeParser.convert("400...499")
488
+ end
489
+ assert_raise do
490
+ StatusCodeParser.convert("10..20")
491
+ end
492
+ assert_raise do
493
+ StatusCodeParser.convert("4XX")
494
+ end
495
+ assert_raise do
496
+ StatusCodeParser.convert("4XX..5XX")
497
+ end
498
+ assert_raise do
499
+ StatusCodeParser.convert("200.0..400")
500
+ end
501
+ assert_raise do
502
+ StatusCodeParser.convert("-200..400")
503
+ end
504
+
505
+ end
506
+
507
+ def test_array_extend
508
+ assert_equal [].to_set, Set.new([])
509
+ assert_equal [1, 2].to_set, Set.new([1, 2])
510
+ end
511
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-out-http-ext-ignore-ssl-errors
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joyce Chan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: yajl-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fluentd
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.0
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.10.0
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: test-unit
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 3.1.0
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 3.1.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: coveralls
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description: A generic Fluentd output plugin to send logs to an HTTP endpoint with
104
+ SSL and Header option, extended from kawasakitoshiya@gmail.com's similarily named
105
+ gem'
106
+ email:
107
+ - none@none.com
108
+ executables: []
109
+ extensions: []
110
+ extra_rdoc_files: []
111
+ files:
112
+ - ".coveralls.yml"
113
+ - ".gitignore"
114
+ - ".travis.yml"
115
+ - CHANGELOG.md
116
+ - Gemfile
117
+ - LICENSE.txt
118
+ - README.md
119
+ - Rakefile
120
+ - fluent-plugin-out-http-ext.gemspec
121
+ - lib/fluent/plugin/out_http_ext.rb
122
+ - lib/fluent/test/http_output_test.rb
123
+ - test/plugin/test_helper.rb
124
+ - test/plugin/test_out_http_ext.rb
125
+ homepage:
126
+ licenses:
127
+ - Apache-2.0
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.5.1
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: A generic Fluentd output plugin to send logs to an HTTP endpoint with SSL
149
+ and Header option, extended from kawasakitoshiya@gmail.com's similarily named gem'
150
+ test_files:
151
+ - test/plugin/test_helper.rb
152
+ - test/plugin/test_out_http_ext.rb