fluent-plugin-kinesis-modified 3.1.3

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.
@@ -0,0 +1,101 @@
1
+ #
2
+ # Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
5
+ # may not use this file except in compliance with the License. A copy of
6
+ # the License is located at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the "license" file accompanying this file. This file is
11
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ # ANY KIND, either express or implied. See the License for the specific
13
+ # language governing permissions and limitations under the License.
14
+
15
+ require 'fluent/configurable'
16
+ require 'google/protobuf'
17
+
18
+ Google::Protobuf::DescriptorPool.generated_pool.build do
19
+ add_message "AggregatedRecord" do
20
+ repeated :partition_key_table, :string, 1
21
+ repeated :explicit_hash_key_table, :string, 2
22
+ repeated :records, :message, 3, "Record"
23
+ end
24
+ add_message "Tag" do
25
+ optional :key, :string, 1
26
+ optional :value, :string, 2
27
+ end
28
+ add_message "Record" do
29
+ optional :partition_key_index, :uint64, 1
30
+ optional :explicit_hash_key_index, :uint64, 2
31
+ optional :data, :bytes, 3
32
+ repeated :tags, :message, 4, "Tag"
33
+ end
34
+ end
35
+
36
+ module Fluent
37
+ module Plugin
38
+ module KinesisHelper
39
+ class Aggregator
40
+ AggregatedRecord = Google::Protobuf::DescriptorPool.generated_pool.lookup("AggregatedRecord").msgclass
41
+ Tag = Google::Protobuf::DescriptorPool.generated_pool.lookup("Tag").msgclass
42
+ Record = Google::Protobuf::DescriptorPool.generated_pool.lookup("Record").msgclass
43
+
44
+ class InvalidEncodingError < ::StandardError; end
45
+
46
+ MagicNumber = ['F3899AC2'].pack('H*')
47
+
48
+ def aggregate(records, partition_key)
49
+ message = AggregatedRecord.encode(AggregatedRecord.new(
50
+ partition_key_table: ['a', partition_key],
51
+ records: records.map{|data|
52
+ Record.new(partition_key_index: 1, data: data)
53
+ },
54
+ ))
55
+ [MagicNumber, message, Digest::MD5.digest(message)].pack("A4A*A16")
56
+ end
57
+
58
+ def deaggregate(encoded)
59
+ unless aggregated?(encoded)
60
+ raise InvalidEncodingError, "Invalid MagicNumber #{encoded[0..3]}}"
61
+ end
62
+ message, digest = encoded[4..encoded.length-17], encoded[encoded.length-16..-1]
63
+ if Digest::MD5.digest(message) != digest
64
+ raise InvalidEncodingError, "Digest mismatch #{digest}"
65
+ end
66
+ decoded = AggregatedRecord.decode(message)
67
+ records = decoded.records.map(&:data)
68
+ partition_key = decoded.partition_key_table[1]
69
+ [records, partition_key]
70
+ end
71
+
72
+ def aggregated?(encoded)
73
+ encoded[0..3] == MagicNumber
74
+ end
75
+
76
+ def aggregated_size_offset(partition_key)
77
+ data = 'd'
78
+ encoded = aggregate([record(data)], partition_key)
79
+ finalize(encoded).size - data.size
80
+ end
81
+
82
+ module Mixin
83
+ AggregateOffset = 25
84
+ RecordOffset = 10
85
+
86
+ module Params
87
+ include Fluent::Configurable
88
+ end
89
+
90
+ def self.included(mod)
91
+ mod.include Params
92
+ end
93
+
94
+ def aggregator
95
+ @aggregator ||= Aggregator.new
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,201 @@
1
+ #
2
+ # Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
5
+ # may not use this file except in compliance with the License. A copy of
6
+ # the License is located at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the "license" file accompanying this file. This file is
11
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ # ANY KIND, either express or implied. See the License for the specific
13
+ # language governing permissions and limitations under the License.
14
+
15
+ require 'fluent_plugin_kinesis/version'
16
+ require 'fluent/configurable'
17
+
18
+ module Fluent
19
+ module Plugin
20
+ module KinesisHelper
21
+ module API
22
+ MaxRecordSize = 1024 * 1024 # 1 MB
23
+
24
+ module APIParams
25
+ include Fluent::Configurable
26
+ config_param :max_record_size, :integer, default: MaxRecordSize
27
+ end
28
+
29
+ def self.included(mod)
30
+ mod.include APIParams
31
+ end
32
+
33
+ def configure(conf)
34
+ super
35
+ if @max_record_size > MaxRecordSize
36
+ raise ConfigError, "max_record_size can't be grater than #{MaxRecordSize/1024} KB."
37
+ end
38
+ end
39
+
40
+ module BatchRequest
41
+ module BatchRequestParams
42
+ include Fluent::Configurable
43
+ config_param :retries_on_batch_request, :integer, default: 8
44
+ config_param :reset_backoff_if_success, :bool, default: true
45
+ config_param :batch_request_max_count, :integer, default: nil
46
+ config_param :size_kb_per_record, :integer, default: nil
47
+ end
48
+
49
+ def self.included(mod)
50
+ mod.include BatchRequestParams
51
+ end
52
+
53
+ def configure(conf)
54
+ super
55
+ if @batch_request_max_count.nil?
56
+ @batch_request_max_count = self.class::BatchRequestLimitCount
57
+ elsif @batch_request_max_count > self.class::BatchRequestLimitCount
58
+ raise ConfigError, "batch_request_max_count can't be grater than #{self.class::BatchRequestLimitCount}."
59
+ end
60
+ if @size_kb_per_record.nil?
61
+ @size_kb_per_record = self.class::BatchRequestLimitSize
62
+ else
63
+ @size_kb_per_record *= 1024
64
+ if @size_kb_per_record > self.class::BatchRequestLimitSize
65
+ raise ConfigError, "size_kb_per_record can't be grater than #{self.class::BatchRequestLimitSize}."
66
+ end
67
+ end
68
+ end
69
+
70
+ def size_of_values(record)
71
+ record.compact.map(&:size).inject(:+) || 0
72
+ end
73
+
74
+ private
75
+
76
+ def split_to_batches(records, &block)
77
+ batch = []
78
+ size = 0
79
+ records.each do |record|
80
+ record_size = size_of_values(record)
81
+ if (batch.size+1 > @batch_request_max_count or size+record_size > @size_kb_per_record) and batch.size > 0
82
+ yield(batch, size)
83
+ batch = []
84
+ size = 0
85
+ end
86
+ batch << record
87
+ size += record_size
88
+ end
89
+ yield(batch, size) if batch.size > 0
90
+ end
91
+
92
+ def batch_request_with_retry(batch, retry_count=0, backoff: nil, &block)
93
+ backoff ||= Backoff.new
94
+ res = yield(batch)
95
+ if failed_count(res) > 0
96
+ failed_records = collect_failed_records(batch, res)
97
+ if retry_count < @retries_on_batch_request
98
+ backoff.reset if @reset_backoff_if_success and any_records_shipped?(res)
99
+ wait_second = backoff.next
100
+ msg = 'Retrying to request batch. Retry count: %3d, Retry records: %3d, Wait seconds %3.2f' % [retry_count+1, failed_records.size, wait_second]
101
+ log.warn(truncate msg)
102
+ # TODO: sleep() doesn't wait the given seconds sometime.
103
+ # The root cause is unknown so far, so I'd like to add debug print only. It should be fixed in the future.
104
+ log.debug("#{Thread.current.object_id} sleep start")
105
+ sleep(wait_second)
106
+ log.debug("#{Thread.current.object_id} sleep finish")
107
+ batch_request_with_retry(retry_records(failed_records), retry_count+1, backoff: backoff, &block)
108
+ else
109
+ give_up_retries(failed_records)
110
+ end
111
+ end
112
+ end
113
+
114
+ def any_records_shipped?(res)
115
+ results(res).size > failed_count(res)
116
+ end
117
+
118
+ def collect_failed_records(records, res)
119
+ failed_records = []
120
+ results(res).each_with_index do |record, index|
121
+ next unless record[:error_code]
122
+ original = case request_type
123
+ when :streams, :firehose; records[index]
124
+ when :streams_aggregated; records
125
+ end
126
+ failed_records.push(
127
+ original: original,
128
+ error_code: record[:error_code],
129
+ error_message: record[:error_message]
130
+ )
131
+ end
132
+ failed_records
133
+ end
134
+
135
+ def retry_records(failed_records)
136
+ case request_type
137
+ when :streams, :firehose
138
+ failed_records.map{|r| r[:original] }
139
+ when :streams_aggregated
140
+ failed_records.first[:original]
141
+ end
142
+ end
143
+
144
+ def failed_count(res)
145
+ failed_field = case request_type
146
+ when :streams; :failed_record_count
147
+ when :streams_aggregated; :failed_record_count
148
+ when :firehose; :failed_put_count
149
+ end
150
+ res[failed_field]
151
+ end
152
+
153
+ def results(res)
154
+ result_field = case request_type
155
+ when :streams; :records
156
+ when :streams_aggregated; :records
157
+ when :firehose; :request_responses
158
+ end
159
+ res[result_field]
160
+ end
161
+
162
+ def give_up_retries(failed_records)
163
+ failed_records.each {|record|
164
+ log.error(truncate 'Could not put record, Error: %s/%s, Record: %s' % [
165
+ record[:error_code],
166
+ record[:error_message],
167
+ record[:original]
168
+ ])
169
+ }
170
+ end
171
+
172
+ class Backoff
173
+ def initialize
174
+ @count = 0
175
+ end
176
+
177
+ def next
178
+ value = calc(@count)
179
+ @count += 1
180
+ value
181
+ end
182
+
183
+ def reset
184
+ @count = 0
185
+ end
186
+
187
+ private
188
+
189
+ def calc(count)
190
+ (2 ** count) * scaling_factor
191
+ end
192
+
193
+ def scaling_factor
194
+ 0.3 + (0.5-rand) * 0.1
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,169 @@
1
+ #
2
+ # Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
5
+ # may not use this file except in compliance with the License. A copy of
6
+ # the License is located at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the "license" file accompanying this file. This file is
11
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ # ANY KIND, either express or implied. See the License for the specific
13
+ # language governing permissions and limitations under the License.
14
+
15
+ require 'fluent/configurable'
16
+ require 'aws-sdk-core'
17
+
18
+ module Fluent
19
+ module Plugin
20
+ module KinesisHelper
21
+ module Client
22
+ module ClientParams
23
+ include Fluent::Configurable
24
+ config_param :region, :string, default: nil
25
+
26
+ config_param :http_proxy, :string, default: nil, secret: true
27
+ config_param :endpoint, :string, default: nil
28
+ config_param :ssl_verify_peer, :bool, default: true
29
+
30
+ config_param :aws_key_id, :string, default: nil, secret: true
31
+ config_param :aws_sec_key, :string, default: nil, secret: true
32
+ config_section :assume_role_credentials, multi: false do
33
+ desc "The Amazon Resource Name (ARN) of the role to assume"
34
+ config_param :role_arn, :string, secret: true
35
+ desc "An identifier for the assumed role session"
36
+ config_param :role_session_name, :string
37
+ desc "An IAM policy in JSON format"
38
+ config_param :policy, :string, default: nil
39
+ desc "The duration, in seconds, of the role session (900-3600)"
40
+ config_param :duration_seconds, :integer, default: nil
41
+ desc "A unique identifier that is used by third parties when assuming roles in their customers' accounts."
42
+ config_param :external_id, :string, default: nil, secret: true
43
+ desc "A http proxy url for requests to aws sts service"
44
+ config_param :sts_http_proxy, :string, default: nil, secret: true
45
+ end
46
+ config_section :instance_profile_credentials, multi: false do
47
+ desc "Number of times to retry when retrieving credentials"
48
+ config_param :retries, :integer, default: nil
49
+ desc "IP address (default:169.254.169.254)"
50
+ config_param :ip_address, :string, default: nil
51
+ desc "Port number (default:80)"
52
+ config_param :port, :integer, default: nil
53
+ desc "Number of seconds to wait for the connection to open"
54
+ config_param :http_open_timeout, :float, default: nil
55
+ desc "Number of seconds to wait for one block to be read"
56
+ config_param :http_read_timeout, :float, default: nil
57
+ # config_param :delay, :integer or :proc, :default => nil
58
+ # config_param :http_degub_output, :io, :default => nil
59
+ end
60
+ config_section :shared_credentials, multi: false do
61
+ desc "Path to the shared file. (default: $HOME/.aws/credentials)"
62
+ config_param :path, :string, default: nil
63
+ desc "Profile name. Default to 'default' or ENV['AWS_PROFILE']"
64
+ config_param :profile_name, :string, default: nil
65
+ end
66
+ config_section :process_credentials, multi: false do
67
+ desc "External process to execute."
68
+ config_param :process, :string
69
+ end
70
+ end
71
+
72
+ def self.included(mod)
73
+ mod.include ClientParams
74
+ end
75
+
76
+ def configure(conf)
77
+ super
78
+ @region = client.config.region if @region.nil?
79
+ end
80
+
81
+ def client
82
+ @client ||= client_class.new(client_options)
83
+ end
84
+
85
+ private
86
+
87
+ def client_class
88
+ case request_type
89
+ when :streams, :streams_aggregated
90
+ require 'aws-sdk-kinesis'
91
+ Aws::Kinesis::Client
92
+ when :firehose
93
+ require 'aws-sdk-firehose'
94
+ Aws::Firehose::Client
95
+ end
96
+ end
97
+
98
+ def client_options
99
+ options = setup_credentials
100
+ options.update(
101
+ user_agent_suffix: "fluent-plugin-kinesis/#{request_type}/#{FluentPluginKinesis::VERSION}"
102
+ )
103
+ options.update(region: @region) unless @region.nil?
104
+ options.update(http_proxy: @http_proxy) unless @http_proxy.nil?
105
+ options.update(endpoint: @endpoint) unless @endpoint.nil?
106
+ options.update(ssl_verify_peer: @ssl_verify_peer) unless @ssl_verify_peer.nil?
107
+ if @debug
108
+ options.update(logger: Logger.new(log.out))
109
+ options.update(log_level: :debug)
110
+ end
111
+ options
112
+ end
113
+
114
+ def setup_credentials
115
+ options = {}
116
+ credentials_options = {}
117
+ case
118
+ when @aws_key_id && @aws_sec_key
119
+ options[:access_key_id] = @aws_key_id
120
+ options[:secret_access_key] = @aws_sec_key
121
+ when @assume_role_credentials
122
+ c = @assume_role_credentials
123
+ credentials_options[:role_arn] = c.role_arn
124
+ credentials_options[:role_session_name] = c.role_session_name
125
+ credentials_options[:policy] = c.policy if c.policy
126
+ credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
127
+ credentials_options[:external_id] = c.external_id if c.external_id
128
+ if c.sts_http_proxy and @region
129
+ credentials_options[:client] = Aws::STS::Client.new(region: @region, http_proxy: c.sts_http_proxy)
130
+ elsif @region
131
+ credentials_options[:client] = Aws::STS::Client.new(region: @region)
132
+ elsif c.sts_http_proxy
133
+ credentials_options[:client] = Aws::STS::Client.new(http_proxy: c.sts_http_proxy)
134
+ end
135
+ options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
136
+ when @instance_profile_credentials
137
+ c = @instance_profile_credentials
138
+ credentials_options[:retries] = c.retries if c.retries
139
+ credentials_options[:ip_address] = c.ip_address if c.ip_address
140
+ credentials_options[:port] = c.port if c.port
141
+ credentials_options[:http_open_timeout] = c.http_open_timeout if c.http_open_timeout
142
+ credentials_options[:http_read_timeout] = c.http_read_timeout if c.http_read_timeout
143
+ if ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
144
+ options[:credentials] = Aws::ECSCredentials.new(credentials_options)
145
+ else
146
+ options[:credentials] = Aws::InstanceProfileCredentials.new(credentials_options)
147
+ end
148
+ when @shared_credentials
149
+ c = @shared_credentials
150
+ credentials_options[:path] = c.path if c.path
151
+ credentials_options[:profile_name] = c.profile_name if c.profile_name
152
+ options[:credentials] = Aws::SharedCredentials.new(credentials_options)
153
+ when @process_credentials
154
+ if Gem::Version.new(Aws::CORE_GEM_VERSION) < Gem::Version.new('3.24.0')
155
+ raise Fluent::ConfigError, "Config process_credentials requires aws-sdk-core >= 3.24.0. Found aws-sdk-core #{Aws::CORE_GEM_VERSION} instead."
156
+ end
157
+ c = @process_credentials
158
+ process = c.process
159
+ options[:credentials] = Aws::ProcessCredentials.new(process)
160
+ else
161
+ # Use default credentials
162
+ # See http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html
163
+ end
164
+ options
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end