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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +20 -0
- data/CONTRIBUTORS.txt +6 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +202 -0
- data/NOTICE.txt +2 -0
- data/README.md +307 -0
- data/Rakefile +23 -0
- data/fluent-plugin-kinesis.gemspec +41 -0
- data/lib/fluent/plugin/out_kinesis.rb +277 -0
- data/lib/fluent/plugin/version.rb +16 -0
- data/test/helper.rb +31 -0
- data/test/plugin/test_out_kinesis.rb +407 -0
- metadata +163 -0
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
|