fluent-plugin-out-kafka-rest 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +11 -0
- data/README.md +43 -0
- data/Rakefile +11 -0
- data/fluent-plugin-out-kafka-rest.gemspec +21 -0
- data/lib/fluent/plugin/out_kafka_rest.rb +170 -0
- data/lib/fluent/test/https_output_test.rb +60 -0
- data/test/plugin/test_out_https.rb +270 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4df0762c0b4f96b734f8492c8a4ae198260bc421
|
4
|
+
data.tar.gz: cc1534fdd3a60a3221d0f4259ef200917dc4cec9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db0a371008c9c3b9dbf9762570345dbcaad5d0b0446995431746b30ffe8498bef47876296d7c76bdabd1f8b66a350608cf6cdd0e7db665f4d71500355eddd417
|
7
|
+
data.tar.gz: 6595f68524f2c242bce7b7cb4e034d83385a87e99919fc099c05e7ec8cc52f24ce033a9c030b34f6ed26cb7b87d0bfb8486bf80dd0bdda6a68c4020c12576be0
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# fluent-plugin-out-kafka-rest, a plugin for [Fluentd](http://fluentd.org) (WIP)
|
2
|
+
|
3
|
+
A [fluentd][1] output plugin for sending logs to Kafka REST Proxy.
|
4
|
+
This plugin does not use the native library of Apache Kafka itself.
|
5
|
+
Please refer to Confluent's [kakfa-rest](https://github.com/confluentinc/kafka-rest)
|
6
|
+
for the detail of REST Proxy service.
|
7
|
+
|
8
|
+
## Configs
|
9
|
+
|
10
|
+
<match *>
|
11
|
+
type kafka-rest
|
12
|
+
endpoint_url https://localhost.local/api/
|
13
|
+
# use_ssl false
|
14
|
+
# include_tag false
|
15
|
+
# include_timestamp false
|
16
|
+
# http_method post
|
17
|
+
# serializer one
|
18
|
+
# rate_limit_msec 0
|
19
|
+
# authentication basic # default: nil
|
20
|
+
# username alice
|
21
|
+
# password bobpop
|
22
|
+
</match>
|
23
|
+
|
24
|
+
## ToDo
|
25
|
+
|
26
|
+
* Change tests
|
27
|
+
* Fix the function to include tags and timestamps.
|
28
|
+
We should include such information into the request body.
|
29
|
+
* Add function to submit multiple records at once.
|
30
|
+
* Try SSL via ELB
|
31
|
+
* Avro support
|
32
|
+
|
33
|
+
## Note
|
34
|
+
|
35
|
+
* Set `use_ssl` to true to use https connection
|
36
|
+
* Set `include_tag` to true to include fluentd tag in the event log as a property
|
37
|
+
* Set `include_timestamp` to true to include timestamp (UNIX time) in the event log as a property
|
38
|
+
* Set `serializer` to any to use your own serializer rule except for Kafka REST Proxy's JSON protocol
|
39
|
+
* By default, it does not verify the https server. Use VERIFY_PEER and place the cert.pem to the location specified by OpenSSL::X509::DEFAULT_CERT_FILE.
|
40
|
+
* Majority of the code are cloned from [fluent-plugin-out-https][2]
|
41
|
+
|
42
|
+
[1]: http://fluentd.org/
|
43
|
+
[2]: https://github.com/kazunori279/fluent-plugin-out-https
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "fluent-plugin-out-kafka-rest"
|
5
|
+
gem.version = "0.1.0"
|
6
|
+
gem.authors = ["dobachi"]
|
7
|
+
gem.email = ["dobachi1983oss@gmail.com"]
|
8
|
+
gem.summary = %q{A fluentd output plugin for sending logs to Kafka REST Proxy}
|
9
|
+
gem.description = gem.summary
|
10
|
+
gem.homepage = "https://github.com/dobachi/fluent-plugin-out-kafka-rest"
|
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"
|
19
|
+
gem.add_development_dependency "bundler"
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
class Fluent::KafkaRestOutput < Fluent::Output
|
2
|
+
Fluent::Plugin.register_output('kafka-rest', self)
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
require 'net/https'
|
7
|
+
require 'openssl'
|
8
|
+
require 'uri'
|
9
|
+
require 'yajl'
|
10
|
+
require 'base64'
|
11
|
+
end
|
12
|
+
|
13
|
+
# https or http
|
14
|
+
config_param :use_ssl, :bool, :default => false
|
15
|
+
|
16
|
+
# include tag
|
17
|
+
config_param :include_tag, :bool, :default => false
|
18
|
+
|
19
|
+
# include timestamp
|
20
|
+
config_param :include_timestamp, :bool, :default => false
|
21
|
+
|
22
|
+
# Endpoint URL ex. localhost.local/api/
|
23
|
+
config_param :endpoint_url, :string
|
24
|
+
|
25
|
+
# HTTP method
|
26
|
+
config_param :http_method, :string, :default => :post
|
27
|
+
|
28
|
+
# json_bin ( Should support avro in the future)
|
29
|
+
config_param :serializer, :string, :default => :json_bin
|
30
|
+
|
31
|
+
# Content-Type
|
32
|
+
config_param :content_type, :string, :default => 'application/json'
|
33
|
+
|
34
|
+
# Simple rate limiting: ignore any records within `rate_limit_msec`
|
35
|
+
# since the last one.
|
36
|
+
config_param :rate_limit_msec, :integer, :default => 0
|
37
|
+
|
38
|
+
# nil | 'none' | 'basic'
|
39
|
+
config_param :authentication, :string, :default => nil
|
40
|
+
config_param :username, :string, :default => ''
|
41
|
+
config_param :password, :string, :default => ''
|
42
|
+
|
43
|
+
def configure(conf)
|
44
|
+
super
|
45
|
+
|
46
|
+
@use_ssl = conf['use_ssl']
|
47
|
+
@include_tag = conf['include_tag']
|
48
|
+
@include_timestamp = conf['include_timestamp']
|
49
|
+
|
50
|
+
serializers = [:json_bin] # Should support :avro in the future
|
51
|
+
@serializer = if serializers.include? @serializer.intern
|
52
|
+
@serializer.intern
|
53
|
+
else
|
54
|
+
:json_bin
|
55
|
+
end
|
56
|
+
|
57
|
+
@content_type = conf['content_type']
|
58
|
+
|
59
|
+
# Kafka REST Proxy accepts only POST method at the moment
|
60
|
+
http_methods = [:post]
|
61
|
+
@http_method = if http_methods.include? @http_method.intern
|
62
|
+
@http_method.intern
|
63
|
+
else
|
64
|
+
:post
|
65
|
+
end
|
66
|
+
|
67
|
+
@auth = case @authentication
|
68
|
+
when 'basic' then :basic
|
69
|
+
else
|
70
|
+
:none
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def start
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
def shutdown
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_url(tag, time, record)
|
83
|
+
@endpoint_url
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_body(req, tag, time, record)
|
87
|
+
# TODO: Add avro support
|
88
|
+
if @include_tag
|
89
|
+
record['tag'] = tag
|
90
|
+
end
|
91
|
+
if @include_timestamp
|
92
|
+
record['timestamp'] = Time.now.to_i
|
93
|
+
end
|
94
|
+
if @serializer == :json_bin
|
95
|
+
set_json_body(req, record)
|
96
|
+
# elsif @serializer == :avro
|
97
|
+
# set_avro_body(req, record)
|
98
|
+
end
|
99
|
+
req
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_header(req, tag, time, record)
|
103
|
+
req
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_json_body(req, data)
|
107
|
+
dumped_data = Yajl.dump(data)
|
108
|
+
encoded_data = Base64.encode64(dumped_data)
|
109
|
+
req.body = Yajl.dump({ "records" => [{ "value" => encoded_data }] })
|
110
|
+
req['Content-Type'] = 'application/vnd.kafka.binary.v1+json'
|
111
|
+
end
|
112
|
+
|
113
|
+
def set_avro_body(req, data)
|
114
|
+
# TODO: Implement avro body parser
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_request(tag, time, record)
|
118
|
+
url = format_url(tag, time, record)
|
119
|
+
uri = URI.parse(url)
|
120
|
+
req = Net::HTTP.const_get(@http_method.to_s.capitalize).new(uri.path)
|
121
|
+
set_body(req, tag, time, record)
|
122
|
+
set_header(req, tag, time, record)
|
123
|
+
return req, uri
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_request(req, uri)
|
127
|
+
is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
|
128
|
+
if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
|
129
|
+
$log.info('Dropped request due to rate limiting')
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
res = nil
|
134
|
+
begin
|
135
|
+
if @auth and @auth == :basic
|
136
|
+
req.basic_auth(@username, @password)
|
137
|
+
end
|
138
|
+
@last_request_time = Time.now.to_f
|
139
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
140
|
+
https.use_ssl = @use_ssl
|
141
|
+
https.ca_file = OpenSSL::X509::DEFAULT_CERT_FILE
|
142
|
+
# https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
143
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
144
|
+
res = https.start {|http| http.request(req) }
|
145
|
+
rescue IOError, EOFError, SystemCallError
|
146
|
+
# server didn't respond
|
147
|
+
$log.warn "Net::HTTP.#{req.method.capitalize} raises exception: #{$!.class}, '#{$!.message}'"
|
148
|
+
end
|
149
|
+
unless res and res.is_a?(Net::HTTPSuccess)
|
150
|
+
res_summary = if res
|
151
|
+
"#{res.code} #{res.message} #{res.body}"
|
152
|
+
else
|
153
|
+
"res=nil"
|
154
|
+
end
|
155
|
+
$log.warn "failed to #{req.method} #{uri} (#{res_summary})"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def handle_record(tag, time, record)
|
160
|
+
req, uri = create_request(tag, time, record)
|
161
|
+
send_request(req, uri)
|
162
|
+
end
|
163
|
+
|
164
|
+
def emit(tag, es, chain)
|
165
|
+
es.each do |time, record|
|
166
|
+
handle_record(tag, time, record)
|
167
|
+
end
|
168
|
+
chain.next
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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/https'
|
45
|
+
https = Net::HTTP.new(server, port)
|
46
|
+
https.use_ssl = true
|
47
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
48
|
+
https.start {|http|
|
49
|
+
http.get(path, headers).code
|
50
|
+
}
|
51
|
+
end
|
52
|
+
def get_content(server, port, path, headers={})
|
53
|
+
require 'net/https'
|
54
|
+
https = Net::HTTP.new(server, port)
|
55
|
+
https.use_ssl = true
|
56
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
57
|
+
https.start {|http|
|
58
|
+
http.get(path, headers).body
|
59
|
+
}
|
60
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'yajl'
|
5
|
+
require 'fluent/test/https_output_test'
|
6
|
+
require 'fluent/plugin/out_kafka_rest'
|
7
|
+
require 'webrick'
|
8
|
+
require 'webrick/https'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
TEST_LISTEN_PORT = 5126
|
12
|
+
|
13
|
+
|
14
|
+
class HTTPSOutputTestBase < Test::Unit::TestCase
|
15
|
+
# setup / teardown for servers
|
16
|
+
def setup
|
17
|
+
Fluent::Test.setup
|
18
|
+
@posts = []
|
19
|
+
@puts = []
|
20
|
+
@prohibited = 0
|
21
|
+
@requests = 0
|
22
|
+
@auth = false
|
23
|
+
@dummy_server_thread = Thread.new do
|
24
|
+
srv = if ENV['VERBOSE']
|
25
|
+
WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT, SSLEnable => true, :SSLCertName => [ [ 'CN', WEBrick::Utils::getservername ] ]})
|
26
|
+
else
|
27
|
+
logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
|
28
|
+
WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT, :Logger => logger, :AccessLog => [], :SSLEnable => true, :SSLCertName => [ [ 'CN', WEBrick::Utils::getservername ] ]})
|
29
|
+
end
|
30
|
+
begin
|
31
|
+
# Kafka REST Proxy accepts only POST method at the moment
|
32
|
+
allowed_methods = %w(POST)
|
33
|
+
srv.mount_proc('/api/') { |req,res|
|
34
|
+
@requests += 1
|
35
|
+
unless allowed_methods.include? req.request_method
|
36
|
+
res.status = 405
|
37
|
+
res.body = 'request method mismatch'
|
38
|
+
next
|
39
|
+
end
|
40
|
+
if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
|
41
|
+
# ok, authorized
|
42
|
+
elsif @auth
|
43
|
+
res.status = 403
|
44
|
+
@prohibited += 1
|
45
|
+
next
|
46
|
+
else
|
47
|
+
# ok, authorization not required
|
48
|
+
end
|
49
|
+
|
50
|
+
record = {:auth => nil}
|
51
|
+
if req.content_type == 'application/vnd.kafka.binary.v1+json'
|
52
|
+
raw_content = Yajl.load(req.body)
|
53
|
+
record[:body] = raw_content["records"].map{|s| Base64.decode64(s["value"])}
|
54
|
+
else
|
55
|
+
record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
|
56
|
+
end
|
57
|
+
|
58
|
+
instance_variable_get("@#{req.request_method.downcase}s").push(record)
|
59
|
+
|
60
|
+
res.status = 200
|
61
|
+
}
|
62
|
+
srv.mount_proc('/') { |req,res|
|
63
|
+
res.status = 200
|
64
|
+
res.body = 'running'
|
65
|
+
}
|
66
|
+
srv.start
|
67
|
+
ensure
|
68
|
+
srv.shutdown
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# to wait completion of dummy server.start()
|
73
|
+
require 'thread'
|
74
|
+
cv = ConditionVariable.new
|
75
|
+
watcher = Thread.new {
|
76
|
+
connected = false
|
77
|
+
while not connected
|
78
|
+
begin
|
79
|
+
get_content('localhost', TEST_LISTEN_PORT, '/')
|
80
|
+
connected = true
|
81
|
+
rescue Errno::ECONNREFUSED
|
82
|
+
sleep 0.1
|
83
|
+
rescue StandardError => e
|
84
|
+
p e
|
85
|
+
sleep 0.1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
cv.signal
|
89
|
+
}
|
90
|
+
mutex = Mutex.new
|
91
|
+
mutex.synchronize {
|
92
|
+
cv.wait(mutex)
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_dummy_server
|
97
|
+
host = '127.0.0.1'
|
98
|
+
port = TEST_LISTEN_PORT
|
99
|
+
https = Net::HTTP.new(host, port)
|
100
|
+
https.use_ssl = true
|
101
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
102
|
+
client = https.start()
|
103
|
+
|
104
|
+
assert_equal '200', client.request_get('/').code
|
105
|
+
assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge').code
|
106
|
+
|
107
|
+
assert_equal 1, @posts.size
|
108
|
+
|
109
|
+
assert_equal '1', @posts[0][:form]['number']
|
110
|
+
assert_equal 'gauge', @posts[0][:form]['mode']
|
111
|
+
assert_nil @posts[0][:auth]
|
112
|
+
|
113
|
+
@auth = true
|
114
|
+
|
115
|
+
assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge').code
|
116
|
+
|
117
|
+
req_with_auth = lambda do |number, mode, user, pass|
|
118
|
+
url = URI.parse("https://#{host}:#{port}/api/service/metrics/pos")
|
119
|
+
req = Net::HTTP::Post.new(url.path)
|
120
|
+
req.basic_auth user, pass
|
121
|
+
req.set_form_data({'number'=>number, 'mode'=>mode})
|
122
|
+
req
|
123
|
+
end
|
124
|
+
|
125
|
+
assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
|
126
|
+
|
127
|
+
assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
|
128
|
+
|
129
|
+
assert_equal 1, @posts.size
|
130
|
+
|
131
|
+
assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
|
132
|
+
|
133
|
+
assert_equal 2, @posts.size
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def teardown
|
138
|
+
@dummy_server_thread.kill
|
139
|
+
@dummy_server_thread.join
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class HTTPSOutputTest < HTTPSOutputTestBase
|
144
|
+
CONFIG = %[
|
145
|
+
use_ssl true
|
146
|
+
endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT}/api/
|
147
|
+
]
|
148
|
+
|
149
|
+
CONFIG_JSON_BIN = %[
|
150
|
+
use_ssl true
|
151
|
+
endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT}/api/
|
152
|
+
serializer json_bin
|
153
|
+
]
|
154
|
+
|
155
|
+
CONFIG_HTTP_ERROR = %[
|
156
|
+
use_ssl true
|
157
|
+
endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT + 1}/api/
|
158
|
+
]
|
159
|
+
|
160
|
+
RATE_LIMIT_MSEC = 1200
|
161
|
+
|
162
|
+
CONFIG_RATE_LIMIT = %[
|
163
|
+
use_ssl true
|
164
|
+
endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT}/api/
|
165
|
+
rate_limit_msec #{RATE_LIMIT_MSEC}
|
166
|
+
]
|
167
|
+
|
168
|
+
def create_driver(conf=CONFIG, tag='test.metrics')
|
169
|
+
Fluent::Test::OutputTestDriver.new(Fluent::KafkaRestOutput, tag).configure(conf)
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_configure
|
173
|
+
d = create_driver
|
174
|
+
assert_equal "https://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
|
175
|
+
assert_equal :json_bin, d.instance.serializer
|
176
|
+
|
177
|
+
d = create_driver CONFIG_JSON_BIN
|
178
|
+
assert_equal "https://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
|
179
|
+
assert_equal :json_bin, d.instance.serializer
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_emit_json_bin
|
183
|
+
binary_string = "\xe3\x81\x82".force_encoding("ascii-8bit")
|
184
|
+
d = create_driver CONFIG_JSON_BIN
|
185
|
+
d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
|
186
|
+
d.run
|
187
|
+
|
188
|
+
assert_equal 1, @posts.size
|
189
|
+
record = @posts[0]
|
190
|
+
body = Yajl.load(record[:body][0])
|
191
|
+
|
192
|
+
assert_equal 50, body['field1']
|
193
|
+
assert_equal 20, body['field2']
|
194
|
+
assert_equal 10, body['field3']
|
195
|
+
assert_equal 1, body['otherfield']
|
196
|
+
assert_equal binary_string, body['binary']
|
197
|
+
assert_nil record[:auth]
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_http_error
|
201
|
+
d = create_driver CONFIG_HTTP_ERROR
|
202
|
+
d.emit({ 'field1' => 50 })
|
203
|
+
d.run
|
204
|
+
# drive asserts the next output chain is called;
|
205
|
+
# so no exception means our plugin handled the error
|
206
|
+
|
207
|
+
assert_equal 0, @requests
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_rate_limiting
|
211
|
+
d = create_driver CONFIG_RATE_LIMIT
|
212
|
+
record = { :k => 1 }
|
213
|
+
|
214
|
+
last_emit = _current_msec
|
215
|
+
d.emit(record)
|
216
|
+
d.run
|
217
|
+
|
218
|
+
assert_equal 1, @posts.size
|
219
|
+
|
220
|
+
d.emit({})
|
221
|
+
d.run
|
222
|
+
assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
|
223
|
+
assert_equal 1, @posts.size
|
224
|
+
|
225
|
+
sleep (last_emit + RATE_LIMIT_MSEC - _current_msec) * 0.001
|
226
|
+
|
227
|
+
assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
|
228
|
+
d.emit(record)
|
229
|
+
d.run
|
230
|
+
assert_equal 2, @posts.size
|
231
|
+
end
|
232
|
+
|
233
|
+
def _current_msec
|
234
|
+
Time.now.to_f * 1000
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_auth
|
238
|
+
@auth = true # enable authentication of dummy server
|
239
|
+
|
240
|
+
d = create_driver(CONFIG, 'test.metrics')
|
241
|
+
d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
|
242
|
+
d.run # failed in background, and output warn log
|
243
|
+
|
244
|
+
assert_equal 0, @posts.size
|
245
|
+
assert_equal 1, @prohibited
|
246
|
+
|
247
|
+
d = create_driver(CONFIG + %[
|
248
|
+
authentication basic
|
249
|
+
username alice
|
250
|
+
password wrong_password
|
251
|
+
], 'test.metrics')
|
252
|
+
d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
|
253
|
+
d.run # failed in background, and output warn log
|
254
|
+
|
255
|
+
assert_equal 0, @posts.size
|
256
|
+
assert_equal 2, @prohibited
|
257
|
+
|
258
|
+
d = create_driver(CONFIG + %[
|
259
|
+
authentication basic
|
260
|
+
username alice
|
261
|
+
password secret!
|
262
|
+
], 'test.metrics')
|
263
|
+
d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
|
264
|
+
d.run # failed in background, and output warn log
|
265
|
+
|
266
|
+
assert_equal 1, @posts.size
|
267
|
+
assert_equal 2, @prohibited
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-out-kafka-rest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- dobachi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-13 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
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.10.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A fluentd output plugin for sending logs to Kafka REST Proxy
|
70
|
+
email:
|
71
|
+
- dobachi1983oss@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- CHANGELOG.md
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- fluent-plugin-out-kafka-rest.gemspec
|
83
|
+
- lib/fluent/plugin/out_kafka_rest.rb
|
84
|
+
- lib/fluent/test/https_output_test.rb
|
85
|
+
- test/plugin/test_out_https.rb
|
86
|
+
homepage: https://github.com/dobachi/fluent-plugin-out-kafka-rest
|
87
|
+
licenses: []
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.2.5
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A fluentd output plugin for sending logs to Kafka REST Proxy
|
109
|
+
test_files:
|
110
|
+
- test/plugin/test_out_https.rb
|