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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +77 -1131
  3. data/Rakefile +15 -0
  4. data/appveyor.yml +2 -2
  5. data/example/in_forward.conf +3 -0
  6. data/example/in_forward_client.conf +37 -0
  7. data/example/in_forward_shared_key.conf +15 -0
  8. data/example/in_forward_users.conf +24 -0
  9. data/example/multi_filters.conf +61 -0
  10. data/example/out_forward.conf +13 -13
  11. data/example/out_forward_client.conf +109 -0
  12. data/example/out_forward_shared_key.conf +36 -0
  13. data/example/out_forward_users.conf +65 -0
  14. data/example/{out_buffered_null.conf → out_null.conf} +10 -6
  15. data/example/secondary_file.conf +41 -0
  16. data/fluentd.gemspec +19 -16
  17. data/lib/fluent/agent.rb +5 -0
  18. data/lib/fluent/command/debug.rb +4 -4
  19. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +2 -0
  20. data/lib/fluent/compat/output.rb +5 -2
  21. data/lib/fluent/config/configure_proxy.rb +26 -5
  22. data/lib/fluent/config/error.rb +3 -0
  23. data/lib/fluent/config/section.rb +15 -0
  24. data/lib/fluent/event_router.rb +77 -4
  25. data/lib/fluent/plugin/base.rb +12 -3
  26. data/lib/fluent/plugin/buffer.rb +5 -1
  27. data/lib/fluent/plugin/filter.rb +48 -6
  28. data/lib/fluent/plugin/filter_record_transformer.rb +3 -1
  29. data/lib/fluent/plugin/filter_stdout.rb +0 -4
  30. data/lib/fluent/plugin/in_debug_agent.rb +5 -5
  31. data/lib/fluent/plugin/in_dummy.rb +9 -1
  32. data/lib/fluent/plugin/in_forward.rb +330 -62
  33. data/lib/fluent/plugin/in_monitor_agent.rb +31 -77
  34. data/lib/fluent/plugin/in_tail.rb +76 -92
  35. data/lib/fluent/plugin/multi_output.rb +4 -0
  36. data/lib/fluent/plugin/out_forward.rb +328 -222
  37. data/lib/fluent/plugin/out_null.rb +37 -0
  38. data/lib/fluent/plugin/out_secondary_file.rb +128 -0
  39. data/lib/fluent/plugin/out_stdout.rb +38 -2
  40. data/lib/fluent/plugin/output.rb +29 -6
  41. data/lib/fluent/plugin/storage_local.rb +16 -0
  42. data/lib/fluent/plugin_helper/timer.rb +6 -1
  43. data/lib/fluent/root_agent.rb +4 -1
  44. data/lib/fluent/supervisor.rb +62 -9
  45. data/lib/fluent/test/base.rb +3 -0
  46. data/lib/fluent/test/driver/base.rb +5 -0
  47. data/lib/fluent/test/formatter_test.rb +2 -0
  48. data/lib/fluent/test/parser_test.rb +2 -0
  49. data/lib/fluent/test/startup_shutdown.rb +33 -0
  50. data/lib/fluent/version.rb +1 -1
  51. data/lib/fluent/winsvc.rb +1 -2
  52. data/test/compat/test_calls_super.rb +2 -0
  53. data/test/config/test_configurable.rb +88 -0
  54. data/test/config/test_types.rb +7 -3
  55. data/test/plugin/test_filter.rb +121 -23
  56. data/test/plugin/test_filter_record_transformer.rb +22 -6
  57. data/test/plugin/test_in_debug_agent.rb +2 -2
  58. data/test/plugin/test_in_forward.rb +920 -407
  59. data/test/plugin/test_in_monitor_agent.rb +333 -0
  60. data/test/plugin/test_in_tail.rb +686 -595
  61. data/test/plugin/test_out_forward.rb +150 -207
  62. data/test/plugin/test_out_null.rb +85 -9
  63. data/test/plugin/test_out_secondary_file.rb +432 -0
  64. data/test/plugin/test_out_stdout.rb +143 -45
  65. data/test/plugin/test_output.rb +53 -0
  66. data/test/plugin/test_output_as_buffered.rb +13 -0
  67. data/test/plugin/test_output_as_buffered_overflow.rb +3 -0
  68. data/test/plugin/test_output_as_buffered_retries.rb +11 -0
  69. data/test/plugin/test_output_as_buffered_secondary.rb +12 -0
  70. data/test/plugin/test_output_as_standard.rb +12 -0
  71. data/test/plugin_helper/test_compat_parameters.rb +2 -0
  72. data/test/plugin_helper/test_timer.rb +31 -0
  73. data/test/test_event_router.rb +87 -0
  74. data/test/test_filter.rb +48 -6
  75. data/test/test_log.rb +5 -2
  76. data/test/test_output.rb +41 -1
  77. data/test/test_plugin_classes.rb +17 -9
  78. data/test/test_root_agent.rb +188 -0
  79. metadata +62 -68
  80. data/lib/fluent/plugin/out_buffered_null.rb +0 -59
  81. data/lib/fluent/plugin/out_buffered_stdout.rb +0 -70
  82. data/test/plugin/test_out_buffered_null.rb +0 -79
  83. 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, default: :tcp do |val|
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 # 0 means do not wait for ack responses
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
- # backward compatibility
93
- config_param :port, :integer, default: LISTEN_PORT
94
- config_param :host, :string, default: nil
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
- # backward compatibility
100
- if host = conf['host']
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
- conf.elements.each {|e|
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
- node_conf = NodeConfig.new(name, host, port, weight, standby, failure,
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(log, node_conf)
134
+ @nodes << NoneHeartbeatNode.new(self, server, failure: failure)
141
135
  else
142
- @nodes << Node.new(log, node_conf)
136
+ @nodes << Node.new(self, server, failure: failure)
143
137
  end
144
- log.info "adding forwarding server '#{name}'", host: host, port: port, weight: weight, plugin_id: plugin_id
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(node, tag, chunk)
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
- #log.trace "sending heartbeat #{n.host}:#{n.port} on #{@heartbeat_type}"
394
- if @heartbeat_type == :tcp
395
- send_heartbeat_tcp(n)
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
- # TODO log
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(log, conf)
442
- @log = log
443
- @conf = conf
444
- @name = @conf.name
445
- @host = @conf.host
446
- @port = @conf.port
447
- @weight = @conf.weight
448
- @failure = @conf.failure
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
- attr_reader :conf
457
- attr_reader :name, :host, :port, :weight
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
- @conf.standby
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 @conf.expire_dns_cache
522
+ case @sender.expire_dns_cache
475
523
  when 0
476
524
  # cache is disabled
477
- return resolve_dns!
525
+ resolve_dns!
478
526
 
479
527
  when nil
480
528
  # persistent cache
481
- return @resolved_host ||= resolve_dns!
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 >= @conf.expire_dns_cache
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
- return rh
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 = @conf.dns_round_robin ? addrinfo_list.sample : addrinfo_list.first
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 @conf.phi_failure_detector
567
+ if @sender.phi_failure_detector
520
568
  phi = @failure.phi(now)
521
- #$log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
522
- if phi > @conf.phi_threshold
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
- return false
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
- #@log.trace "heartbeat from '#{@name}'", :host=>@host, :port=>@port, :available=>@available, :sample_size=>@failure.sample_size
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
- return true
586
+ true
541
587
  else
542
- return nil
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