fluentd 0.14.2 → 0.14.5
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 +4 -4
- data/ChangeLog +77 -1131
- data/Rakefile +15 -0
- data/appveyor.yml +2 -2
- data/example/in_forward.conf +3 -0
- data/example/in_forward_client.conf +37 -0
- data/example/in_forward_shared_key.conf +15 -0
- data/example/in_forward_users.conf +24 -0
- data/example/multi_filters.conf +61 -0
- data/example/out_forward.conf +13 -13
- data/example/out_forward_client.conf +109 -0
- data/example/out_forward_shared_key.conf +36 -0
- data/example/out_forward_users.conf +65 -0
- data/example/{out_buffered_null.conf → out_null.conf} +10 -6
- data/example/secondary_file.conf +41 -0
- data/fluentd.gemspec +19 -16
- data/lib/fluent/agent.rb +5 -0
- data/lib/fluent/command/debug.rb +4 -4
- data/lib/fluent/compat/handle_tag_and_time_mixin.rb +2 -0
- data/lib/fluent/compat/output.rb +5 -2
- data/lib/fluent/config/configure_proxy.rb +26 -5
- data/lib/fluent/config/error.rb +3 -0
- data/lib/fluent/config/section.rb +15 -0
- data/lib/fluent/event_router.rb +77 -4
- data/lib/fluent/plugin/base.rb +12 -3
- data/lib/fluent/plugin/buffer.rb +5 -1
- data/lib/fluent/plugin/filter.rb +48 -6
- data/lib/fluent/plugin/filter_record_transformer.rb +3 -1
- data/lib/fluent/plugin/filter_stdout.rb +0 -4
- data/lib/fluent/plugin/in_debug_agent.rb +5 -5
- data/lib/fluent/plugin/in_dummy.rb +9 -1
- data/lib/fluent/plugin/in_forward.rb +330 -62
- data/lib/fluent/plugin/in_monitor_agent.rb +31 -77
- data/lib/fluent/plugin/in_tail.rb +76 -92
- data/lib/fluent/plugin/multi_output.rb +4 -0
- data/lib/fluent/plugin/out_forward.rb +328 -222
- data/lib/fluent/plugin/out_null.rb +37 -0
- data/lib/fluent/plugin/out_secondary_file.rb +128 -0
- data/lib/fluent/plugin/out_stdout.rb +38 -2
- data/lib/fluent/plugin/output.rb +29 -6
- data/lib/fluent/plugin/storage_local.rb +16 -0
- data/lib/fluent/plugin_helper/timer.rb +6 -1
- data/lib/fluent/root_agent.rb +4 -1
- data/lib/fluent/supervisor.rb +62 -9
- data/lib/fluent/test/base.rb +3 -0
- data/lib/fluent/test/driver/base.rb +5 -0
- data/lib/fluent/test/formatter_test.rb +2 -0
- data/lib/fluent/test/parser_test.rb +2 -0
- data/lib/fluent/test/startup_shutdown.rb +33 -0
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +1 -2
- data/test/compat/test_calls_super.rb +2 -0
- data/test/config/test_configurable.rb +88 -0
- data/test/config/test_types.rb +7 -3
- data/test/plugin/test_filter.rb +121 -23
- data/test/plugin/test_filter_record_transformer.rb +22 -6
- data/test/plugin/test_in_debug_agent.rb +2 -2
- data/test/plugin/test_in_forward.rb +920 -407
- data/test/plugin/test_in_monitor_agent.rb +333 -0
- data/test/plugin/test_in_tail.rb +686 -595
- data/test/plugin/test_out_forward.rb +150 -207
- data/test/plugin/test_out_null.rb +85 -9
- data/test/plugin/test_out_secondary_file.rb +432 -0
- data/test/plugin/test_out_stdout.rb +143 -45
- data/test/plugin/test_output.rb +53 -0
- data/test/plugin/test_output_as_buffered.rb +13 -0
- data/test/plugin/test_output_as_buffered_overflow.rb +3 -0
- data/test/plugin/test_output_as_buffered_retries.rb +11 -0
- data/test/plugin/test_output_as_buffered_secondary.rb +12 -0
- data/test/plugin/test_output_as_standard.rb +12 -0
- data/test/plugin_helper/test_compat_parameters.rb +2 -0
- data/test/plugin_helper/test_timer.rb +31 -0
- data/test/test_event_router.rb +87 -0
- data/test/test_filter.rb +48 -6
- data/test/test_log.rb +5 -2
- data/test/test_output.rb +41 -1
- data/test/test_plugin_classes.rb +17 -9
- data/test/test_root_agent.rb +188 -0
- metadata +62 -68
- data/lib/fluent/plugin/out_buffered_null.rb +0 -59
- data/lib/fluent/plugin/out_buffered_stdout.rb +0 -70
- data/test/plugin/test_out_buffered_null.rb +0 -79
- data/test/plugin/test_out_buffered_stdout.rb +0 -122
|
@@ -53,18 +53,7 @@ module Fluent
|
|
|
53
53
|
desc 'The timeout time when sending event logs.'
|
|
54
54
|
config_param :send_timeout, :time, default: 60
|
|
55
55
|
desc 'The transport protocol to use for heartbeats.(udp,tcp,none)'
|
|
56
|
-
config_param :heartbeat_type,
|
|
57
|
-
case val.downcase
|
|
58
|
-
when 'tcp'
|
|
59
|
-
:tcp
|
|
60
|
-
when 'udp'
|
|
61
|
-
:udp
|
|
62
|
-
when 'none'
|
|
63
|
-
:none
|
|
64
|
-
else
|
|
65
|
-
raise ConfigError, "forward output heartbeat type should be 'tcp', 'udp', or 'none'"
|
|
66
|
-
end
|
|
67
|
-
end
|
|
56
|
+
config_param :heartbeat_type, :enum, list: [:tcp, :udp, :none], default: :tcp
|
|
68
57
|
desc 'The interval of the heartbeat packer.'
|
|
69
58
|
config_param :heartbeat_interval, :time, default: 1
|
|
70
59
|
desc 'The wait time before accepting a server fault recovery.'
|
|
@@ -81,32 +70,54 @@ module Fluent
|
|
|
81
70
|
desc 'Change the protocol to at-least-once.'
|
|
82
71
|
config_param :require_ack_response, :bool, default: false # require in_forward to respond with ack
|
|
83
72
|
desc 'This option is used when require_ack_response is true.'
|
|
84
|
-
config_param :ack_response_timeout, :time, default: 190
|
|
73
|
+
config_param :ack_response_timeout, :time, default: 190
|
|
74
|
+
desc 'Reading data size from server'
|
|
75
|
+
config_param :read_length, :size, default: 512 # 512bytes
|
|
76
|
+
desc 'The interval while reading data from server'
|
|
77
|
+
config_param :read_interval_msec, :integer, default: 50 # 50ms
|
|
85
78
|
# Linux default tcp_syn_retries is 5 (in many environment)
|
|
86
79
|
# 3 + 6 + 12 + 24 + 48 + 96 -> 189 (sec)
|
|
87
80
|
desc 'Enable client-side DNS round robin.'
|
|
88
81
|
config_param :dns_round_robin, :bool, default: false # heartbeat_type 'udp' is not available for this
|
|
89
82
|
|
|
83
|
+
config_section :security, required: false, multi: false do
|
|
84
|
+
desc 'The hostname'
|
|
85
|
+
config_param :self_hostname, :string
|
|
86
|
+
desc 'Shared key for authentication'
|
|
87
|
+
config_param :shared_key, :string, secret: true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
config_section :server, param_name: :servers do
|
|
91
|
+
desc "The IP address or host name of the server."
|
|
92
|
+
config_param :host, :string
|
|
93
|
+
desc "The name of the server. Used in log messages."
|
|
94
|
+
config_param :name, :string, default: nil
|
|
95
|
+
desc "The port number of the host."
|
|
96
|
+
config_param :port, :integer, default: LISTEN_PORT
|
|
97
|
+
desc "The shared key per server."
|
|
98
|
+
config_param :shared_key, :string, default: nil, secret: true
|
|
99
|
+
desc "The username for authentication."
|
|
100
|
+
config_param :username, :string, default: ''
|
|
101
|
+
desc "The password for authentication."
|
|
102
|
+
config_param :password, :string, default: '', secret: true
|
|
103
|
+
desc "Marks a node as the standby node for an Active-Standby model between Fluentd nodes."
|
|
104
|
+
config_param :standby, :bool, default: false
|
|
105
|
+
desc "The load balancing weight."
|
|
106
|
+
config_param :weight, :integer, default: 60
|
|
107
|
+
end
|
|
108
|
+
|
|
90
109
|
attr_reader :nodes
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
config_param :
|
|
94
|
-
|
|
111
|
+
config_param :port, :integer, default: LISTEN_PORT, obsoleted: "User <server> section instead."
|
|
112
|
+
config_param :host, :string, default: nil, obsoleted: "Use <server> section instead."
|
|
113
|
+
|
|
114
|
+
attr_reader :read_interval, :recover_sample_size
|
|
95
115
|
|
|
96
116
|
def configure(conf)
|
|
97
117
|
super
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
log.warn "'host' option in forward output is obsoleted. Use '<server> host xxx </server>' instead."
|
|
102
|
-
port = conf['port']
|
|
103
|
-
port = port ? port.to_i : LISTEN_PORT
|
|
104
|
-
element = conf.add_element('server')
|
|
105
|
-
element['host'] = host
|
|
106
|
-
element['port'] = port.to_s
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
recover_sample_size = @recover_wait / @heartbeat_interval
|
|
119
|
+
@read_interval = @read_interval_msec / 1000.0
|
|
120
|
+
@recover_sample_size = @recover_wait / @heartbeat_interval
|
|
110
121
|
|
|
111
122
|
if @dns_round_robin
|
|
112
123
|
if @heartbeat_type == :udp
|
|
@@ -114,39 +125,23 @@ module Fluent
|
|
|
114
125
|
end
|
|
115
126
|
end
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
next if e.name != "server"
|
|
119
|
-
|
|
120
|
-
host = e['host']
|
|
121
|
-
port = e['port']
|
|
122
|
-
port = port ? port.to_i : LISTEN_PORT
|
|
123
|
-
|
|
124
|
-
weight = e['weight']
|
|
125
|
-
weight = weight ? weight.to_i : 60
|
|
126
|
-
|
|
127
|
-
standby = !!e['standby']
|
|
128
|
-
|
|
129
|
-
name = e['name']
|
|
130
|
-
unless name
|
|
131
|
-
name = "#{host}:#{port}"
|
|
132
|
-
end
|
|
133
|
-
|
|
128
|
+
@servers.each do |server|
|
|
134
129
|
failure = FailureDetector.new(@heartbeat_interval, @hard_timeout, Time.now.to_i.to_f)
|
|
130
|
+
name = server.name || "#{server.host}:#{server.port}"
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
@phi_threshold, recover_sample_size, @expire_dns_cache, @phi_failure_detector, @dns_round_robin)
|
|
138
|
-
|
|
132
|
+
log.info "adding forwarding server '#{name}'", host: server.host, port: server.port, weight: server.weight, plugin_id: plugin_id
|
|
139
133
|
if @heartbeat_type == :none
|
|
140
|
-
@nodes << NoneHeartbeatNode.new(
|
|
134
|
+
@nodes << NoneHeartbeatNode.new(self, server, failure: failure)
|
|
141
135
|
else
|
|
142
|
-
@nodes << Node.new(
|
|
136
|
+
@nodes << Node.new(self, server, failure: failure)
|
|
143
137
|
end
|
|
144
|
-
|
|
145
|
-
}
|
|
138
|
+
end
|
|
146
139
|
|
|
147
140
|
if @nodes.empty?
|
|
148
141
|
raise ConfigError, "forward output plugin requires at least one <server> is required"
|
|
149
142
|
end
|
|
143
|
+
|
|
144
|
+
raise Fluent::ConfigError, "ack_response_timeout must be a positive integer" if @ack_response_timeout < 1
|
|
150
145
|
end
|
|
151
146
|
|
|
152
147
|
def start
|
|
@@ -155,6 +150,7 @@ module Fluent
|
|
|
155
150
|
@rand_seed = Random.new.seed
|
|
156
151
|
rebuild_weight_array
|
|
157
152
|
@rr = 0
|
|
153
|
+
@usock = nil
|
|
158
154
|
|
|
159
155
|
unless @heartbeat_type == :none
|
|
160
156
|
@loop = Coolio::Loop.new
|
|
@@ -205,7 +201,7 @@ module Fluent
|
|
|
205
201
|
|
|
206
202
|
if node.available?
|
|
207
203
|
begin
|
|
208
|
-
send_data(
|
|
204
|
+
node.send_data(tag, chunk)
|
|
209
205
|
return
|
|
210
206
|
rescue
|
|
211
207
|
# for load balancing during detecting crashed servers
|
|
@@ -221,6 +217,12 @@ module Fluent
|
|
|
221
217
|
end
|
|
222
218
|
end
|
|
223
219
|
|
|
220
|
+
# MessagePack FixArray length is 3
|
|
221
|
+
FORWARD_HEADER = [0x93].pack('C').freeze
|
|
222
|
+
def forward_header
|
|
223
|
+
FORWARD_HEADER
|
|
224
|
+
end
|
|
225
|
+
|
|
224
226
|
private
|
|
225
227
|
|
|
226
228
|
def rebuild_weight_array
|
|
@@ -265,111 +267,6 @@ module Fluent
|
|
|
265
267
|
@weight_array = weight_array
|
|
266
268
|
end
|
|
267
269
|
|
|
268
|
-
# MessagePack FixArray length is 3
|
|
269
|
-
FORWARD_HEADER = [0x93].pack('C').freeze
|
|
270
|
-
def forward_header
|
|
271
|
-
FORWARD_HEADER
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
#FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
|
|
275
|
-
def send_heartbeat_tcp(node)
|
|
276
|
-
sock = connect(node)
|
|
277
|
-
begin
|
|
278
|
-
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
|
279
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
|
280
|
-
opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
|
281
|
-
# don't send any data to not cause a compatibility problem
|
|
282
|
-
#sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
|
283
|
-
#sock.write FORWARD_TCP_HEARTBEAT_DATA
|
|
284
|
-
node.heartbeat(true)
|
|
285
|
-
ensure
|
|
286
|
-
sock.close_write
|
|
287
|
-
sock.close
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def send_data(node, tag, chunk)
|
|
292
|
-
sock = connect(node)
|
|
293
|
-
begin
|
|
294
|
-
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
|
295
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
|
296
|
-
|
|
297
|
-
opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
|
298
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
|
299
|
-
|
|
300
|
-
# beginArray(3)
|
|
301
|
-
sock.write forward_header
|
|
302
|
-
|
|
303
|
-
# writeRaw(tag)
|
|
304
|
-
sock.write tag.to_msgpack # tag
|
|
305
|
-
|
|
306
|
-
# beginRaw(size)
|
|
307
|
-
sz = chunk.size
|
|
308
|
-
#if sz < 32
|
|
309
|
-
# # FixRaw
|
|
310
|
-
# sock.write [0xa0 | sz].pack('C')
|
|
311
|
-
#elsif sz < 65536
|
|
312
|
-
# # raw 16
|
|
313
|
-
# sock.write [0xda, sz].pack('Cn')
|
|
314
|
-
#else
|
|
315
|
-
# raw 32
|
|
316
|
-
sock.write [0xdb, sz].pack('CN')
|
|
317
|
-
#end
|
|
318
|
-
|
|
319
|
-
# writeRawBody(packed_es)
|
|
320
|
-
chunk.write_to(sock)
|
|
321
|
-
|
|
322
|
-
option = { 'size' => chunk.size_of_events }
|
|
323
|
-
option['chunk'] = Base64.encode64(chunk.unique_id) if @require_ack_response
|
|
324
|
-
sock.write option.to_msgpack
|
|
325
|
-
|
|
326
|
-
if @require_ack_response && @ack_response_timeout > 0
|
|
327
|
-
# Waiting for a response here results in a decrease of throughput because a chunk queue is locked.
|
|
328
|
-
# To avoid a decrease of troughput, it is necessary to prepare a list of chunks that wait for responses
|
|
329
|
-
# and process them asynchronously.
|
|
330
|
-
if IO.select([sock], nil, nil, @ack_response_timeout)
|
|
331
|
-
raw_data = sock.recv(1024)
|
|
332
|
-
|
|
333
|
-
# When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
|
|
334
|
-
# If this happens we assume the data wasn't delivered and retry it.
|
|
335
|
-
if raw_data.empty?
|
|
336
|
-
@log.warn "node #{node.host}:#{node.port} closed the connection. regard it as unavailable."
|
|
337
|
-
node.disable!
|
|
338
|
-
raise ForwardOutputConnectionClosedError, "node #{node.host}:#{node.port} closed connection"
|
|
339
|
-
else
|
|
340
|
-
# Serialization type of the response is same as sent data.
|
|
341
|
-
res = MessagePack.unpack(raw_data)
|
|
342
|
-
|
|
343
|
-
if res['ack'] != option['chunk']
|
|
344
|
-
# Some errors may have occured when ack and chunk id is different, so send the chunk again.
|
|
345
|
-
raise ForwardOutputResponseError, "ack in response and chunk id in sent data are different"
|
|
346
|
-
end
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
else
|
|
350
|
-
# IO.select returns nil on timeout.
|
|
351
|
-
# There are 2 types of cases when no response has been received:
|
|
352
|
-
# (1) the node does not support sending responses
|
|
353
|
-
# (2) the node does support sending response but responses have not arrived for some reasons.
|
|
354
|
-
@log.warn "no response from #{node.host}:#{node.port}. regard it as unavailable."
|
|
355
|
-
node.disable!
|
|
356
|
-
raise ForwardOutputACKTimeoutError, "node #{node.host}:#{node.port} does not return ACK"
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
node.heartbeat(false)
|
|
361
|
-
res # for test
|
|
362
|
-
ensure
|
|
363
|
-
sock.close_write
|
|
364
|
-
sock.close
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
def connect(node)
|
|
369
|
-
# TODO unix socket?
|
|
370
|
-
TCPSocket.new(node.resolved_host, node.port)
|
|
371
|
-
end
|
|
372
|
-
|
|
373
270
|
class HeartbeatRequestTimer < Coolio::TimerWatcher
|
|
374
271
|
def initialize(interval, callback)
|
|
375
272
|
super(interval, true)
|
|
@@ -390,15 +287,11 @@ module Fluent
|
|
|
390
287
|
rebuild_weight_array
|
|
391
288
|
end
|
|
392
289
|
begin
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
else
|
|
397
|
-
@usock.send "\0", 0, Socket.pack_sockaddr_in(n.port, n.resolved_host)
|
|
398
|
-
end
|
|
290
|
+
log.trace "sending heartbeat", host: n.host, port: n.port, heartbeat_type: @heartbeat_type
|
|
291
|
+
n.usock = @usock if @usock
|
|
292
|
+
n.send_heartbeat
|
|
399
293
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNREFUSED
|
|
400
|
-
|
|
401
|
-
log.debug "failed to send heartbeat packet to #{n.host}:#{n.port}", error: $!.to_s
|
|
294
|
+
log.debug "failed to send heartbeat packet", host: n.host, port: n.port, heartbeat_type: @heartbeat_type, error: $!
|
|
402
295
|
end
|
|
403
296
|
}
|
|
404
297
|
end
|
|
@@ -434,27 +327,38 @@ module Fluent
|
|
|
434
327
|
end
|
|
435
328
|
end
|
|
436
329
|
|
|
437
|
-
NodeConfig = Struct.new("NodeConfig", :name, :host, :port, :weight, :standby, :failure,
|
|
438
|
-
:phi_threshold, :recover_sample_size, :expire_dns_cache, :phi_failure_detector, :dns_round_robin)
|
|
439
|
-
|
|
440
330
|
class Node
|
|
441
|
-
def initialize(
|
|
442
|
-
@
|
|
443
|
-
@
|
|
444
|
-
|
|
445
|
-
@
|
|
446
|
-
@
|
|
447
|
-
@
|
|
448
|
-
@
|
|
331
|
+
def initialize(sender, server, failure:)
|
|
332
|
+
@sender = sender
|
|
333
|
+
@log = sender.log
|
|
334
|
+
|
|
335
|
+
@name = server.name
|
|
336
|
+
@host = server.host
|
|
337
|
+
@port = server.port
|
|
338
|
+
@weight = server.weight
|
|
339
|
+
@standby = server.standby
|
|
340
|
+
@failure = failure
|
|
449
341
|
@available = true
|
|
342
|
+
@state = nil
|
|
343
|
+
|
|
344
|
+
@usock = nil
|
|
345
|
+
|
|
346
|
+
@username = server.username
|
|
347
|
+
@password = server.password
|
|
348
|
+
@shared_key = server.shared_key || (sender.security && sender.security.shared_key) || ""
|
|
349
|
+
@shared_key_salt = generate_salt
|
|
350
|
+
@shared_key_nonce = ""
|
|
351
|
+
|
|
352
|
+
@unpacker = Fluent::Engine.msgpack_unpacker
|
|
450
353
|
|
|
451
354
|
@resolved_host = nil
|
|
452
355
|
@resolved_time = 0
|
|
453
356
|
resolved_host # check dns
|
|
454
357
|
end
|
|
455
358
|
|
|
456
|
-
|
|
457
|
-
|
|
359
|
+
attr_accessor :usock
|
|
360
|
+
|
|
361
|
+
attr_reader :name, :host, :port, :weight, :standby, :state
|
|
458
362
|
attr_reader :sockaddr # used by on_heartbeat
|
|
459
363
|
attr_reader :failure, :available # for test
|
|
460
364
|
|
|
@@ -467,33 +371,177 @@ module Fluent
|
|
|
467
371
|
end
|
|
468
372
|
|
|
469
373
|
def standby?
|
|
470
|
-
@
|
|
374
|
+
@standby
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def connect
|
|
378
|
+
TCPSocket.new(resolved_host, port)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def set_socket_options(sock)
|
|
382
|
+
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
|
383
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
|
384
|
+
|
|
385
|
+
opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
|
386
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
|
387
|
+
|
|
388
|
+
sock
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def establish_connection(sock)
|
|
392
|
+
while available? && @state != :established
|
|
393
|
+
begin
|
|
394
|
+
# TODO: On Ruby 2.2 or earlier, read_nonblock doesn't work expectedly.
|
|
395
|
+
# We need rewrite around here using new socket/server plugin helper.
|
|
396
|
+
buf = sock.read_nonblock(@sender.read_length)
|
|
397
|
+
if buf.empty?
|
|
398
|
+
sleep @sender.read_interval
|
|
399
|
+
next
|
|
400
|
+
end
|
|
401
|
+
@unpacker.feed_each(buf) do |data|
|
|
402
|
+
on_read(sock, data)
|
|
403
|
+
end
|
|
404
|
+
rescue IO::WaitReadable
|
|
405
|
+
# If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by IO::WaitReadable.
|
|
406
|
+
# So IO::WaitReadable can be used to rescue the exceptions for retrying read_nonblock.
|
|
407
|
+
# http://docs.ruby-lang.org/en/2.3.0/IO.html#method-i-read_nonblock
|
|
408
|
+
sleep @sender.read_interval unless @state == :established
|
|
409
|
+
rescue SystemCallError => e
|
|
410
|
+
@log.warn "disconnected by error", host: @host, port: @port, error: e
|
|
411
|
+
disable!
|
|
412
|
+
break
|
|
413
|
+
rescue EOFError
|
|
414
|
+
@log.warn "disconnected", host: @host, port: @port
|
|
415
|
+
disable!
|
|
416
|
+
break
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def send_data(tag, chunk)
|
|
422
|
+
sock = connect
|
|
423
|
+
@state = @sender.security ? :helo : :established
|
|
424
|
+
begin
|
|
425
|
+
set_socket_options(sock)
|
|
426
|
+
|
|
427
|
+
if @state != :established
|
|
428
|
+
establish_connection(sock)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
unless available?
|
|
432
|
+
raise ForwardOutputConnectionClosedError, "failed to establish connection with node #{@name}"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
option = { 'size' => chunk.size_of_events }
|
|
436
|
+
option['chunk'] = Base64.encode64(chunk.unique_id) if @sender.require_ack_response
|
|
437
|
+
|
|
438
|
+
# out_forward always uses Raw32 type for content.
|
|
439
|
+
# Raw16 can store only 64kbytes, and it should be much smaller than buffer chunk size.
|
|
440
|
+
|
|
441
|
+
sock.write @sender.forward_header # beginArray(3)
|
|
442
|
+
sock.write tag.to_msgpack # 1. writeRaw(tag)
|
|
443
|
+
sock.write [0xdb, chunk.size].pack('CN') # 2. beginRaw(size) raw32
|
|
444
|
+
chunk.write_to(sock) # writeRawBody(packed_es)
|
|
445
|
+
sock.write option.to_msgpack # 3. writeOption(option)
|
|
446
|
+
|
|
447
|
+
if @sender.require_ack_response
|
|
448
|
+
# Waiting for a response here results in a decrease of throughput because a chunk queue is locked.
|
|
449
|
+
# To avoid a decrease of troughput, it is necessary to prepare a list of chunks that wait for responses
|
|
450
|
+
# and process them asynchronously.
|
|
451
|
+
if IO.select([sock], nil, nil, @sender.ack_response_timeout)
|
|
452
|
+
raw_data = begin
|
|
453
|
+
sock.recv(1024)
|
|
454
|
+
rescue Errno::ECONNRESET
|
|
455
|
+
""
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
|
|
459
|
+
# If this happens we assume the data wasn't delivered and retry it.
|
|
460
|
+
if raw_data.empty?
|
|
461
|
+
@log.warn "node closed the connection. regard it as unavailable.", host: @host, port: @port
|
|
462
|
+
disable!
|
|
463
|
+
raise ForwardOutputConnectionClosedError, "node #{@host}:#{@port} closed connection"
|
|
464
|
+
else
|
|
465
|
+
@unpacker.feed(raw_data)
|
|
466
|
+
res = @unpacker.read
|
|
467
|
+
if res['ack'] != option['chunk']
|
|
468
|
+
# Some errors may have occured when ack and chunk id is different, so send the chunk again.
|
|
469
|
+
raise ForwardOutputResponseError, "ack in response and chunk id in sent data are different"
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
else
|
|
474
|
+
# IO.select returns nil on timeout.
|
|
475
|
+
# There are 2 types of cases when no response has been received:
|
|
476
|
+
# (1) the node does not support sending responses
|
|
477
|
+
# (2) the node does support sending response but responses have not arrived for some reasons.
|
|
478
|
+
@log.warn "no response from node. regard it as unavailable.", host: @host, port: @port
|
|
479
|
+
disable!
|
|
480
|
+
raise ForwardOutputACKTimeoutError, "node #{host}:#{port} does not return ACK"
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
heartbeat(false)
|
|
485
|
+
res # for test
|
|
486
|
+
ensure
|
|
487
|
+
sock.close_write
|
|
488
|
+
sock.close
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
|
|
493
|
+
def send_heartbeat
|
|
494
|
+
case @sender.heartbeat_type
|
|
495
|
+
when :tcp
|
|
496
|
+
sock = connect
|
|
497
|
+
begin
|
|
498
|
+
opt = [1, @sender.send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
|
499
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
|
500
|
+
# opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
|
501
|
+
# sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
|
502
|
+
|
|
503
|
+
## don't send any data to not cause a compatibility problem
|
|
504
|
+
# sock.write FORWARD_TCP_HEARTBEAT_DATA
|
|
505
|
+
|
|
506
|
+
# successful tcp connection establishment is considered as valid heartbeat
|
|
507
|
+
heartbeat(true)
|
|
508
|
+
ensure
|
|
509
|
+
sock.close_write
|
|
510
|
+
sock.close
|
|
511
|
+
end
|
|
512
|
+
when :udp
|
|
513
|
+
@usock.send "\0", 0, Socket.pack_sockaddr_in(n.port, n.resolved_host)
|
|
514
|
+
when :none # :none doesn't use this class
|
|
515
|
+
raise "BUG: heartbeat_type none must not use Node"
|
|
516
|
+
else
|
|
517
|
+
raise "BUG: unknown heartbeat_type '#{@sender.heartbeat_type}'"
|
|
518
|
+
end
|
|
471
519
|
end
|
|
472
520
|
|
|
473
521
|
def resolved_host
|
|
474
|
-
case @
|
|
522
|
+
case @sender.expire_dns_cache
|
|
475
523
|
when 0
|
|
476
524
|
# cache is disabled
|
|
477
|
-
|
|
525
|
+
resolve_dns!
|
|
478
526
|
|
|
479
527
|
when nil
|
|
480
528
|
# persistent cache
|
|
481
|
-
|
|
529
|
+
@resolved_host ||= resolve_dns!
|
|
482
530
|
|
|
483
531
|
else
|
|
484
532
|
now = Engine.now
|
|
485
533
|
rh = @resolved_host
|
|
486
|
-
if !rh || now - @resolved_time >= @
|
|
534
|
+
if !rh || now - @resolved_time >= @sender.expire_dns_cache
|
|
487
535
|
rh = @resolved_host = resolve_dns!
|
|
488
536
|
@resolved_time = now
|
|
489
537
|
end
|
|
490
|
-
|
|
538
|
+
rh
|
|
491
539
|
end
|
|
492
540
|
end
|
|
493
541
|
|
|
494
542
|
def resolve_dns!
|
|
495
543
|
addrinfo_list = Socket.getaddrinfo(@host, @port, nil, Socket::SOCK_STREAM)
|
|
496
|
-
addrinfo = @
|
|
544
|
+
addrinfo = @sender.dns_round_robin ? addrinfo_list.sample : addrinfo_list.first
|
|
497
545
|
@sockaddr = Socket.pack_sockaddr_in(addrinfo[1], addrinfo[3]) # used by on_heartbeat
|
|
498
546
|
addrinfo[3]
|
|
499
547
|
end
|
|
@@ -516,36 +564,122 @@ module Fluent
|
|
|
516
564
|
return true
|
|
517
565
|
end
|
|
518
566
|
|
|
519
|
-
if @
|
|
567
|
+
if @sender.phi_failure_detector
|
|
520
568
|
phi = @failure.phi(now)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
@log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, phi: phi
|
|
569
|
+
if phi > @sender.phi_threshold
|
|
570
|
+
@log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, phi: phi, phi_threshold: @sender.phi_threshold
|
|
524
571
|
@available = false
|
|
525
572
|
@resolved_host = nil # expire cached host
|
|
526
573
|
@failure.clear
|
|
527
574
|
return true
|
|
528
575
|
end
|
|
529
576
|
end
|
|
530
|
-
|
|
577
|
+
false
|
|
531
578
|
end
|
|
532
579
|
|
|
533
580
|
def heartbeat(detect=true)
|
|
534
581
|
now = Time.now.to_f
|
|
535
582
|
@failure.add(now)
|
|
536
|
-
|
|
537
|
-
if detect && !@available && @failure.sample_size > @conf.recover_sample_size
|
|
583
|
+
if detect && !@available && @failure.sample_size > @sender.recover_sample_size
|
|
538
584
|
@available = true
|
|
539
585
|
@log.warn "recovered forwarding server '#{@name}'", host: @host, port: @port
|
|
540
|
-
|
|
586
|
+
true
|
|
541
587
|
else
|
|
542
|
-
|
|
588
|
+
nil
|
|
543
589
|
end
|
|
544
590
|
end
|
|
545
591
|
|
|
592
|
+
# TODO: #to_msgpack(string) is deprecated
|
|
546
593
|
def to_msgpack(out = '')
|
|
547
594
|
[@host, @port, @weight, @available].to_msgpack(out)
|
|
548
595
|
end
|
|
596
|
+
|
|
597
|
+
def generate_salt
|
|
598
|
+
SecureRandom.hex(16)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def check_helo(message)
|
|
602
|
+
@log.debug "checking helo"
|
|
603
|
+
# ['HELO', options(hash)]
|
|
604
|
+
unless message.size == 2 && message[0] == 'HELO'
|
|
605
|
+
return false
|
|
606
|
+
end
|
|
607
|
+
opts = message[1] || {}
|
|
608
|
+
# make shared_key_check failed (instead of error) if protocol version mismatch exist
|
|
609
|
+
@shared_key_nonce = opts['nonce'] || ''
|
|
610
|
+
@authentication = opts['auth'] || ''
|
|
611
|
+
true
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def generate_ping
|
|
615
|
+
@log.debug "generating ping"
|
|
616
|
+
# ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + nonce + shared_key),
|
|
617
|
+
# username || '', sha512\_hex(auth\_salt + username + password) || '']
|
|
618
|
+
shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt)
|
|
619
|
+
.update(@sender.security.self_hostname)
|
|
620
|
+
.update(@shared_key_nonce)
|
|
621
|
+
.update(@shared_key)
|
|
622
|
+
.hexdigest
|
|
623
|
+
ping = ['PING', @sender.security.self_hostname, @shared_key_salt, shared_key_hexdigest]
|
|
624
|
+
if !@authentication.empty?
|
|
625
|
+
password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
|
|
626
|
+
ping.push(@username, password_hexdigest)
|
|
627
|
+
else
|
|
628
|
+
ping.push('','')
|
|
629
|
+
end
|
|
630
|
+
ping
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def check_pong(message)
|
|
634
|
+
@log.debug "checking pong"
|
|
635
|
+
# ['PONG', bool(authentication result), 'reason if authentication failed',
|
|
636
|
+
# self_hostname, sha512\_hex(salt + self_hostname + nonce + sharedkey)]
|
|
637
|
+
unless message.size == 5 && message[0] == 'PONG'
|
|
638
|
+
return false, 'invalid format for PONG message'
|
|
639
|
+
end
|
|
640
|
+
_pong, auth_result, reason, hostname, shared_key_hexdigest = message
|
|
641
|
+
|
|
642
|
+
unless auth_result
|
|
643
|
+
return false, 'authentication failed: ' + reason
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
if hostname == @sender.security.self_hostname
|
|
647
|
+
return false, 'same hostname between input and output: invalid configuration'
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
|
|
651
|
+
unless shared_key_hexdigest == clientside
|
|
652
|
+
return false, 'shared key mismatch'
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
return true, nil
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def on_read(sock, data)
|
|
659
|
+
@log.trace __callee__
|
|
660
|
+
|
|
661
|
+
case @state
|
|
662
|
+
when :helo
|
|
663
|
+
unless check_helo(data)
|
|
664
|
+
@log.warn "received invalid helo message from #{@name}"
|
|
665
|
+
disable! # shutdown
|
|
666
|
+
return
|
|
667
|
+
end
|
|
668
|
+
sock.write(generate_ping.to_msgpack)
|
|
669
|
+
@state = :pingpong
|
|
670
|
+
when :pingpong
|
|
671
|
+
succeeded, reason = check_pong(data)
|
|
672
|
+
unless succeeded
|
|
673
|
+
@log.warn "connection refused to #{@name}: #{reason}"
|
|
674
|
+
disable! # shutdown
|
|
675
|
+
return
|
|
676
|
+
end
|
|
677
|
+
@state = :established
|
|
678
|
+
@log.debug "connection established", host: @host, port: @port
|
|
679
|
+
else
|
|
680
|
+
raise "BUG: unknown session state: #{@state}"
|
|
681
|
+
end
|
|
682
|
+
end
|
|
549
683
|
end
|
|
550
684
|
|
|
551
685
|
# Override Node to disable heartbeat
|
|
@@ -625,33 +759,5 @@ module Fluent
|
|
|
625
759
|
@last = 0
|
|
626
760
|
end
|
|
627
761
|
end
|
|
628
|
-
|
|
629
|
-
## TODO
|
|
630
|
-
#class RPC
|
|
631
|
-
# def initialize(this)
|
|
632
|
-
# @this = this
|
|
633
|
-
# end
|
|
634
|
-
#
|
|
635
|
-
# def list_nodes
|
|
636
|
-
# @this.nodes
|
|
637
|
-
# end
|
|
638
|
-
#
|
|
639
|
-
# def list_fault_nodes
|
|
640
|
-
# list_nodes.select {|n| !n.available? }
|
|
641
|
-
# end
|
|
642
|
-
#
|
|
643
|
-
# def list_available_nodes
|
|
644
|
-
# list_nodes.select {|n| n.available? }
|
|
645
|
-
# end
|
|
646
|
-
#
|
|
647
|
-
# def add_node(name, host, port, weight)
|
|
648
|
-
# end
|
|
649
|
-
#
|
|
650
|
-
# def recover_node(host, port)
|
|
651
|
-
# end
|
|
652
|
-
#
|
|
653
|
-
# def remove_node(host, port)
|
|
654
|
-
# end
|
|
655
|
-
#end
|
|
656
762
|
end
|
|
657
763
|
end
|