fluent-plugin-kinesis 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require "bundler/gem_tasks"
15
+
16
+ require 'rake/testtask'
17
+ Rake::TestTask.new(:test) do |test|
18
+ test.libs << 'lib' << 'test'
19
+ test.pattern = 'test/**/test_*.rb'
20
+ test.verbose = true
21
+ end
22
+
23
+ task :default => :test
@@ -0,0 +1,41 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ # coding: utf-8
15
+ lib = File.expand_path('../lib', __FILE__)
16
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
17
+
18
+ require "fluent/plugin/version"
19
+
20
+ Gem::Specification.new do |spec|
21
+ spec.name = "fluent-plugin-kinesis"
22
+ spec.version = FluentPluginKinesis::VERSION
23
+ spec.author = 'Amazon Web Services'
24
+ spec.summary = %q{Fluentd output plugin that sends events to Amazon Kinesis.}
25
+ spec.homepage = "https://github.com/awslabs/aws-kinesis-fluent-plugin"
26
+ spec.license = "Apache License, Version 2.0"
27
+
28
+ spec.files = `git ls-files`.split($/)
29
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.3"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "test-unit-rr", "~> 1.0"
36
+
37
+ spec.add_dependency "fluentd", ">= 0.10.53", "< 0.13"
38
+ spec.add_dependency "aws-sdk-core", "~> 2.0.12"
39
+ spec.add_dependency "multi_json", "~> 1.0"
40
+ spec.add_dependency "msgpack", ">= 0.5.8"
41
+ end
@@ -0,0 +1,277 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'aws-sdk-core'
15
+ require 'multi_json'
16
+ require 'logger'
17
+ require 'securerandom'
18
+ require 'fluent/plugin/version'
19
+
20
+ module FluentPluginKinesis
21
+ class OutputFilter < Fluent::BufferedOutput
22
+
23
+ include Fluent::DetachMultiProcessMixin
24
+ include Fluent::SetTimeKeyMixin
25
+ include Fluent::SetTagKeyMixin
26
+
27
+ USER_AGENT_NAME = 'fluent-plugin-kinesis-output-filter'
28
+ PROC_BASE_STR = 'proc {|record| %s }'
29
+ PUT_RECORDS_MAX_COUNT = 500
30
+ PUT_RECORD_MAX_DATA_SIZE = 1024 * 50
31
+ PUT_RECORDS_MAX_DATA_SIZE = 1024 * 1024 * 5
32
+
33
+ Fluent::Plugin.register_output('kinesis',self)
34
+
35
+ config_set_default :include_time_key, true
36
+ config_set_default :include_tag_key, true
37
+
38
+ config_param :aws_key_id, :string, default: nil
39
+ config_param :aws_sec_key, :string, default: nil
40
+ # The 'region' parameter is optional because
41
+ # it may be set as an environment variable.
42
+ config_param :region, :string, default: nil
43
+
44
+ config_param :stream_name, :string
45
+ config_param :random_partition_key, :bool, default: false
46
+ config_param :partition_key, :string, default: nil
47
+ config_param :partition_key_expr, :string, default: nil
48
+ config_param :explicit_hash_key, :string, default: nil
49
+ config_param :explicit_hash_key_expr, :string, default: nil
50
+ config_param :order_events, :bool, default: false
51
+ config_param :retries_on_putrecords, :integer, default: 3
52
+
53
+ config_param :debug, :bool, default: false
54
+
55
+ def configure(conf)
56
+ super
57
+ validate_params
58
+
59
+ if @detach_process or (@num_threads > 1)
60
+ @parallel_mode = true
61
+ if @detach_process
62
+ @use_detach_multi_process_mixin = true
63
+ end
64
+ else
65
+ @parallel_mode = false
66
+ end
67
+
68
+ if @parallel_mode
69
+ if @order_events
70
+ log.warn 'You have set "order_events" to true, however this configuration will be ignored due to "detach_process" and/or "num_threads".'
71
+ end
72
+ @order_events = false
73
+ end
74
+
75
+ if @partition_key_expr
76
+ partition_key_proc_str = sprintf(
77
+ PROC_BASE_STR, @partition_key_expr
78
+ )
79
+ @partition_key_proc = eval(partition_key_proc_str)
80
+ end
81
+
82
+ if @explicit_hash_key_expr
83
+ explicit_hash_key_proc_str = sprintf(
84
+ PROC_BASE_STR, @explicit_hash_key_expr
85
+ )
86
+ @explicit_hash_key_proc = eval(explicit_hash_key_proc_str)
87
+ end
88
+ end
89
+
90
+ def start
91
+ detach_multi_process do
92
+ super
93
+ load_client
94
+ check_connection_to_stream
95
+ end
96
+ end
97
+
98
+ def format(tag, time, record)
99
+ data = {
100
+ data: record.to_json,
101
+ partition_key: get_key(:partition_key,record)
102
+ }
103
+
104
+ if @explicit_hash_key or @explicit_hash_key_proc
105
+ data[:explicit_hash_key] = get_key(:explicit_hash_key,record)
106
+ end
107
+
108
+ data.to_msgpack
109
+ end
110
+
111
+ def write(chunk)
112
+ data_list = chunk.to_enum(:msgpack_each).find_all{|record|
113
+ unless record_exceeds_max_size?(record['data'])
114
+ true
115
+ else
116
+ log.error sprintf('Record exceeds the 50KB per-record size limit and will not be delivered: %s', record['data'])
117
+ false
118
+ end
119
+ }.map{|record|
120
+ build_data_to_put(record)
121
+ }
122
+
123
+ if @order_events
124
+ put_record_for_order_events(data_list)
125
+ else
126
+ records_array = build_records_array_to_put(data_list)
127
+ records_array.each{|records|
128
+ put_records_with_retry(records)
129
+ }
130
+ end
131
+ end
132
+
133
+ private
134
+ def validate_params
135
+ unless @random_partition_key or @partition_key or @partition_key_expr
136
+ raise Fluent::ConfigError, "'random_partition_key' or 'partition_key' or 'partition_key_expr' is required"
137
+ end
138
+ end
139
+
140
+ def load_client
141
+
142
+ user_agent_suffix = "#{USER_AGENT_NAME}/#{FluentPluginKinesis::VERSION}"
143
+
144
+ options = {
145
+ user_agent_suffix: user_agent_suffix
146
+ }
147
+
148
+ if @region
149
+ options[:region] = @region
150
+ end
151
+
152
+ if @aws_key_id && @aws_sec_key
153
+ options.update(
154
+ access_key_id: @aws_key_id,
155
+ secret_access_key: @aws_sec_key,
156
+ )
157
+ end
158
+
159
+ if @debug
160
+ options.update(
161
+ logger: Logger.new(log.out),
162
+ log_level: :debug
163
+ )
164
+ # XXX: Add the following options, if necessary
165
+ # :http_wire_trace => true
166
+ end
167
+
168
+ @client = Aws::Kinesis::Client.new(options)
169
+
170
+ end
171
+
172
+ def check_connection_to_stream
173
+ @client.describe_stream(stream_name: @stream_name)
174
+ end
175
+
176
+ def get_key(name, record)
177
+ if @random_partition_key
178
+ SecureRandom.uuid
179
+ else
180
+ key = instance_variable_get("@#{name}")
181
+ key_proc = instance_variable_get("@#{name}_proc")
182
+
183
+ value = key ? record[key] : record
184
+
185
+ if key_proc
186
+ value = key_proc.call(value)
187
+ end
188
+
189
+ value.to_s
190
+ end
191
+ end
192
+
193
+ def build_data_to_put(data)
194
+ Hash[data.map{|k, v| [k.to_sym, v] }]
195
+ end
196
+
197
+ def put_record_for_order_events(data_list)
198
+ sequence_number_for_ordering = nil
199
+ data_list.each do |data_to_put|
200
+ if sequence_number_for_ordering
201
+ data_to_put.update(
202
+ sequence_number_for_ordering: sequence_number_for_ordering
203
+ )
204
+ end
205
+ data_to_put.update(
206
+ stream_name: @stream_name
207
+ )
208
+ result = @client.put_record(data_to_put)
209
+ sequence_number_for_ordering = result[:sequence_number]
210
+ end
211
+ end
212
+
213
+ def build_records_array_to_put(data_list)
214
+ records_array = []
215
+ records = []
216
+ records_payload_length = 0
217
+ data_list.each{|data_to_put|
218
+ payload = data_to_put[:data]
219
+ if records.length >= PUT_RECORDS_MAX_COUNT or (records_payload_length + payload.length) >= PUT_RECORDS_MAX_DATA_SIZE
220
+ records_array.push(records)
221
+ records = []
222
+ records_payload_length = 0
223
+ end
224
+ records.push(data_to_put)
225
+ records_payload_length += payload.length
226
+ }
227
+ records_array.push(records)
228
+ records_array
229
+ end
230
+
231
+ def put_records_with_retry(records,retry_count=0)
232
+ response = @client.put_records(
233
+ records: records,
234
+ stream_name: @stream_name
235
+ )
236
+
237
+ if response[:failed_record_count] && response[:failed_record_count] > 0
238
+ failed_records = []
239
+ response[:records].each_with_index{|record,index|
240
+ if record[:error_code]
241
+ failed_records.push({body: records[index], error_code: record[:error_code]})
242
+ end
243
+ }
244
+
245
+ if(retry_count < @retries_on_putrecords)
246
+ sleep(calculate_sleep_duration(retry_count))
247
+ retry_count += 1
248
+ log.warn sprintf('Retrying to put records. Retry count: %d', retry_count)
249
+ put_records_with_retry(
250
+ failed_records.map{|record| record[:body]},
251
+ retry_count
252
+ )
253
+ else
254
+ failed_records.each{|record|
255
+ log.error sprintf(
256
+ 'Could not put record, Error: %s, Record: %s',
257
+ record[:error_code],
258
+ record[:body].to_json
259
+ )
260
+ }
261
+ end
262
+ end
263
+ end
264
+
265
+ def calculate_sleep_duration(current_retry)
266
+ Array.new(@retries_on_putrecords){|n| ((2 ** n) * scaling_factor)}[current_retry]
267
+ end
268
+
269
+ def scaling_factor
270
+ 0.5 + Kernel.rand * 0.1
271
+ end
272
+
273
+ def record_exceeds_max_size?(record_string)
274
+ return record_string.length > PUT_RECORD_MAX_DATA_SIZE
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ module FluentPluginKinesis
15
+ VERSION = '0.3.1'
16
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,31 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'rubygems'
15
+ require 'bundler'
16
+ require 'stringio'
17
+ begin
18
+ Bundler.setup(:default, :development)
19
+ rescue Bundler::BundlerError => e
20
+ $stderr.puts e.message
21
+ $stderr.puts "Run `bundle install` to install missing gems"
22
+ exit e.status_code
23
+ end
24
+
25
+ require 'test/unit'
26
+ require 'test/unit/rr'
27
+
28
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
29
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
30
+ require 'fluent/test'
31
+ require 'fluent/plugin/out_kinesis'
@@ -0,0 +1,407 @@
1
+ # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'helper'
15
+
16
+ class KinesisOutputTest < Test::Unit::TestCase
17
+ def setup
18
+ Fluent::Test.setup
19
+ end
20
+
21
+ CONFIG = %[
22
+ aws_key_id test_key_id
23
+ aws_sec_key test_sec_key
24
+ stream_name test_stream
25
+ region us-east-1
26
+ partition_key test_partition_key
27
+ ]
28
+
29
+ def create_driver(conf = CONFIG, tag='test')
30
+ Fluent::Test::BufferedOutputTestDriver
31
+ .new(FluentPluginKinesis::OutputFilter, tag).configure(conf)
32
+ end
33
+
34
+ def create_mock_client
35
+ client = mock(Object.new)
36
+ mock(Aws::Kinesis::Client).new({}) { client }
37
+ return client
38
+ end
39
+
40
+ def test_configure
41
+ d = create_driver
42
+ assert_equal 'test_key_id', d.instance.aws_key_id
43
+ assert_equal 'test_sec_key', d.instance.aws_sec_key
44
+ assert_equal 'test_stream', d.instance.stream_name
45
+ assert_equal 'us-east-1', d.instance.region
46
+ assert_equal 'test_partition_key', d.instance.partition_key
47
+ end
48
+
49
+ def test_configure_with_more_options
50
+
51
+ conf = %[
52
+ stream_name test_stream
53
+ region us-east-1
54
+ partition_key test_partition_key
55
+ partition_key_expr record
56
+ explicit_hash_key test_hash_key
57
+ explicit_hash_key_expr record
58
+ order_events true
59
+ ]
60
+ d = create_driver(conf)
61
+ assert_equal 'test_stream', d.instance.stream_name
62
+ assert_equal 'us-east-1', d.instance.region
63
+ assert_equal 'test_partition_key', d.instance.partition_key
64
+ assert_equal 'Proc',
65
+ d.instance.instance_variable_get(:@partition_key_proc).class.to_s
66
+ assert_equal 'test_hash_key', d.instance.explicit_hash_key
67
+ assert_equal 'Proc',
68
+ d.instance.instance_variable_get(:@explicit_hash_key_proc).class.to_s
69
+ assert_equal 'a',
70
+ d.instance.instance_variable_get(:@partition_key_proc).call('a')
71
+ assert_equal 'a',
72
+ d.instance.instance_variable_get(:@explicit_hash_key_proc).call('a')
73
+ assert_equal true, d.instance.order_events
74
+ assert_equal nil, d.instance.instance_variable_get(:@sequence_number_for_ordering)
75
+ end
76
+
77
+ def test_mode_configuration
78
+
79
+ conf = %[
80
+ stream_name test_stream
81
+ region us-east-1
82
+ partition_key test_partition_key
83
+ ]
84
+ d = create_driver(conf)
85
+ assert_equal(false, d.instance.order_events)
86
+ assert_equal(false, d.instance.instance_variable_get(:@parallel_mode))
87
+
88
+ conf = %[
89
+ stream_name test_stream
90
+ region us-east-1
91
+ partition_key test_partition_key
92
+ order_events true
93
+ ]
94
+ d = create_driver(conf)
95
+ assert_equal(true, d.instance.order_events)
96
+ assert_equal(false, d.instance.instance_variable_get(:@parallel_mode))
97
+
98
+ conf = %[
99
+ stream_name test_stream
100
+ region us-east-1
101
+ partition_key test_partition_key
102
+ num_threads 1
103
+ ]
104
+ d = create_driver(conf)
105
+ assert_equal(false, d.instance.order_events)
106
+ assert_equal(false, d.instance.instance_variable_get(:@parallel_mode))
107
+
108
+ conf = %[
109
+ stream_name test_stream
110
+ region us-east-1
111
+ partition_key test_partition_key
112
+ num_threads 2
113
+ ]
114
+ d = create_driver(conf)
115
+ assert_equal(false, d.instance.order_events)
116
+ assert_equal(true, d.instance.instance_variable_get(:@parallel_mode))
117
+
118
+ conf = %[
119
+ stream_name test_stream
120
+ region us-east-1
121
+ partition_key test_partition_key
122
+ detach_process 1
123
+ ]
124
+ d = create_driver(conf)
125
+ assert_equal(false, d.instance.order_events)
126
+ assert_equal(true, d.instance.instance_variable_get(:@parallel_mode))
127
+
128
+ conf = %[
129
+ stream_name test_stream
130
+ region us-east-1
131
+ partition_key test_partition_key
132
+ order_events true
133
+ detach_process 1
134
+ num_threads 2
135
+ ]
136
+ d = create_driver(conf)
137
+ assert_equal(false, d.instance.order_events)
138
+ assert_equal(true, d.instance.instance_variable_get(:@parallel_mode))
139
+
140
+ end
141
+
142
+ def test_format
143
+
144
+ d = create_driver
145
+
146
+ data1 = {"test_partition_key"=>"key1","a"=>1,"time"=>"2011-01-02T13:14:15Z","tag"=>"test"}
147
+ data2 = {"test_partition_key"=>"key2","a"=>2,"time"=>"2011-01-02T13:14:15Z","tag"=>"test"}
148
+
149
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
150
+ d.emit(data1, time)
151
+ d.emit(data2, time)
152
+
153
+ d.expect_format({
154
+ 'data' => data1.to_json,
155
+ 'partition_key' => 'key1' }.to_msgpack
156
+ )
157
+ d.expect_format({
158
+ 'data' => data2.to_json,
159
+ 'partition_key' => 'key2' }.to_msgpack
160
+ )
161
+
162
+ client = create_mock_client
163
+ client.describe_stream(stream_name: 'test_stream')
164
+ client.put_records(
165
+ stream_name: 'test_stream',
166
+ records: [
167
+ {
168
+ data: data1.to_json,
169
+ partition_key: 'key1'
170
+ },
171
+ {
172
+ data: data2.to_json,
173
+ partition_key: 'key2'
174
+ }
175
+ ]
176
+ ) { {} }
177
+
178
+ d.run
179
+ end
180
+
181
+ def test_order_events
182
+
183
+ d = create_driver(CONFIG + "\norder_events true")
184
+
185
+ data1 = {"test_partition_key"=>"key1","a"=>1,"time"=>"2011-01-02T13:14:15Z","tag"=>"test"}
186
+ data2 = {"test_partition_key"=>"key2","a"=>2,"time"=>"2011-01-02T13:14:15Z","tag"=>"test"}
187
+
188
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
189
+ d.emit(data1, time)
190
+ d.emit(data2, time)
191
+
192
+ d.expect_format({
193
+ 'data' => data1.to_json,
194
+ 'partition_key' => 'key1' }.to_msgpack
195
+ )
196
+ d.expect_format({
197
+ 'data' => data2.to_json,
198
+ 'partition_key' => 'key2' }.to_msgpack
199
+ )
200
+
201
+ client = create_mock_client
202
+ client.describe_stream(stream_name: 'test_stream')
203
+ client.put_record(
204
+ data: data1.to_json,
205
+ partition_key: 'key1',
206
+ stream_name: 'test_stream'
207
+ ) { {sequence_number: 1} }
208
+ client.put_record(
209
+ data: data2.to_json,
210
+ partition_key: 'key2',
211
+ sequence_number_for_ordering: 1,
212
+ stream_name: 'test_stream'
213
+ ) { {} }
214
+
215
+ d.run
216
+ end
217
+
218
+ def test_format_at_lowlevel
219
+ d = create_driver
220
+ data = {"test_partition_key"=>"key1","a"=>1}
221
+ assert_equal(
222
+ MessagePack.pack({
223
+ "data" => data.to_json,
224
+ "partition_key" => "key1"
225
+ }),
226
+ d.instance.format('test','test',data)
227
+ )
228
+ end
229
+
230
+ def test_format_at_lowlevel_with_more_options
231
+
232
+ conf = %[
233
+ stream_name test_stream
234
+ region us-east-1
235
+ partition_key test_partition_key
236
+ partition_key_expr record
237
+ explicit_hash_key test_hash_key
238
+ explicit_hash_key_expr record
239
+ order_events true
240
+ ]
241
+
242
+ d = create_driver(conf)
243
+ data = {"test_partition_key"=>"key1","test_hash_key"=>"hash1","a"=>1}
244
+ assert_equal(
245
+ MessagePack.pack({
246
+ "data" => data.to_json,
247
+ "partition_key" => "key1",
248
+ "explicit_hash_key" => "hash1"
249
+ }),
250
+ d.instance.format('test','test',data)
251
+ )
252
+ end
253
+
254
+ def test_get_key
255
+ d = create_driver
256
+ assert_equal(
257
+ "1",
258
+ d.instance.send(:get_key, "partition_key", {"test_partition_key" => 1})
259
+ )
260
+
261
+ assert_equal(
262
+ "abc",
263
+ d.instance.send(:get_key, "partition_key", {"test_partition_key" => "abc"})
264
+ )
265
+
266
+ d = create_driver(%[
267
+ random_partition_key true
268
+ stream_name test_stream
269
+ region us-east-1'
270
+ ])
271
+
272
+ assert_match(
273
+ /\A[\da-f-]{36}\z/,
274
+ d.instance.send(:get_key, 'foo', 'bar')
275
+ )
276
+
277
+ d = create_driver(%[
278
+ random_partition_key true
279
+ partition_key test_key
280
+ stream_name test_stream
281
+ region us-east-1'
282
+ ])
283
+
284
+ assert_match(
285
+ /\A[\da-f-]{36}\z/,
286
+ d.instance.send(
287
+ :get_key,
288
+ 'partition_key',
289
+ {"test_key" => 'key1'}
290
+ )
291
+ )
292
+
293
+ d = create_driver(%[
294
+ random_partition_key true
295
+ partition_key test_key
296
+ explicit_hash_key explicit_key
297
+ stream_name test_stream
298
+ region us-east-1'
299
+ ])
300
+
301
+ assert_match(
302
+ /\A[\da-f-]{36}\z/,
303
+ d.instance.send(
304
+ :get_key,
305
+ 'partition_key',
306
+ {"test_key" => 'key1', "explicit_key" => 'key2'}
307
+ )
308
+ )
309
+ end
310
+
311
+ def test_record_exceeds_max_size
312
+ d = create_driver
313
+ string = ''
314
+ (1..1024).each{ string = string + '1' }
315
+ assert_equal(
316
+ false,
317
+ d.instance.send(:record_exceeds_max_size?,string)
318
+ )
319
+
320
+ string = ''
321
+ (1..(1024*50)).each{ string = string + '1' }
322
+ assert_equal(
323
+ false,
324
+ d.instance.send(:record_exceeds_max_size?,string)
325
+ )
326
+
327
+ string = ''
328
+ (1..(1024*51)).each{ string = string + '1' }
329
+ assert_equal(
330
+ true,
331
+ d.instance.send(:record_exceeds_max_size?,string)
332
+ )
333
+ end
334
+
335
+ def test_build_records_array_to_put
336
+ d = create_driver
337
+
338
+ data_list = []
339
+ (0..500).each do |n|
340
+ data_list.push({data: n.to_s})
341
+ end
342
+ result = d.instance.send(:build_records_array_to_put,data_list)
343
+ assert_equal(2,result.length)
344
+ assert_equal(500,result[0].length)
345
+ assert_equal(1,result[1].length)
346
+
347
+ data_list = []
348
+ (0..1400).each do
349
+ data_list.push({data: '1'})
350
+ end
351
+ result = d.instance.send(:build_records_array_to_put,data_list)
352
+ assert_equal(3,result.length)
353
+ assert_equal(500,result[0].length)
354
+ assert_equal(500,result[1].length)
355
+ assert_equal(401,result[2].length)
356
+
357
+ data_list = []
358
+ data_string = ''
359
+ (0..(1024*30)).each do
360
+ data_string = data_string + '1'
361
+ end
362
+ (0..500).each do
363
+ data_list.push({data: data_string})
364
+ end
365
+ result = d.instance.send(:build_records_array_to_put,data_list)
366
+ assert_equal(3,result.length)
367
+ assert_equal(170,result[0].length)
368
+ assert_operator(
369
+ 1024 * 1024 *5, :>,
370
+ result[0].reduce(0){|sum,i| sum + i[:data].length}
371
+ )
372
+ assert_equal(170,result[1].length)
373
+ assert_operator(
374
+ 1024 * 1024 *5, :>,
375
+ result[1].reduce(0){|sum,i| sum + i[:data].length}
376
+ )
377
+ assert_equal(161,result[2].length)
378
+ assert_operator(
379
+ 1024 * 1024 *5, :>,
380
+ result[2].reduce(0){|sum,i| sum + i[:data].length}
381
+ )
382
+ end
383
+
384
+ def test_build_data_to_put
385
+ d = create_driver
386
+ assert_equal(
387
+ {key: 1},
388
+ d.instance.send(:build_data_to_put,{"key"=>1})
389
+ )
390
+ end
391
+
392
+ def test_calculate_sleep_duration
393
+ d = create_driver
394
+ assert_operator(
395
+ 1, :>,
396
+ d.instance.send(:calculate_sleep_duration,0)
397
+ )
398
+ assert_operator(
399
+ 2, :>,
400
+ d.instance.send(:calculate_sleep_duration,1)
401
+ )
402
+ assert_operator(
403
+ 4, :>,
404
+ d.instance.send(:calculate_sleep_duration,2)
405
+ )
406
+ end
407
+ end