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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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