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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/MAINTAINERS.md +7 -6
- data/example/counter.conf +18 -0
- data/example/secondary_file.conf +3 -2
- data/lib/fluent/counter.rb +23 -0
- data/lib/fluent/counter/base_socket.rb +46 -0
- data/lib/fluent/counter/client.rb +288 -0
- data/lib/fluent/counter/error.rb +65 -0
- data/lib/fluent/counter/mutex_hash.rb +163 -0
- data/lib/fluent/counter/server.rb +273 -0
- data/lib/fluent/counter/store.rb +205 -0
- data/lib/fluent/counter/validator.rb +145 -0
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/log.rb +7 -0
- data/lib/fluent/plugin/filter_grep.rb +20 -24
- data/lib/fluent/plugin/output.rb +50 -1
- data/lib/fluent/plugin_helper.rb +1 -0
- data/lib/fluent/plugin_helper/counter.rb +51 -0
- data/lib/fluent/plugin_helper/retry_state.rb +15 -7
- data/lib/fluent/plugin_helper/server.rb +3 -0
- data/lib/fluent/supervisor.rb +30 -5
- data/lib/fluent/system_config.rb +26 -2
- data/lib/fluent/version.rb +1 -1
- data/test/counter/test_client.rb +549 -0
- data/test/counter/test_error.rb +44 -0
- data/test/counter/test_mutex_hash.rb +179 -0
- data/test/counter/test_server.rb +583 -0
- data/test/counter/test_store.rb +252 -0
- data/test/counter/test_validator.rb +137 -0
- data/test/plugin/test_output_as_buffered_backup.rb +271 -0
- data/test/plugin_helper/test_retry_state.rb +20 -0
- data/test/test_supervisor.rb +20 -0
- metadata +29 -5
@@ -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
|