fluent-plugin-sixpack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ Naoki AINOYA <ainonic _at_ gmail.com>
2
+ TAGOMORI Satoshi <tagomoris _at_ gmail.com>
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-sixpack.gemspec
4
+ gemspec
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
+ [![Build Status](https://travis-ci.org/ainoya/fluent-plugin-sixpack.svg?branch=master)](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
@@ -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
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