fluent-plugin-sixpack 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/.document +5 -0
- data/.gitignore +52 -0
- data/.travis.yml +4 -0
- data/AUTHORS +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +119 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/fluent-plugin-sixpack.gemspec +22 -0
- data/lib/fluent/plugin/out_sixpack.rb +250 -0
- data/test/helper.rb +52 -0
- data/test/plugin/test_out_growthforecast.rb +273 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7de3147cdbfecd0b93439d12c370b6db923dcbcc
|
4
|
+
data.tar.gz: cab2e034439051a97ce950949801cf70b3a159f3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 68cdd62f01db47a775f91ef2ed17e8b1137e05933aa8bbaa55d9934c85963fed5ccb73b171db2ca733d067dccbac78f6dea20d3b09f0623d1bbff00df95eab4c
|
7
|
+
data.tar.gz: 89f4a0ef7f4f3271f5aca1e8d82df38dc6d0f7b882e8d5222ad4c6ec982c325d766a153d43db3975a2270cbb0e02e49214a5849f1624086f239fe0bc0b3dff31
|
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
18
|
+
#
|
19
|
+
# * Create a file at ~/.gitignore
|
20
|
+
# * Include files you want ignored
|
21
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
22
|
+
#
|
23
|
+
# After doing this, these files will be ignored in all your git projects,
|
24
|
+
# saving you from having to 'pollute' every project you touch with them
|
25
|
+
#
|
26
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
27
|
+
#
|
28
|
+
# For MacOS:
|
29
|
+
#
|
30
|
+
.DS_Store
|
31
|
+
|
32
|
+
# For TextMate
|
33
|
+
*.tmproj
|
34
|
+
tmtags
|
35
|
+
|
36
|
+
# For emacs:
|
37
|
+
*~
|
38
|
+
\#*
|
39
|
+
.\#*
|
40
|
+
|
41
|
+
# For vim:
|
42
|
+
*.swp
|
43
|
+
|
44
|
+
# For redcar:
|
45
|
+
#.redcar
|
46
|
+
|
47
|
+
# For rubinius:
|
48
|
+
#*.rbc
|
49
|
+
|
50
|
+
Gemfile.lock
|
51
|
+
vendor
|
52
|
+
|
data/.travis.yml
ADDED
data/AUTHORS
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2012- Naoki AINOYA, TAGOMORI Satoshi
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# fluent-plugin-sixpack
|
2
|
+
|
3
|
+
## SixpackOutput
|
4
|
+
|
5
|
+
[](https://travis-ci.org/ainoya/fluent-plugin-sixpack)
|
6
|
+
|
7
|
+
[Fluentd](http://fluentd.org) plugin to execute A/B testing with application log.
|
8
|
+
|
9
|
+
To get A/B test statistics, this plugin forwards logs to Sixpack server.
|
10
|
+
|
11
|
+
About Sixpack, see:
|
12
|
+
* Github: https://github.com/seatgeek/sixpack
|
13
|
+
* Product site: http://sixpack.seatgeek.com
|
14
|
+
|
15
|
+
### Configuration
|
16
|
+
|
17
|
+
For messages to participate A/B test such as:
|
18
|
+
|
19
|
+
tag:your.arbitrary.tag {"record_type":"participate", "experiment":"header-color", "alternatives":"red,green,blue", "alternative":"red", "client_id":"ID-0000-0001"}
|
20
|
+
|
21
|
+
Or messages to convert A/B test such as
|
22
|
+
|
23
|
+
tag:your.arbitrary.tag {"record_type":"convert", "experiment":"header-color", "client_id":"ID-0000-0001"}
|
24
|
+
|
25
|
+
Configuration example for graphs in sixpack with POST api url `http://sixpack:5000/(participate|convert)`. You must set this parameter.
|
26
|
+
|
27
|
+
<match app.abtest.log>
|
28
|
+
type sixpack
|
29
|
+
sixpackapi_url http://sixpack.local:5000/
|
30
|
+
</match>
|
31
|
+
|
32
|
+
With this configuration, out_sixpack posts urls below.
|
33
|
+
|
34
|
+
## Parameters
|
35
|
+
|
36
|
+
### Sixpack parameters
|
37
|
+
|
38
|
+
You can change assignment between param keys and values, with using settings below.
|
39
|
+
|
40
|
+
- `key_experiment, :default => 'experiment'`
|
41
|
+
- `key_alternatives, :default => 'alternatives'`
|
42
|
+
- `key_alternative, :default => 'alternative'`
|
43
|
+
- `key_client_id, :default => 'client_id'`
|
44
|
+
- `key_record_type, :default => 'record_type'`
|
45
|
+
|
46
|
+
### Configuration parameters
|
47
|
+
|
48
|
+
* background_post
|
49
|
+
|
50
|
+
Post to Sixpack in background thread, without retries for failures (Default: false)
|
51
|
+
|
52
|
+
* timeout
|
53
|
+
|
54
|
+
Read/Write timeout seconds (Default: 60)
|
55
|
+
|
56
|
+
* retry
|
57
|
+
|
58
|
+
Do retry for HTTP request failures, or not. This feature will be set as false for `background_post yes` automatically. (Default: true)
|
59
|
+
|
60
|
+
* ssl
|
61
|
+
|
62
|
+
Use SSL (https) or not. Default is false.
|
63
|
+
|
64
|
+
* verify\_ssl
|
65
|
+
|
66
|
+
Do SSL verification or not. Default is false (ignore the SSL verification).
|
67
|
+
|
68
|
+
* authentication
|
69
|
+
|
70
|
+
Specify `basic` if your Sixpack protected with basic authentication. Default is 'none' (no authentication).
|
71
|
+
|
72
|
+
* username
|
73
|
+
|
74
|
+
The username for authentication.
|
75
|
+
|
76
|
+
* password
|
77
|
+
|
78
|
+
The password for authentication.
|
79
|
+
|
80
|
+
## TODO
|
81
|
+
|
82
|
+
* More Test
|
83
|
+
* More Documents
|
84
|
+
* Full compatibility with sixpack api
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
Once you've made your great commits:
|
89
|
+
|
90
|
+
1. [Fork][fk] fluent-plugin-sixpack
|
91
|
+
2. Create your feature branch (``git checkout -b my-new-feature``)
|
92
|
+
3. Write tests
|
93
|
+
4. Run tests with ``rake test``
|
94
|
+
5. Commit your changes (``git commit -am 'Added some feature'``)
|
95
|
+
6. Push to the branch (``git push origin my-new-feature``)
|
96
|
+
7. Create new pull request
|
97
|
+
8. That's it!
|
98
|
+
|
99
|
+
Or, you can create an [Issue][is].
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
### Copyright
|
104
|
+
|
105
|
+
* Copyright (c) 2014- Naoki AINOYA
|
106
|
+
* License
|
107
|
+
* Apache License, Version 2.0
|
108
|
+
|
109
|
+
## Other Licence
|
110
|
+
|
111
|
+
### Copyright of fluent-plugin-growthforecast
|
112
|
+
|
113
|
+
This plugin implementation is based on [fluent-plugin-growthforecast](https://github.com/tagomoris/fluent-plugin-growthforecast).
|
114
|
+
|
115
|
+
* Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
|
116
|
+
* License
|
117
|
+
* Apache License, Version 2.0
|
118
|
+
|
119
|
+
[fk]: http://help.github.com/forking/
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.4
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "fluent-plugin-sixpack"
|
5
|
+
gem.version = "0.1.0"
|
6
|
+
gem.authors = ["Naoki AINOYA"]
|
7
|
+
gem.email = ["ainonic@gmail.com"]
|
8
|
+
gem.summary = %q{Fluentd output plugin to post numbers to sixpack (by seatgeek)}
|
9
|
+
gem.description = %q{For sixpack, see http://sixpack.seatgeek.com }
|
10
|
+
gem.homepage = "https://github.com/ainoya/fluent-plugin-sixpack"
|
11
|
+
gem.license = "APLv2"
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_development_dependency "rake"
|
19
|
+
gem.add_runtime_dependency "fluentd"
|
20
|
+
gem.add_runtime_dependency "fluent-mixin-config-placeholders"
|
21
|
+
gem.add_runtime_dependency "resolve-hostname", ">= 0.0.4"
|
22
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
class Fluent::SixpackOutput < Fluent::Output
|
2
|
+
Fluent::Plugin.register_output('sixpack', self)
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
require 'net/http'
|
7
|
+
require 'uri'
|
8
|
+
require 'resolve/hostname'
|
9
|
+
end
|
10
|
+
|
11
|
+
config_param :sixpackapi_url, :string
|
12
|
+
config_param :key_experiment, :default => 'experiment'
|
13
|
+
config_param :key_alternatives, :default => 'alternatives'
|
14
|
+
config_param :key_alternative, :default => 'alternative'
|
15
|
+
config_param :key_client_id, :default => 'client_id'
|
16
|
+
config_param :key_record_type, :default => 'record_type'
|
17
|
+
|
18
|
+
config_param :user_agent, :default => 'user_agent'
|
19
|
+
config_param :ip_address, :default => 'ip_address'
|
20
|
+
|
21
|
+
config_param :ssl, :bool, :default => false
|
22
|
+
config_param :verify_ssl, :bool, :default => false
|
23
|
+
|
24
|
+
config_param :mode, :string, :default => 'gauge' # or count/modified
|
25
|
+
|
26
|
+
config_param :background_post, :bool, :default => false
|
27
|
+
|
28
|
+
config_param :timeout, :integer, :default => nil # default 60secs
|
29
|
+
config_param :retry, :bool, :default => true
|
30
|
+
config_param :keepalive, :bool, :default => true
|
31
|
+
|
32
|
+
config_param :authentication, :string, :default => nil # nil or 'none' or 'basic'
|
33
|
+
config_param :username, :string, :default => ''
|
34
|
+
config_param :password, :string, :default => ''
|
35
|
+
|
36
|
+
SIXPACK_PATH= {
|
37
|
+
:participate => '/participate',
|
38
|
+
:convert => '/convert'
|
39
|
+
}
|
40
|
+
|
41
|
+
# Define `log` method for v0.10.42 or earlier
|
42
|
+
unless method_defined?(:log)
|
43
|
+
define_method("log") { $log }
|
44
|
+
end
|
45
|
+
|
46
|
+
def configure(conf)
|
47
|
+
super
|
48
|
+
|
49
|
+
@mode = case @mode
|
50
|
+
when 'count' then :count
|
51
|
+
when 'modified' then :modified
|
52
|
+
else
|
53
|
+
:gauge
|
54
|
+
end
|
55
|
+
|
56
|
+
@auth = case @authentication
|
57
|
+
when 'basic' then :basic
|
58
|
+
else
|
59
|
+
:none
|
60
|
+
end
|
61
|
+
@resolver = Resolve::Hostname.new(:system_resolver => true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def start
|
65
|
+
super
|
66
|
+
|
67
|
+
@running = true
|
68
|
+
@thread = nil
|
69
|
+
@queue = nil
|
70
|
+
@mutex = nil
|
71
|
+
if @background_post
|
72
|
+
@mutex = Mutex.new
|
73
|
+
@queue = []
|
74
|
+
@thread = Thread.new(&method(:poster))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def shutdown
|
79
|
+
@running = false
|
80
|
+
@thread.join if @thread
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
def poster
|
85
|
+
while @running
|
86
|
+
if @queue.size < 1
|
87
|
+
sleep(0.2)
|
88
|
+
next
|
89
|
+
end
|
90
|
+
|
91
|
+
events = @mutex.synchronize {
|
92
|
+
es,@queue = @queue,[]
|
93
|
+
es
|
94
|
+
}
|
95
|
+
begin
|
96
|
+
post_events(events) if events.size > 0
|
97
|
+
rescue => e
|
98
|
+
log.warn "HTTP POST in background Error occures to sixpack server", :error_class => e.class, :error => e.message
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def connect_to
|
104
|
+
url = URI.parse(@sixpackapi_url)
|
105
|
+
return url.host, url.port
|
106
|
+
end
|
107
|
+
|
108
|
+
def http_connection(host, port)
|
109
|
+
http = Net::HTTP.new(@resolver.getaddress(host), port)
|
110
|
+
if @timeout
|
111
|
+
http.open_timeout = @timeout
|
112
|
+
http.read_timeout = @timeout
|
113
|
+
end
|
114
|
+
if @ssl
|
115
|
+
http.use_ssl = true
|
116
|
+
unless @verify_ssl
|
117
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
118
|
+
end
|
119
|
+
end
|
120
|
+
http
|
121
|
+
end
|
122
|
+
|
123
|
+
def map_sixpack_path(record)
|
124
|
+
sixpack_path
|
125
|
+
end
|
126
|
+
|
127
|
+
def map_sixpack_path_with_query(record)
|
128
|
+
sixpack_path = SIXPACK_PATH[record[@key_record_type].to_sym]
|
129
|
+
case record[@key_record_type]
|
130
|
+
when 'participate'
|
131
|
+
return sixpack_path, URI.encode_www_form({
|
132
|
+
:experiment => record[@key_experiment],
|
133
|
+
:alternatives => record[@key_alternatives].split(','),
|
134
|
+
:alternative => record[@key_alternative],
|
135
|
+
:client_id => record[@key_client_id],
|
136
|
+
})
|
137
|
+
when 'convert'
|
138
|
+
return sixpack_path, URI.encode_www_form({
|
139
|
+
:experiment => record[@key_experiment],
|
140
|
+
:client_id => record[@key_client_id],
|
141
|
+
})
|
142
|
+
else
|
143
|
+
log.warn 'failed to map sixpack path and query'
|
144
|
+
raise
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def post_request(event)
|
149
|
+
uri = URI.parse(@sixpackapi_url)
|
150
|
+
uri.path, uri.query = map_sixpack_path_with_query(event[:record])
|
151
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
152
|
+
if @auth and @auth == :basic
|
153
|
+
req.basic_auth(@username, @password)
|
154
|
+
end
|
155
|
+
req['Host'] = uri.host
|
156
|
+
if @keepalive
|
157
|
+
req['Connection'] = 'Keep-Alive'
|
158
|
+
end
|
159
|
+
|
160
|
+
req
|
161
|
+
end
|
162
|
+
|
163
|
+
def post(event)
|
164
|
+
url = @sixpackapi_url
|
165
|
+
res = nil
|
166
|
+
begin
|
167
|
+
host,port = connect_to
|
168
|
+
req = post_request(event)
|
169
|
+
http = http_connection(host, port)
|
170
|
+
res = http.start {|http| http.request(req) }
|
171
|
+
rescue IOError, EOFError, SystemCallError
|
172
|
+
# server didn't respond
|
173
|
+
log.warn "net/http GET raises exception: #{$!.class}, '#{$!.message}'"
|
174
|
+
end
|
175
|
+
unless res and res.is_a?(Net::HTTPSuccess)
|
176
|
+
log.warn "failed to post to sixpack #{url}, record#{event[:record]}, code: #{res && res.code}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def post_keepalive(events) # [{:tag=>'',:name=>'',:value=>X}]
|
181
|
+
return if events.size < 1
|
182
|
+
|
183
|
+
# sixpack host/port is same for all events (host is from configuration)
|
184
|
+
host,port = connect_to
|
185
|
+
|
186
|
+
requests = events.map{|e| post_request(e)}
|
187
|
+
|
188
|
+
http = nil
|
189
|
+
requests.each do |req|
|
190
|
+
begin
|
191
|
+
unless http
|
192
|
+
http = http_connection(host, port)
|
193
|
+
http.start
|
194
|
+
end
|
195
|
+
res = http.request(req)
|
196
|
+
unless res and res.is_a?(Net::HTTPSuccess)
|
197
|
+
log.warn "failed to post to sixpack: #{host}:#{port}#{req.path}, post_data: #{req.body} code: #{res && res.code}"
|
198
|
+
end
|
199
|
+
rescue IOError, EOFError, Errno::ECONNRESET, Errno::ETIMEDOUT, SystemCallError
|
200
|
+
log.warn "net/http keepalive POST raises exception: #{$!.class}, '#{$!.message}'"
|
201
|
+
begin
|
202
|
+
http.finish
|
203
|
+
rescue
|
204
|
+
# ignore all errors for connection with error
|
205
|
+
end
|
206
|
+
http = nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
begin
|
210
|
+
http.finish
|
211
|
+
rescue
|
212
|
+
# ignore
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def post_events(events)
|
217
|
+
if @keepalive
|
218
|
+
post_keepalive(events)
|
219
|
+
else
|
220
|
+
events.each do |event|
|
221
|
+
post(event)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def emit(tag, es, chain)
|
227
|
+
events = []
|
228
|
+
|
229
|
+
es.each {|time,record|
|
230
|
+
if SIXPACK_PATH.has_key?(record[@key_record_type].to_sym)
|
231
|
+
events.push({:time => time, :tag => tag, :record => record})
|
232
|
+
end
|
233
|
+
}
|
234
|
+
|
235
|
+
if @thread
|
236
|
+
@mutex.synchronize do
|
237
|
+
@queue += events
|
238
|
+
end
|
239
|
+
else
|
240
|
+
begin
|
241
|
+
post_events(events)
|
242
|
+
rescue => e
|
243
|
+
log.warn "HTTP POST Error occures to sixpack server", :error_class => e.class, :error => e.message
|
244
|
+
raise if @retry
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
chain.next
|
249
|
+
end
|
250
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
|
+
require 'fluent/test'
|
15
|
+
unless ENV.has_key?('VERBOSE')
|
16
|
+
nulllogger = Object.new
|
17
|
+
nulllogger.instance_eval {|obj|
|
18
|
+
def method_missing(method, *args)
|
19
|
+
# pass
|
20
|
+
end
|
21
|
+
}
|
22
|
+
$log = nulllogger
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'fluent/plugin/out_sixpack'
|
26
|
+
|
27
|
+
class Test::Unit::TestCase
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'webrick'
|
31
|
+
|
32
|
+
# to handle POST/PUT/DELETE ...
|
33
|
+
module WEBrick::HTTPServlet
|
34
|
+
class ProcHandler < AbstractServlet
|
35
|
+
alias do_POST do_GET
|
36
|
+
alias do_PUT do_GET
|
37
|
+
alias do_DELETE do_GET
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_code(server, port, path, headers={})
|
42
|
+
require 'net/http'
|
43
|
+
Net::HTTP.start(server, port){|http|
|
44
|
+
http.get(path, headers).code
|
45
|
+
}
|
46
|
+
end
|
47
|
+
def get_content(server, port, path, headers={})
|
48
|
+
require 'net/http'
|
49
|
+
Net::HTTP.start(server, port){|http|
|
50
|
+
http.get(path, headers).body
|
51
|
+
}
|
52
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class SixpackOutputTest < Test::Unit::TestCase
|
4
|
+
# setup/teardown and tests of dummy sixpack server defined at the end of this class...
|
5
|
+
SIXPACK_TEST_LISTEN_PORT = 5125
|
6
|
+
|
7
|
+
CONFIG_NON_KEEPALIVE = %[
|
8
|
+
sixpackapi_url http://127.0.0.1:#{SIXPACK_TEST_LISTEN_PORT}/
|
9
|
+
keepalive false
|
10
|
+
]
|
11
|
+
|
12
|
+
CONFIG_THREADING_KEEPALIVE = %[
|
13
|
+
sixpackapi_url http://127.0.0.1:#{SIXPACK_TEST_LISTEN_PORT}/
|
14
|
+
background_post true
|
15
|
+
keepalive true
|
16
|
+
timeout 120
|
17
|
+
]
|
18
|
+
|
19
|
+
CONFIG_THREADING_NON_KEEPALIVE = %[
|
20
|
+
sixpackapi_url http://127.0.0.1:#{SIXPACK_TEST_LISTEN_PORT}/
|
21
|
+
keepalive false
|
22
|
+
]
|
23
|
+
|
24
|
+
def create_driver(conf=CONFIG_NON_KEEPALIVE, tag='test.metrics')
|
25
|
+
Fluent::Test::OutputTestDriver.new(Fluent::SixpackOutput, tag).configure(conf)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_convert
|
29
|
+
d = create_driver(CONFIG_NON_KEEPALIVE, 'test.metrics')
|
30
|
+
d.emit({'record_type' => 'convert',
|
31
|
+
'client_id' => "0000-0000-0000-0000",
|
32
|
+
'experiment' => 'experiment_test_convert'})
|
33
|
+
sleep 0.5 # wait internal posting thread loop
|
34
|
+
|
35
|
+
assert_equal 1, @posted.size
|
36
|
+
|
37
|
+
assert_equal '0000-0000-0000-0000', @posted[0][:client_id]
|
38
|
+
assert_equal 'experiment_test_convert', @posted[0][:experiment]
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_invalid_type
|
42
|
+
d = create_driver(CONFIG_NON_KEEPALIVE, 'test.metrics')
|
43
|
+
d.emit({'record_type' => 'invalid_type',
|
44
|
+
'client_id' => "0000-0000-0000-0000",
|
45
|
+
'experiment' => 'experiment_test_invalid_type'})
|
46
|
+
sleep 0.5 # wait internal posting thread loop
|
47
|
+
|
48
|
+
assert_equal 0, @posted.size
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_non_keepalive
|
52
|
+
d = create_driver(CONFIG_NON_KEEPALIVE, 'test.metrics')
|
53
|
+
['red', 'blue', 'green'].each_with_index do |color, i|
|
54
|
+
d.emit({'record_type' => 'participate',
|
55
|
+
'alternatives' => 'red,blue,green',
|
56
|
+
'alternative' => color,
|
57
|
+
'client_id' => "0000-0000-0000-000#{i}",
|
58
|
+
'experiment' => 'experiment_test_threading_non_keepalive'})
|
59
|
+
sleep 0.5 # wait internal posting thread loop
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_equal 3, @posted.size
|
63
|
+
v1st = @posted[0]
|
64
|
+
v2nd = @posted[1]
|
65
|
+
v3rd = @posted[2]
|
66
|
+
|
67
|
+
assert_equal 'red', v1st[:alternative]
|
68
|
+
assert_equal '0000-0000-0000-0000', v1st[:client_id]
|
69
|
+
assert_nil v1st[:auth]
|
70
|
+
assert_equal 'experiment_test_threading_non_keepalive', v1st[:experiment]
|
71
|
+
|
72
|
+
assert_equal 'blue', v2nd[:alternative]
|
73
|
+
assert_equal '0000-0000-0000-0001', v2nd[:client_id]
|
74
|
+
assert_nil v2nd[:auth]
|
75
|
+
assert_equal 'experiment_test_threading_non_keepalive', v2nd[:experiment]
|
76
|
+
|
77
|
+
assert_equal 'green', v3rd[:alternative]
|
78
|
+
assert_equal '0000-0000-0000-0002', v3rd[:client_id]
|
79
|
+
assert_nil v3rd[:auth]
|
80
|
+
assert_equal 'experiment_test_threading_non_keepalive', v3rd[:experiment]
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_threading
|
84
|
+
d = create_driver(CONFIG_THREADING_KEEPALIVE, 'test.metrics')
|
85
|
+
['red', 'blue', 'green'].each_with_index do |color, i|
|
86
|
+
d.emit({'record_type' => 'participate',
|
87
|
+
'alternatives' => 'red,blue,green',
|
88
|
+
'alternative' => color,
|
89
|
+
'client_id' => "0000-0000-0000-000#{i}",
|
90
|
+
'experiment' => 'experiment_test_threading_non_keepalive'})
|
91
|
+
sleep 0.5 # wait internal posting thread loop
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_equal 3, @posted.size
|
95
|
+
v1st = @posted[0]
|
96
|
+
v2nd = @posted[1]
|
97
|
+
v3rd = @posted[2]
|
98
|
+
|
99
|
+
assert_equal 'red', v1st[:alternative]
|
100
|
+
assert_equal '0000-0000-0000-0000', v1st[:client_id]
|
101
|
+
assert_nil v1st[:auth]
|
102
|
+
assert_equal 'experiment_test_threading_non_keepalive', v1st[:experiment]
|
103
|
+
|
104
|
+
assert_equal 'blue', v2nd[:alternative]
|
105
|
+
assert_equal '0000-0000-0000-0001', v2nd[:client_id]
|
106
|
+
assert_nil v2nd[:auth]
|
107
|
+
assert_equal 'experiment_test_threading_non_keepalive', v2nd[:experiment]
|
108
|
+
|
109
|
+
assert_equal 'green', v3rd[:alternative]
|
110
|
+
assert_equal '0000-0000-0000-0002', v3rd[:client_id]
|
111
|
+
assert_nil v3rd[:auth]
|
112
|
+
assert_equal 'experiment_test_threading_non_keepalive', v3rd[:experiment]
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_threading_non_keepalive
|
116
|
+
d = create_driver(CONFIG_THREADING_NON_KEEPALIVE, 'test.metrics')
|
117
|
+
['red', 'blue', 'green'].each_with_index do |color, i|
|
118
|
+
d.emit({'record_type' => 'participate',
|
119
|
+
'alternatives' => 'red,blue,green',
|
120
|
+
'alternative' => color,
|
121
|
+
'client_id' => "0000-0000-0000-000#{i}",
|
122
|
+
'experiment' => 'experiment_test_threading_non_keepalive'})
|
123
|
+
d.run
|
124
|
+
sleep 0.5 # wait internal posting thread loop
|
125
|
+
end
|
126
|
+
|
127
|
+
assert_equal 3, @posted.size
|
128
|
+
v1st = @posted[0]
|
129
|
+
v2nd = @posted[1]
|
130
|
+
v3rd = @posted[2]
|
131
|
+
|
132
|
+
assert_equal 'red', v1st[:alternative]
|
133
|
+
assert_equal '0000-0000-0000-0000', v1st[:client_id]
|
134
|
+
assert_nil v1st[:auth]
|
135
|
+
assert_equal 'experiment_test_threading_non_keepalive', v1st[:experiment]
|
136
|
+
|
137
|
+
assert_equal 'blue', v2nd[:alternative]
|
138
|
+
assert_equal '0000-0000-0000-0001', v2nd[:client_id]
|
139
|
+
assert_nil v2nd[:auth]
|
140
|
+
assert_equal 'experiment_test_threading_non_keepalive', v2nd[:experiment]
|
141
|
+
|
142
|
+
assert_equal 'green', v3rd[:alternative]
|
143
|
+
assert_equal '0000-0000-0000-0002', v3rd[:client_id]
|
144
|
+
assert_nil v3rd[:auth]
|
145
|
+
assert_equal 'experiment_test_threading_non_keepalive', v3rd[:experiment]
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# setup / teardown for servers
|
150
|
+
def setup
|
151
|
+
Fluent::Test.setup
|
152
|
+
@posted = []
|
153
|
+
@prohibited = 0
|
154
|
+
@auth = false
|
155
|
+
@enable_float_number = false
|
156
|
+
@dummy_server_thread = Thread.new do
|
157
|
+
srv = if ENV['VERBOSE']
|
158
|
+
WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => SIXPACK_TEST_LISTEN_PORT})
|
159
|
+
else
|
160
|
+
logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
|
161
|
+
WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => SIXPACK_TEST_LISTEN_PORT, :Logger => logger, :AccessLog => []})
|
162
|
+
end
|
163
|
+
begin
|
164
|
+
srv.mount_proc('/participate') { |req,res|
|
165
|
+
unless req.request_method == 'GET'
|
166
|
+
res.status = 405
|
167
|
+
res.body = 'request method mismatch'
|
168
|
+
next
|
169
|
+
end
|
170
|
+
if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
|
171
|
+
# ok, authorized
|
172
|
+
elsif @auth
|
173
|
+
res.status = 403
|
174
|
+
@prohibited += 1
|
175
|
+
next
|
176
|
+
else
|
177
|
+
# ok, authorization not required
|
178
|
+
end
|
179
|
+
|
180
|
+
@posted.push({
|
181
|
+
:alternatives=> req.query["alternatives"],
|
182
|
+
:alternative => req.query["alternative"],
|
183
|
+
:client_id => req.query["client_id"],
|
184
|
+
:experiment => req.query["experiment"]
|
185
|
+
})
|
186
|
+
|
187
|
+
res.status = 200
|
188
|
+
}
|
189
|
+
srv.mount_proc('/convert') { |req,res|
|
190
|
+
@posted.push({
|
191
|
+
:client_id => req.query["client_id"],
|
192
|
+
:experiment => req.query["experiment"]
|
193
|
+
})
|
194
|
+
res.status = 200
|
195
|
+
}
|
196
|
+
srv.mount_proc('/') { |req,res|
|
197
|
+
res.status = 200
|
198
|
+
res.body = 'running'
|
199
|
+
}
|
200
|
+
srv.start
|
201
|
+
ensure
|
202
|
+
srv.shutdown
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# to wait completion of dummy server.start()
|
207
|
+
require 'thread'
|
208
|
+
cv = ConditionVariable.new
|
209
|
+
watcher = Thread.new {
|
210
|
+
connected = false
|
211
|
+
while not connected
|
212
|
+
begin
|
213
|
+
get_content('localhost', SIXPACK_TEST_LISTEN_PORT, '/')
|
214
|
+
connected = true
|
215
|
+
rescue Errno::ECONNREFUSED
|
216
|
+
sleep 0.1
|
217
|
+
rescue StandardError => e
|
218
|
+
p e
|
219
|
+
sleep 0.1
|
220
|
+
end
|
221
|
+
end
|
222
|
+
cv.signal
|
223
|
+
}
|
224
|
+
mutex = Mutex.new
|
225
|
+
mutex.synchronize {
|
226
|
+
cv.wait(mutex)
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_dummy_server
|
231
|
+
d = create_driver
|
232
|
+
d.instance.sixpackapi_url =~ /^http:\/\/([.:a-z0-9]+)\//
|
233
|
+
server = $1
|
234
|
+
host = server.split(':')[0]
|
235
|
+
port = server.split(':')[1].to_i
|
236
|
+
client = Net::HTTP.start(host, port)
|
237
|
+
|
238
|
+
assert_equal '200', client.request_get('/').code
|
239
|
+
assert_equal '200', client.request_get('/participate?experiment=experiment_test_threading_non_keepalive&alternatives=red&alternatives=blue&alternatives=green&alternative=green&client_id=0000-0000-0000-0001').code
|
240
|
+
|
241
|
+
assert_equal 1, @posted.size
|
242
|
+
|
243
|
+
assert_equal 'green', @posted[0][:alternative]
|
244
|
+
assert_equal '0000-0000-0000-0001', @posted[0][:client_id]
|
245
|
+
assert_nil @posted[0][:auth]
|
246
|
+
assert_equal 'experiment_test_threading_non_keepalive', @posted[0][:experiment]
|
247
|
+
|
248
|
+
@auth = true
|
249
|
+
|
250
|
+
req_with_auth = lambda do |number, mode, user, pass|
|
251
|
+
url = URI.parse("http://#{host}:#{port}/participate")
|
252
|
+
req = Net::HTTP::Get.new(url.path)
|
253
|
+
req.basic_auth user, pass
|
254
|
+
req.set_form_data({'number'=>number, 'mode'=>mode})
|
255
|
+
req
|
256
|
+
end
|
257
|
+
|
258
|
+
assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
|
259
|
+
|
260
|
+
assert_equal 1, @posted.size
|
261
|
+
|
262
|
+
assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
|
263
|
+
|
264
|
+
assert_equal 2, @posted.size
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
def teardown
|
269
|
+
@dummy_server_thread.kill
|
270
|
+
@dummy_server_thread.join
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-sixpack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Naoki AINOYA
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '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'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: fluent-mixin-config-placeholders
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
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: resolve-hostname
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.4
|
69
|
+
description: 'For sixpack, see http://sixpack.seatgeek.com '
|
70
|
+
email:
|
71
|
+
- ainonic@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".document"
|
77
|
+
- ".gitignore"
|
78
|
+
- ".travis.yml"
|
79
|
+
- AUTHORS
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- VERSION
|
85
|
+
- fluent-plugin-sixpack.gemspec
|
86
|
+
- lib/fluent/plugin/out_sixpack.rb
|
87
|
+
- test/helper.rb
|
88
|
+
- test/plugin/test_out_growthforecast.rb
|
89
|
+
homepage: https://github.com/ainoya/fluent-plugin-sixpack
|
90
|
+
licenses:
|
91
|
+
- APLv2
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.2.2
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Fluentd output plugin to post numbers to sixpack (by seatgeek)
|
113
|
+
test_files:
|
114
|
+
- test/helper.rb
|
115
|
+
- test/plugin/test_out_growthforecast.rb
|