fluent-plugin-gcloud-pubsub-universe-domain 2.0.0
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 +7 -0
- data/.github/workflows/release.yml +35 -0
- data/CHANGELOG.md +168 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/README.md +225 -0
- data/Rakefile +13 -0
- data/fluent-plugin-gcloud-pubsub-universe-domain.gemspec +25 -0
- data/fluent.conf +42 -0
- data/lib/fluent/plugin/gcloud_pubsub/client.rb +91 -0
- data/lib/fluent/plugin/in_gcloud_pubsub.rb +273 -0
- data/lib/fluent/plugin/out_gcloud_pubsub.rb +161 -0
- data/test/helper.rb +23 -0
- data/test/plugin/test_in_gcloud_pubsub.rb +424 -0
- data/test/plugin/test_out_gcloud_pubsub.rb +375 -0
- metadata +148 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
require_relative "../helper"
|
|
5
|
+
require 'fluent/plugin/compressable'
|
|
6
|
+
require "fluent/test/driver/input"
|
|
7
|
+
|
|
8
|
+
class GcloudPubSubInputTest < Test::Unit::TestCase
|
|
9
|
+
CONFIG = %[
|
|
10
|
+
tag test
|
|
11
|
+
project project-test
|
|
12
|
+
topic topic-test
|
|
13
|
+
subscription subscription-test
|
|
14
|
+
key key-test
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
DEFAULT_HOST = '127.0.0.1'
|
|
18
|
+
DEFAULT_PORT = 24680
|
|
19
|
+
|
|
20
|
+
class DummyInvalidMsgData
|
|
21
|
+
def data
|
|
22
|
+
'foo:bar'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
class DummyInvalidMessage
|
|
26
|
+
def message
|
|
27
|
+
DummyInvalidMsgData.new
|
|
28
|
+
end
|
|
29
|
+
def attributes
|
|
30
|
+
{"attr_1" => "a", "attr_2" => "b"}
|
|
31
|
+
end
|
|
32
|
+
def ack_id
|
|
33
|
+
"ack-id"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_driver(conf=CONFIG)
|
|
38
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::GcloudPubSubInput).configure(conf)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def http_get(path)
|
|
42
|
+
http = Net::HTTP.new(DEFAULT_HOST, DEFAULT_PORT)
|
|
43
|
+
req = Net::HTTP::Get.new(path, {'Content-Type' => 'application/x-www-form-urlencoded'})
|
|
44
|
+
http.request(req)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
setup do
|
|
48
|
+
Fluent::Test.setup
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sub_test_case 'configure' do
|
|
52
|
+
test 'all params are configured' do
|
|
53
|
+
d = create_driver(%[
|
|
54
|
+
tag test
|
|
55
|
+
project project-test
|
|
56
|
+
topic topic-test
|
|
57
|
+
subscription subscription-test
|
|
58
|
+
key key-test
|
|
59
|
+
max_messages 1000
|
|
60
|
+
return_immediately true
|
|
61
|
+
pull_interval 2
|
|
62
|
+
pull_threads 3
|
|
63
|
+
attribute_keys attr-test
|
|
64
|
+
enable_rpc true
|
|
65
|
+
rpc_bind 127.0.0.1
|
|
66
|
+
rpc_port 24681
|
|
67
|
+
universe_domain universe-domain-test
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
assert_equal('test', d.instance.tag)
|
|
71
|
+
assert_equal('project-test', d.instance.project)
|
|
72
|
+
assert_equal('topic-test', d.instance.topic)
|
|
73
|
+
assert_equal('subscription-test', d.instance.subscription)
|
|
74
|
+
assert_equal('key-test', d.instance.key)
|
|
75
|
+
assert_equal(2.0, d.instance.pull_interval)
|
|
76
|
+
assert_equal(1000, d.instance.max_messages)
|
|
77
|
+
assert_equal(true, d.instance.return_immediately)
|
|
78
|
+
assert_equal(3, d.instance.pull_threads)
|
|
79
|
+
assert_equal(['attr-test'], d.instance.attribute_keys)
|
|
80
|
+
assert_equal(true, d.instance.enable_rpc)
|
|
81
|
+
assert_equal('127.0.0.1', d.instance.rpc_bind)
|
|
82
|
+
assert_equal(24681, d.instance.rpc_port)
|
|
83
|
+
assert_equal('universe-domain-test', d.instance.universe_domain)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
test 'default values are configured' do
|
|
87
|
+
d = create_driver
|
|
88
|
+
assert_equal(5.0, d.instance.pull_interval)
|
|
89
|
+
assert_equal(100, d.instance.max_messages)
|
|
90
|
+
assert_equal(true, d.instance.return_immediately)
|
|
91
|
+
assert_equal(1, d.instance.pull_threads)
|
|
92
|
+
assert_equal([], d.instance.attribute_keys)
|
|
93
|
+
assert_equal(false, d.instance.enable_rpc)
|
|
94
|
+
assert_equal('0.0.0.0', d.instance.rpc_bind)
|
|
95
|
+
assert_equal(24680, d.instance.rpc_port)
|
|
96
|
+
assert_equal(nil, d.instance.universe_domain)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sub_test_case 'start' do
|
|
101
|
+
setup do
|
|
102
|
+
@pubsub_mock = mock!
|
|
103
|
+
stub(Google::Cloud::Pubsub).new { @pubsub_mock }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
test '40x error occurred on connecting to Pub/Sub' do
|
|
107
|
+
@pubsub_mock.subscriber('subscription-test').once do
|
|
108
|
+
raise Google::Cloud::NotFoundError.new('TEST')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
d = create_driver
|
|
112
|
+
assert_raise Google::Cloud::NotFoundError do
|
|
113
|
+
d.run {}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
test '50x error occurred on connecting to Pub/Sub' do
|
|
118
|
+
@pubsub_mock.subscriber('subscription-test').once do
|
|
119
|
+
raise Google::Cloud::UnavailableError.new('TEST')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
d = create_driver
|
|
123
|
+
assert_raise Google::Cloud::UnavailableError do
|
|
124
|
+
d.run {}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
sub_test_case 'emit' do
|
|
130
|
+
class DummyMsgData
|
|
131
|
+
def data
|
|
132
|
+
'{"foo": "bar"}'
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
class DummyMessage
|
|
136
|
+
def message
|
|
137
|
+
DummyMsgData.new
|
|
138
|
+
end
|
|
139
|
+
def attributes
|
|
140
|
+
{"attr_1" => "a", "attr_2" => "b"}
|
|
141
|
+
end
|
|
142
|
+
def ack_id
|
|
143
|
+
"ack-id"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class DummyMsgDataWithTagKey
|
|
148
|
+
def initialize(tag)
|
|
149
|
+
@tag = tag
|
|
150
|
+
end
|
|
151
|
+
def data
|
|
152
|
+
'{"foo": "bar", "test_tag_key": "' + @tag + '"}'
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
class DummyMessageWithTagKey
|
|
156
|
+
def initialize(tag)
|
|
157
|
+
@tag = tag
|
|
158
|
+
end
|
|
159
|
+
def message
|
|
160
|
+
DummyMsgDataWithTagKey.new @tag
|
|
161
|
+
end
|
|
162
|
+
def attributes
|
|
163
|
+
{"attr_1" => "a", "attr_2" => "b"}
|
|
164
|
+
end
|
|
165
|
+
def ack_id
|
|
166
|
+
"ack-id"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class DummyMsgDataGzipped
|
|
171
|
+
include Fluent::Plugin::Compressable
|
|
172
|
+
|
|
173
|
+
def data
|
|
174
|
+
compress '{"foo": "bar"}'
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
class DummyMessageGzipped
|
|
178
|
+
def message
|
|
179
|
+
DummyMsgDataGzipped.new
|
|
180
|
+
end
|
|
181
|
+
def attributes
|
|
182
|
+
{"attr_1" => "a", "attr_2" => "b"}
|
|
183
|
+
end
|
|
184
|
+
def ack_id
|
|
185
|
+
"ack-id"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
setup do
|
|
190
|
+
@subscriber = mock!
|
|
191
|
+
@pubsub_mock = mock!.subscriber('subscription-test') { @subscriber }
|
|
192
|
+
stub(Google::Cloud::Pubsub).new { @pubsub_mock }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
test 'empty' do
|
|
196
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { [] }
|
|
197
|
+
@subscriber.acknowledge.times(0)
|
|
198
|
+
|
|
199
|
+
d = create_driver
|
|
200
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
201
|
+
|
|
202
|
+
assert_true d.events.empty?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
test 'simple' do
|
|
206
|
+
messages = Array.new(1, DummyMessage.new)
|
|
207
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
208
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
209
|
+
|
|
210
|
+
d = create_driver
|
|
211
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
212
|
+
emits = d.events
|
|
213
|
+
|
|
214
|
+
assert(1 <= emits.length)
|
|
215
|
+
emits.each do |tag, time, record|
|
|
216
|
+
assert_equal("test", tag)
|
|
217
|
+
assert_equal({"foo" => "bar"}, record)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
test 'multithread' do
|
|
222
|
+
messages = Array.new(1, DummyMessage.new)
|
|
223
|
+
@subscriber.pull(immediate: true, max: 100).at_least(2) { messages }
|
|
224
|
+
@subscriber.acknowledge(messages).at_least(2)
|
|
225
|
+
|
|
226
|
+
d = create_driver("#{CONFIG}\npull_threads 2")
|
|
227
|
+
d.run(expect_emits: 2, timeout: 1)
|
|
228
|
+
emits = d.events
|
|
229
|
+
|
|
230
|
+
assert(2 <= emits.length)
|
|
231
|
+
emits.each do |tag, time, record|
|
|
232
|
+
assert_equal("test", tag)
|
|
233
|
+
assert_equal({"foo" => "bar"}, record)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
test 'with tag_key' do
|
|
238
|
+
messages = [
|
|
239
|
+
DummyMessageWithTagKey.new('tag1'),
|
|
240
|
+
DummyMessageWithTagKey.new('tag2'),
|
|
241
|
+
DummyMessage.new
|
|
242
|
+
]
|
|
243
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
244
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
245
|
+
|
|
246
|
+
d = create_driver("#{CONFIG}\ntag_key test_tag_key")
|
|
247
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
248
|
+
emits = d.events
|
|
249
|
+
|
|
250
|
+
assert(3 <= emits.length)
|
|
251
|
+
# test tag
|
|
252
|
+
assert_equal("tag1", emits[0][0])
|
|
253
|
+
assert_equal("tag2", emits[1][0])
|
|
254
|
+
assert_equal("test", emits[2][0])
|
|
255
|
+
# test record
|
|
256
|
+
emits.each do |tag, time, record|
|
|
257
|
+
assert_equal({"foo" => "bar"}, record)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
test 'invalid messages with parse_error_action exception ' do
|
|
262
|
+
messages = Array.new(1, DummyInvalidMessage.new)
|
|
263
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
264
|
+
@subscriber.acknowledge.times(0)
|
|
265
|
+
|
|
266
|
+
d = create_driver
|
|
267
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
268
|
+
assert_true d.events.empty?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
test 'with attributes' do
|
|
272
|
+
messages = Array.new(1, DummyMessage.new)
|
|
273
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
274
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
275
|
+
|
|
276
|
+
d = create_driver("#{CONFIG}\nattribute_keys attr_1")
|
|
277
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
278
|
+
emits = d.events
|
|
279
|
+
|
|
280
|
+
assert(1 <= emits.length)
|
|
281
|
+
emits.each do |tag, time, record|
|
|
282
|
+
assert_equal("test", tag)
|
|
283
|
+
assert_equal({"foo" => "bar", "attr_1" => "a"}, record)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
test 'with gzip decompression' do
|
|
288
|
+
messages = Array.new(1, DummyMessageGzipped.new)
|
|
289
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
290
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
291
|
+
|
|
292
|
+
d = create_driver("#{CONFIG}\ndecompression gzip")
|
|
293
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
294
|
+
emits = d.events
|
|
295
|
+
|
|
296
|
+
assert_equal(1, emits.length)
|
|
297
|
+
emits.each do |tag, time, record|
|
|
298
|
+
assert_equal("test", tag)
|
|
299
|
+
assert_equal({"foo" => "bar"}, record)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
test 'invalid messages with parse_error_action warning' do
|
|
304
|
+
messages = Array.new(1, DummyInvalidMessage.new)
|
|
305
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
306
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
307
|
+
|
|
308
|
+
d = create_driver("#{CONFIG}\nparse_error_action warning")
|
|
309
|
+
d.run(expect_emits: 1, timeout: 3)
|
|
310
|
+
assert_true d.events.empty?
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
test 'retry if raised error' do
|
|
314
|
+
class UnknownError < StandardError
|
|
315
|
+
end
|
|
316
|
+
@subscriber.pull(immediate: true, max: 100).at_least(2) { raise UnknownError.new('test') }
|
|
317
|
+
@subscriber.acknowledge.times(0)
|
|
318
|
+
|
|
319
|
+
d = create_driver(CONFIG + 'pull_interval 0.5')
|
|
320
|
+
d.run(expect_emits: 1, timeout: 0.8)
|
|
321
|
+
|
|
322
|
+
assert_equal(0.5, d.instance.pull_interval)
|
|
323
|
+
assert_true d.events.empty?
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
test 'retry if raised RetryableError on pull' do
|
|
327
|
+
@subscriber.pull(immediate: true, max: 100).at_least(2) { raise Google::Cloud::UnavailableError.new('TEST') }
|
|
328
|
+
@subscriber.acknowledge.times(0)
|
|
329
|
+
|
|
330
|
+
d = create_driver("#{CONFIG}\npull_interval 0.5")
|
|
331
|
+
d.run(expect_emits: 1, timeout: 0.8)
|
|
332
|
+
|
|
333
|
+
assert_equal(0.5, d.instance.pull_interval)
|
|
334
|
+
assert_true d.events.empty?
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
test 'retry if raised RetryableError on acknowledge' do
|
|
338
|
+
messages = Array.new(1, DummyMessage.new)
|
|
339
|
+
@subscriber.pull(immediate: true, max: 100).at_least(2) { messages }
|
|
340
|
+
@subscriber.acknowledge(messages).at_least(2) { raise Google::Cloud::UnavailableError.new('TEST') }
|
|
341
|
+
|
|
342
|
+
d = create_driver("#{CONFIG}\npull_interval 0.5")
|
|
343
|
+
d.run(expect_emits: 2, timeout: 3)
|
|
344
|
+
emits = d.events
|
|
345
|
+
|
|
346
|
+
# not acknowledged, but already emitted to engine.
|
|
347
|
+
assert(2 <= emits.length)
|
|
348
|
+
emits.each do |tag, time, record|
|
|
349
|
+
assert_equal("test", tag)
|
|
350
|
+
assert_equal({"foo" => "bar"}, record)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
test 'stop by http rpc' do
|
|
355
|
+
messages = Array.new(1, DummyMessage.new)
|
|
356
|
+
@subscriber.pull(immediate: true, max: 100).once { messages }
|
|
357
|
+
@subscriber.acknowledge(messages).once
|
|
358
|
+
|
|
359
|
+
d = create_driver("#{CONFIG}\npull_interval 1.0\nenable_rpc true")
|
|
360
|
+
assert_equal(false, d.instance.instance_variable_get(:@stop_pull))
|
|
361
|
+
|
|
362
|
+
d.run {
|
|
363
|
+
http_get('/api/in_gcloud_pubsub/pull/stop')
|
|
364
|
+
sleep 0.75
|
|
365
|
+
# d.run sleeps 0.5 sec
|
|
366
|
+
}
|
|
367
|
+
emits = d.events
|
|
368
|
+
|
|
369
|
+
assert_equal(1, emits.length)
|
|
370
|
+
assert_true d.instance.instance_variable_get(:@stop_pull)
|
|
371
|
+
|
|
372
|
+
emits.each do |tag, time, record|
|
|
373
|
+
assert_equal("test", tag)
|
|
374
|
+
assert_equal({"foo" => "bar"}, record)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
test 'start by http rpc' do
|
|
379
|
+
messages = Array.new(1, DummyMessage.new)
|
|
380
|
+
@subscriber.pull(immediate: true, max: 100).at_least(1) { messages }
|
|
381
|
+
@subscriber.acknowledge(messages).at_least(1)
|
|
382
|
+
|
|
383
|
+
d = create_driver("#{CONFIG}\npull_interval 1.0\nenable_rpc true")
|
|
384
|
+
d.instance.stop_pull
|
|
385
|
+
assert_equal(true, d.instance.instance_variable_get(:@stop_pull))
|
|
386
|
+
|
|
387
|
+
d.run(expect_emits: 1, timeout: 3) {
|
|
388
|
+
http_get('/api/in_gcloud_pubsub/pull/start')
|
|
389
|
+
sleep 0.75
|
|
390
|
+
# d.run sleeps 0.5 sec
|
|
391
|
+
}
|
|
392
|
+
emits = d.events
|
|
393
|
+
|
|
394
|
+
assert_equal(true, emits.length > 0)
|
|
395
|
+
assert_false d.instance.instance_variable_get(:@stop_pull)
|
|
396
|
+
|
|
397
|
+
emits.each do |tag, time, record|
|
|
398
|
+
assert_equal("test", tag)
|
|
399
|
+
assert_equal({"foo" => "bar"}, record)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
test 'get status by http rpc when started' do
|
|
404
|
+
d = create_driver("#{CONFIG}\npull_interval 1.0\nenable_rpc true")
|
|
405
|
+
assert_false d.instance.instance_variable_get(:@stop_pull)
|
|
406
|
+
|
|
407
|
+
d.run {
|
|
408
|
+
res = http_get('/api/in_gcloud_pubsub/pull/status')
|
|
409
|
+
assert_equal({"ok" => true, "status" => "started"}, JSON.parse(res.body))
|
|
410
|
+
}
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
test 'get status by http rpc when stopped' do
|
|
414
|
+
d = create_driver("#{CONFIG}\npull_interval 1.0\nenable_rpc true")
|
|
415
|
+
d.instance.stop_pull
|
|
416
|
+
assert_true d.instance.instance_variable_get(:@stop_pull)
|
|
417
|
+
|
|
418
|
+
d.run {
|
|
419
|
+
res = http_get('/api/in_gcloud_pubsub/pull/status')
|
|
420
|
+
assert_equal({"ok" => true, "status" => "stopped"}, JSON.parse(res.body))
|
|
421
|
+
}
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|