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 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