fluentd 1.2.0.pre1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

@@ -0,0 +1,252 @@
1
+ require_relative '../helper'
2
+ require 'fluent/counter/store'
3
+ require 'fluent/time'
4
+ require 'timecop'
5
+
6
+ class CounterStoreTest < ::Test::Unit::TestCase
7
+ setup do
8
+ @name = 'key_name'
9
+ @scope = "server\tworker\tplugin"
10
+
11
+ # timecop isn't compatible with EventTime
12
+ t = Time.parse('2016-09-22 16:59:59 +0900')
13
+ Timecop.freeze(t)
14
+ @now = Fluent::EventTime.now
15
+ end
16
+
17
+ shutdown do
18
+ Timecop.return
19
+ end
20
+
21
+ def extract_value_from_counter(counter, key)
22
+ store = counter.instance_variable_get(:@storage).instance_variable_get(:@store)
23
+ store[key]
24
+ end
25
+
26
+ sub_test_case 'init' do
27
+ setup do
28
+ @reset_interval = 10
29
+ @store = Fluent::Counter::Store.new
30
+ @data = { 'name' => @name, 'reset_interval' => @reset_interval }
31
+ @key = Fluent::Counter::Store.gen_key(@scope, @name)
32
+ end
33
+
34
+ test 'create new value in the counter' do
35
+ v = @store.init(@key, @data)
36
+
37
+ assert_equal @name, v['name']
38
+ assert_equal @reset_interval, v['reset_interval']
39
+
40
+ v2 = extract_value_from_counter(@store, @key)
41
+ v2 = @store.send(:build_response, v2)
42
+ assert_equal v, v2
43
+ end
44
+
45
+ test 'raise an error when a passed key already exists' do
46
+ @store.init(@key, @data)
47
+
48
+ assert_raise Fluent::Counter::InvalidParams do
49
+ @store.init(@key, @data)
50
+ end
51
+ end
52
+
53
+ test 'return a value when passed key already exists and a ignore option is true' do
54
+ v = @store.init(@key, @data)
55
+ v1 = extract_value_from_counter(@store, @key)
56
+ v1 = @store.send(:build_response, v1)
57
+ v2 = @store.init(@key, @data, ignore: true)
58
+ assert_equal v, v2
59
+ assert_equal v1, v2
60
+ end
61
+ end
62
+
63
+ sub_test_case 'get' do
64
+ setup do
65
+ @store = Fluent::Counter::Store.new
66
+ data = { 'name' => @name, 'reset_interval' => 10 }
67
+ @key = Fluent::Counter::Store.gen_key(@scope, @name)
68
+ @store.init(@key, data)
69
+ end
70
+
71
+ test 'return a value from the counter' do
72
+ v = extract_value_from_counter(@store, @key)
73
+ expected = @store.send(:build_response, v)
74
+ assert_equal expected, @store.get(@key)
75
+ end
76
+
77
+ test 'return a raw value from the counter when raw option is true' do
78
+ v = extract_value_from_counter(@store, @key)
79
+ assert_equal v, @store.get(@key, raw: true)
80
+ end
81
+
82
+ test "return nil when a passed key doesn't exist" do
83
+ assert_equal nil, @store.get('unknown_key')
84
+ end
85
+
86
+ test "raise a error when when a passed key doesn't exist and raise_error option is true" do
87
+ assert_raise Fluent::Counter::UnknownKey do
88
+ @store.get('unknown_key', raise_error: true)
89
+ end
90
+ end
91
+ end
92
+
93
+ sub_test_case 'key?' do
94
+ setup do
95
+ @store = Fluent::Counter::Store.new
96
+ data = { 'name' => @name, 'reset_interval' => 10 }
97
+ @key = Fluent::Counter::Store.gen_key(@scope, @name)
98
+ @store.init(@key, data)
99
+ end
100
+
101
+ test 'return true when passed key exists' do
102
+ assert_true @store.key?(@key)
103
+ end
104
+
105
+ test "return false when passed key doesn't exist" do
106
+ assert_true !@store.key?('unknown_key')
107
+ end
108
+ end
109
+
110
+ sub_test_case 'delete' do
111
+ setup do
112
+ @store = Fluent::Counter::Store.new
113
+ data = { 'name' => @name, 'reset_interval' => 10 }
114
+ @key = Fluent::Counter::Store.gen_key(@scope, @name)
115
+ @init_value = @store.init(@key, data)
116
+ end
117
+
118
+ test 'delete a value from the counter' do
119
+ v = @store.delete(@key)
120
+ assert_equal @init_value, v
121
+ assert_nil extract_value_from_counter(@store, @key)
122
+ end
123
+
124
+ test "raise an error when passed key doesn't exist" do
125
+ assert_raise Fluent::Counter::UnknownKey do
126
+ @store.delete('unknown_key')
127
+ end
128
+ end
129
+ end
130
+
131
+ sub_test_case 'inc' do
132
+ setup do
133
+ @store = Fluent::Counter::Store.new
134
+ @init_data = { 'name' => @name, 'reset_interval' => 10 }
135
+ @travel_sec = 10
136
+ end
137
+
138
+ data(
139
+ positive: 10,
140
+ negative: -10
141
+ )
142
+ test 'increment or decrement a value in the counter' do |value|
143
+ key = Fluent::Counter::Store.gen_key(@scope, @name)
144
+ @store.init(key, @init_data)
145
+ Timecop.travel(@travel_sec)
146
+ v = @store.inc(key, { 'value' => value })
147
+
148
+ assert_equal value, v['total']
149
+ assert_equal value, v['current']
150
+ assert_equal @now, v['last_reset_at'] # last_reset_at doesn't change
151
+
152
+ v1 = extract_value_from_counter(@store, key)
153
+ v1 = @store.send(:build_response, v1)
154
+ assert_equal v, v1
155
+ end
156
+
157
+ test "raise an error when passed key doesn't exist" do
158
+ assert_raise Fluent::Counter::UnknownKey do
159
+ @store.inc('unknown_key', { 'value' => 1 })
160
+ end
161
+ end
162
+
163
+ test 'raise an error when a type of passed value is incompatible with a stored value' do
164
+ key1 = Fluent::Counter::Store.gen_key(@scope, @name)
165
+ key2 = Fluent::Counter::Store.gen_key(@scope, 'name2')
166
+ key3 = Fluent::Counter::Store.gen_key(@scope, 'name3')
167
+ v1 = @store.init(key1, @init_data.merge('type' => 'integer'))
168
+ v2 = @store.init(key2, @init_data.merge('type' => 'float'))
169
+ v3 = @store.init(key3, @init_data.merge('type' => 'numeric'))
170
+ assert_equal 'integer', v1['type']
171
+ assert_equal 'float', v2['type']
172
+ assert_equal 'numeric', v3['type']
173
+
174
+ assert_raise Fluent::Counter::InvalidParams do
175
+ @store.inc(key1, { 'value' => 1.1 })
176
+ end
177
+
178
+ assert_raise Fluent::Counter::InvalidParams do
179
+ @store.inc(key2, { 'value' => 1 })
180
+ end
181
+
182
+ assert_nothing_raised do
183
+ @store.inc(key3, { 'value' => 1 })
184
+ @store.inc(key3, { 'value' => 1.0 })
185
+ end
186
+ end
187
+ end
188
+
189
+ sub_test_case 'reset' do
190
+ setup do
191
+ @store = Fluent::Counter::Store.new
192
+ @travel_sec = 10
193
+
194
+ @inc_value = 10
195
+ @key = Fluent::Counter::Store.gen_key(@scope, @name)
196
+ @store.init(@key, { 'name' => @name, 'reset_interval' => 10 })
197
+ @store.inc(@key, { 'value' => 10 })
198
+ end
199
+
200
+ test 'reset a value in the counter' do
201
+ Timecop.travel(@travel_sec)
202
+
203
+ v = @store.reset(@key)
204
+ assert_equal @travel_sec, v['elapsed_time']
205
+ assert_true v['success']
206
+ counter = v['counter_data']
207
+
208
+ assert_equal @name, counter['name']
209
+ assert_equal @inc_value, counter['total']
210
+ assert_equal @inc_value, counter['current']
211
+ assert_equal 'numeric', counter['type']
212
+ assert_equal @now, counter['last_reset_at']
213
+ assert_equal 10, counter['reset_interval']
214
+
215
+ v1 = extract_value_from_counter(@store, @key)
216
+ assert_equal 0, v1['current']
217
+ assert_true v1['current'].is_a?(Integer)
218
+ assert_equal @inc_value, v1['total']
219
+ assert_equal (@now + @travel_sec), Fluent::EventTime.new(*v1['last_reset_at'])
220
+ assert_equal (@now + @travel_sec), Fluent::EventTime.new(*v1['last_modified_at'])
221
+ end
222
+
223
+ test 'reset a value after `reset_interval` passed' do
224
+ first_travel_sec = 5
225
+ Timecop.travel(first_travel_sec) # jump time less than reset_interval
226
+ v = @store.reset(@key)
227
+
228
+ assert_equal false, v['success']
229
+ assert_equal first_travel_sec, v['elapsed_time']
230
+ store = extract_value_from_counter(@store, @key)
231
+ assert_equal 10, store['current']
232
+ assert_equal @now, Fluent::EventTime.new(*store['last_reset_at'])
233
+
234
+ # time is passed greater than reset_interval
235
+ Timecop.travel(@travel_sec)
236
+ v = @store.reset(@key)
237
+ assert_true v['success']
238
+ assert_equal @travel_sec + first_travel_sec, v['elapsed_time']
239
+
240
+ v1 = extract_value_from_counter(@store, @key)
241
+ assert_equal 0, v1['current']
242
+ assert_equal (@now + @travel_sec + first_travel_sec), Fluent::EventTime.new(*v1['last_reset_at'])
243
+ assert_equal (@now + @travel_sec + first_travel_sec), Fluent::EventTime.new(*v1['last_modified_at'])
244
+ end
245
+
246
+ test "raise an error when passed key doesn't exist" do
247
+ assert_raise Fluent::Counter::UnknownKey do
248
+ @store.reset('unknown_key')
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,137 @@
1
+ require_relative '../helper'
2
+ require 'fluent/counter/validator'
3
+
4
+ class CounterValidatorTest < ::Test::Unit::TestCase
5
+ data(
6
+ invalid_name1: '',
7
+ invalid_name3: '_',
8
+ invalid_name4: 'A',
9
+ invalid_name5: 'a*',
10
+ invalid_name6: "a\t",
11
+ invalid_name7: "\n",
12
+ )
13
+ test 'invalid name' do |invalid_name|
14
+ assert_nil(Fluent::Counter::Validator::VALID_NAME =~ invalid_name)
15
+ end
16
+
17
+ sub_test_case 'request' do
18
+ test 'return an empty array' do
19
+ data = { 'id' => 0, 'method' => 'init' }
20
+ errors = Fluent::Counter::Validator.request(data)
21
+ assert_empty errors
22
+ end
23
+
24
+ data(
25
+ missing_id: [
26
+ { 'method' => 'init' },
27
+ { 'code' => 'invalid_request', 'message' => 'Request should include `id`' }
28
+ ],
29
+ missing_method: [
30
+ { 'id' => 0 },
31
+ { 'code' => 'invalid_request', 'message' => 'Request should include `method`' }
32
+ ],
33
+ invalid_method: [
34
+ { 'id' => 0, 'method' => "A\t" },
35
+ { 'code' => 'invalid_request', 'message' => '`method` is the invalid format' }
36
+ ],
37
+ unknown_method: [
38
+ { 'id' => 0, 'method' => 'unknown_method' },
39
+ { 'code' => 'method_not_found', 'message' => 'Unknown method name passed: unknown_method' }
40
+ ]
41
+ )
42
+ test 'return an error array' do |(data, expected_error)|
43
+ errors = Fluent::Counter::Validator.request(data)
44
+ assert_equal [expected_error], errors
45
+ end
46
+ end
47
+
48
+ sub_test_case 'call' do
49
+ test "return an error hash when passed method doesn't exist" do
50
+ v = Fluent::Counter::Validator.new(:unknown)
51
+ success, errors = v.call(['key1'])
52
+ assert_empty success
53
+ assert_equal 'internal_server_error', errors.first.to_hash['code']
54
+ end
55
+ end
56
+
57
+ test 'validate_empty!' do
58
+ v = Fluent::Counter::Validator.new(:empty)
59
+ success, errors = v.call([])
60
+ assert_empty success
61
+ assert_equal [Fluent::Counter::InvalidParams.new('One or more `params` are required')], errors
62
+ end
63
+ end
64
+
65
+ class CounterArrayValidatorTest < ::Test::Unit::TestCase
66
+ test 'validate_key!' do
67
+ ary = ['key', 100, '_']
68
+ error_expected = [
69
+ { 'code' => 'invalid_params', 'message' => 'The type of `key` should be String' },
70
+ { 'code' => 'invalid_params', 'message' => '`key` is the invalid format' }
71
+ ]
72
+ v = Fluent::Counter::ArrayValidator.new(:key)
73
+ valid_params, errors = v.call(ary)
74
+
75
+ assert_equal ['key'], valid_params
76
+ assert_equal error_expected, errors.map(&:to_hash)
77
+ end
78
+ end
79
+
80
+ class CounterHashValidatorTest < ::Test::Unit::TestCase
81
+ test 'validate_name!' do
82
+ hash = [
83
+ { 'name' => 'key' },
84
+ {},
85
+ { 'name' => 10 },
86
+ { 'name' => '_' }
87
+ ]
88
+ error_expected = [
89
+ { 'code' => 'invalid_params', 'message' => '`name` is required' },
90
+ { 'code' => 'invalid_params', 'message' => 'The type of `name` should be String' },
91
+ { 'code' => 'invalid_params', 'message' => '`name` is the invalid format' },
92
+ ]
93
+ v = Fluent::Counter::HashValidator.new(:name)
94
+ success, errors = v.call(hash)
95
+
96
+ assert_equal [{ 'name' => 'key' }], success
97
+ assert_equal error_expected, errors.map(&:to_hash)
98
+ end
99
+
100
+ test 'validate_value!' do
101
+ hash = [
102
+ { 'value' => 1 },
103
+ { 'value' => -1 },
104
+ {},
105
+ { 'value' => 'str' }
106
+ ]
107
+ error_expected = [
108
+ { 'code' => 'invalid_params', 'message' => '`value` is required' },
109
+ { 'code' => 'invalid_params', 'message' => 'The type of `value` type should be Numeric' },
110
+ ]
111
+ v = Fluent::Counter::HashValidator.new(:value)
112
+ valid_params, errors = v.call(hash)
113
+
114
+ assert_equal [{ 'value' => 1 }, { 'value' => -1 }], valid_params
115
+ assert_equal error_expected, errors.map(&:to_hash)
116
+ end
117
+
118
+ test 'validate_reset_interval!' do
119
+ hash = [
120
+ { 'reset_interval' => 1 },
121
+ { 'reset_interval' => 1.0 },
122
+ {},
123
+ { 'reset_interval' => -1 },
124
+ { 'reset_interval' => 'str' }
125
+ ]
126
+ error_expected = [
127
+ { 'code' => 'invalid_params', 'message' => '`reset_interval` is required' },
128
+ { 'code' => 'invalid_params', 'message' => '`reset_interval` should be a positive number' },
129
+ { 'code' => 'invalid_params', 'message' => 'The type of `reset_interval` should be Numeric' },
130
+ ]
131
+ v = Fluent::Counter::HashValidator.new(:reset_interval)
132
+ valid_params, errors = v.call(hash)
133
+
134
+ assert_equal [{ 'reset_interval' => 1 }, { 'reset_interval' => 1.0 }], valid_params
135
+ assert_equal error_expected.map(&:to_hash), errors.map(&:to_hash)
136
+ end
137
+ end
@@ -0,0 +1,271 @@
1
+ require_relative '../helper'
2
+ require 'fluent/plugin/output'
3
+ require 'fluent/plugin/buffer'
4
+ require 'fluent/event'
5
+ require 'fluent/error'
6
+
7
+ require 'json'
8
+ require 'time'
9
+ require 'timeout'
10
+ require 'timecop'
11
+
12
+
13
+ class BufferedOutputBackupTest < Test::Unit::TestCase
14
+ class BareOutput < Fluent::Plugin::Output
15
+ def register(name, &block)
16
+ instance_variable_set("@#{name}", block)
17
+ end
18
+ end
19
+ class DummyOutput < BareOutput
20
+ def initialize
21
+ super
22
+ @process = nil
23
+ @format = nil
24
+ @write = nil
25
+ @try_write = nil
26
+ end
27
+ def prefer_buffered_processing
28
+ true
29
+ end
30
+ def prefer_delayed_commit
31
+ false
32
+ end
33
+ def process(tag, es)
34
+ @process ? @process.call(tag, es) : nil
35
+ end
36
+ def format(tag, time, record)
37
+ [tag, time.to_i, record].to_json + "\n"
38
+ end
39
+ def write(chunk)
40
+ @write ? @write.call(chunk) : nil
41
+ end
42
+ def try_write(chunk)
43
+ @try_write ? @try_write.call(chunk) : nil
44
+ end
45
+ end
46
+ class DummyOutputForSecondary < BareOutput
47
+ def initialize
48
+ super
49
+ @process = nil
50
+ @format = nil
51
+ @write = nil
52
+ @try_write = nil
53
+ end
54
+ def prefer_buffered_processing
55
+ true
56
+ end
57
+ def prefer_delayed_commit
58
+ false
59
+ end
60
+ def process(tag, es)
61
+ @process ? @process.call(tag, es) : nil
62
+ end
63
+ def format(tag, time, record)
64
+ [tag, time.to_i, record].to_json + "\n"
65
+ end
66
+ def write(chunk)
67
+ @write ? @write.call(chunk) : nil
68
+ end
69
+ def try_write(chunk)
70
+ @try_write ? @try_write.call(chunk) : nil
71
+ end
72
+ end
73
+ class DummyAsyncOutputForSecondary < BareOutput
74
+ def initialize
75
+ super
76
+ @process = nil
77
+ @format = nil
78
+ @write = nil
79
+ @try_write = nil
80
+ end
81
+ def prefer_buffered_processing
82
+ true
83
+ end
84
+ def prefer_delayed_commit
85
+ true
86
+ end
87
+ def process(tag, es)
88
+ @process ? @process.call(tag, es) : nil
89
+ end
90
+ def format(tag, time, record)
91
+ [tag, time.to_i, record].to_json + "\n"
92
+ end
93
+ def write(chunk)
94
+ @write ? @write.call(chunk) : nil
95
+ end
96
+ def try_write(chunk)
97
+ @try_write ? @try_write.call(chunk) : nil
98
+ end
99
+ end
100
+
101
+ TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/bu#{ENV['TEST_ENV_NUMBER']}")
102
+
103
+ def create_output
104
+ DummyOutput.new
105
+ end
106
+ def create_metadata(timekey: nil, tag: nil, variables: nil)
107
+ Fluent::Plugin::Buffer::Metadata.new(timekey, tag, variables)
108
+ end
109
+ def waiting(seconds)
110
+ begin
111
+ Timeout.timeout(seconds) do
112
+ yield
113
+ end
114
+ rescue Timeout::Error
115
+ STDERR.print(*@i.log.out.logs)
116
+ raise
117
+ end
118
+ end
119
+
120
+ def dummy_event_stream
121
+ Fluent::ArrayEventStream.new([
122
+ [ event_time('2016-04-13 18:33:00'), {"name" => "moris", "age" => 36, "message" => "data1"} ],
123
+ [ event_time('2016-04-13 18:33:13'), {"name" => "moris", "age" => 36, "message" => "data2"} ],
124
+ [ event_time('2016-04-13 18:33:32'), {"name" => "moris", "age" => 36, "message" => "data3"} ],
125
+ ])
126
+ end
127
+
128
+ setup do
129
+ @i = create_output
130
+ FileUtils.rm_rf(TMP_DIR)
131
+ FileUtils.mkdir_p(TMP_DIR)
132
+
133
+ Fluent::Plugin.register_output('backup_output', DummyOutput)
134
+ Fluent::Plugin.register_output('backup_output2', DummyOutputForSecondary)
135
+ Fluent::Plugin.register_output('backup_async_output', DummyAsyncOutputForSecondary)
136
+ end
137
+
138
+ teardown do
139
+ if @i
140
+ @i.stop unless @i.stopped?
141
+ @i.before_shutdown unless @i.before_shutdown?
142
+ @i.shutdown unless @i.shutdown?
143
+ @i.after_shutdown unless @i.after_shutdown?
144
+ @i.close unless @i.closed?
145
+ @i.terminate unless @i.terminated?
146
+ end
147
+ Timecop.return
148
+ end
149
+
150
+ sub_test_case 'buffered output for broken chunks' do
151
+ def flush_chunks
152
+ @i.start
153
+ @i.after_start
154
+
155
+ @i.interrupt_flushes
156
+
157
+ now = Time.parse('2016-04-13 18:33:30 -0700')
158
+ Timecop.freeze(now)
159
+ @i.emit_events("test.tag.1", dummy_event_stream())
160
+ now = Time.parse('2016-04-13 18:33:32 -0700')
161
+ Timecop.freeze(now)
162
+
163
+ @i.enqueue_thread_wait
164
+ @i.flush_thread_wakeup
165
+ waiting(4) { Thread.pass until @i.write_count > 0 }
166
+
167
+ assert { @i.write_count > 0 }
168
+ Timecop.freeze(now)
169
+ @i.flush_thread_wakeup
170
+ end
171
+
172
+ test 'backup chunk without secondary' do
173
+ Fluent::SystemConfig.overwrite_system_config('root_dir' => TMP_DIR) do
174
+ id = 'backup_test'
175
+ hash = {
176
+ 'flush_interval' => 1,
177
+ 'flush_thread_burst_interval' => 0.1,
178
+ }
179
+ chunk_id = nil
180
+ @i.configure(config_element('ROOT', '', {'@id' => id}, [config_element('buffer', 'tag', hash)]))
181
+ @i.register(:write) { |chunk|
182
+ chunk_id = chunk.unique_id;
183
+ raise Fluent::UnrecoverableError, "yay, your #write must fail"
184
+ }
185
+
186
+ flush_chunks
187
+
188
+ target = "#{TMP_DIR}/backup/worker0/#{id}/#{@i.dump_unique_id_hex(chunk_id)}.log"
189
+ assert_true File.exist?(target)
190
+ logs = @i.log.out.logs
191
+ assert { logs.any? { |l| l.include?("got unrecoverable error in primary and no secondary") } }
192
+ end
193
+ end
194
+
195
+ test 'backup chunk with same type secondary' do
196
+ Fluent::SystemConfig.overwrite_system_config('root_dir' => TMP_DIR) do
197
+ id = 'backup_test_with_same_secondary'
198
+ hash = {
199
+ 'flush_interval' => 1,
200
+ 'flush_thread_burst_interval' => 0.1,
201
+ }
202
+ chunk_id = nil
203
+ secconf = config_element('secondary','',{'@type' => 'backup_output'})
204
+ @i.configure(config_element('ROOT', '', {'@id' => id}, [config_element('buffer', 'tag', hash), secconf]))
205
+ @i.register(:write) { |chunk|
206
+ chunk_id = chunk.unique_id;
207
+ raise Fluent::UnrecoverableError, "yay, your #write must fail"
208
+ }
209
+
210
+ flush_chunks
211
+
212
+ target = "#{TMP_DIR}/backup/worker0/#{id}/#{@i.dump_unique_id_hex(chunk_id)}.log"
213
+ assert_true File.exist?(target)
214
+ logs = @i.log.out.logs
215
+ assert { logs.any? { |l| l.include?("got unrecoverable error in primary and secondary type is same as primary") } }
216
+ end
217
+ end
218
+
219
+ test 'backup chunk with different type secondary' do
220
+ Fluent::SystemConfig.overwrite_system_config('root_dir' => TMP_DIR) do
221
+ id = 'backup_test_with_diff_secondary'
222
+ hash = {
223
+ 'flush_interval' => 1,
224
+ 'flush_thread_burst_interval' => 0.1,
225
+ }
226
+ chunk_id = nil
227
+ secconf = config_element('secondary','',{'@type' => 'backup_output2'})
228
+ @i.configure(config_element('ROOT', '', {'@id' => id}, [config_element('buffer', 'tag', hash), secconf]))
229
+ @i.register(:write) { |chunk|
230
+ chunk_id = chunk.unique_id;
231
+ raise Fluent::UnrecoverableError, "yay, your #write must fail"
232
+ }
233
+ @i.secondary.register(:write) { |chunk|
234
+ raise Fluent::UnrecoverableError, "yay, your secondary #write must fail"
235
+ }
236
+
237
+ flush_chunks
238
+
239
+ target = "#{TMP_DIR}/backup/worker0/#{id}/#{@i.dump_unique_id_hex(chunk_id)}.log"
240
+ assert_true File.exist?(target)
241
+ logs = @i.log.out.logs
242
+ assert { logs.any? { |l| l.include?("got unrecoverable error in primary. Skip retry and flush chunk to secondary") } }
243
+ assert { logs.any? { |l| l.include?("got an error in secondary for unrecoverable error") } }
244
+ end
245
+ end
246
+
247
+ test 'backup chunk with async secondary' do
248
+ Fluent::SystemConfig.overwrite_system_config('root_dir' => TMP_DIR) do
249
+ id = 'backup_test_with_diff_secondary'
250
+ hash = {
251
+ 'flush_interval' => 1,
252
+ 'flush_thread_burst_interval' => 0.1,
253
+ }
254
+ chunk_id = nil
255
+ secconf = config_element('secondary','',{'@type' => 'backup_async_output'})
256
+ @i.configure(config_element('ROOT', '', {'@id' => id}, [config_element('buffer', 'tag', hash), secconf]))
257
+ @i.register(:write) { |chunk|
258
+ chunk_id = chunk.unique_id;
259
+ raise Fluent::UnrecoverableError, "yay, your #write must fail"
260
+ }
261
+
262
+ flush_chunks
263
+
264
+ target = "#{TMP_DIR}/backup/worker0/#{id}/#{@i.dump_unique_id_hex(chunk_id)}.log"
265
+ assert_true File.exist?(target)
266
+ logs = @i.log.out.logs
267
+ assert { logs.any? { |l| l.include?("got unrecoverable error in primary and secondary is async output") } }
268
+ end
269
+ end
270
+ end
271
+ end