logstash-input-redis_cluster 1.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.
@@ -0,0 +1,478 @@
1
+ # encoding: utf-8
2
+ require 'logstash/devutils/rspec/shared_examples'
3
+ require "logstash/devutils/rspec/spec_helper"
4
+ require 'logstash/inputs/redis_cluster'
5
+ require 'securerandom'
6
+
7
+ def populate(key, event_count)
8
+ require "logstash/event"
9
+ require "redis"
10
+ require "stud/try"
11
+ redis = RedisClient.cluster(nodes: [
12
+ "redis://host.docker.internal:7000",
13
+ "redis://host.docker.internal:7001",
14
+ "redis://host.docker.internal:7002"
15
+ ], fixed_hostname: "host.docker.internal").new_client
16
+ event_count.times do |value|
17
+ event = LogStash::Event.new("sequence" => value)
18
+ Stud.try(10.times) do
19
+ redis.rpush(key, event.to_json)
20
+ end
21
+ end
22
+ end
23
+
24
+ def process(conf, event_count)
25
+ events = input(conf) do |_, queue|
26
+ sleep 0.1 until queue.size >= event_count
27
+ queue.size.times.map { queue.pop }
28
+ end
29
+ # due multiple workers we get events out-of-order in the output
30
+ events.sort! { |a, b| a.get('sequence') <=> b.get('sequence') }
31
+ expect(events[0].get('sequence')).to eq(0)
32
+ expect(events[100].get('sequence')).to eq(100)
33
+ expect(events[1000].get('sequence')).to eq(1000)
34
+ end
35
+
36
+ # integration tests ---------------------
37
+
38
+ describe LogStash::Inputs::RedisCluster do
39
+
40
+ it_behaves_like "an interruptible input plugin" do
41
+ key = SecureRandom.hex
42
+ let(:config) { {
43
+ "type" => "blah",
44
+ "fixed_hostname" => "host.docker.internal",
45
+ "nodes" => [
46
+ "redis://host.docker.internal:7000",
47
+ "redis://host.docker.internal:7001",
48
+ "redis://host.docker.internal:7002"
49
+ ],
50
+ "key" => "#{key}",
51
+ "data_type" => "list",
52
+ }
53
+ }
54
+ end
55
+
56
+ context "should read events from a list" do
57
+ key = SecureRandom.hex
58
+ event_count = 1000 + rand(50)
59
+ let(:config) { {
60
+ "type" => "blah",
61
+ "fixed_hostname" => "host.docker.internal",
62
+ "nodes" => [
63
+ "redis://host.docker.internal:7000",
64
+ "redis://host.docker.internal:7001",
65
+ "redis://host.docker.internal:7002"
66
+ ],
67
+ "key" => "#{key}",
68
+ "data_type" => "list",
69
+ "batch_count" => 1
70
+ }
71
+ }
72
+
73
+ populate(key, event_count)
74
+ # process(conf, event_count)
75
+ end
76
+
77
+ it "should read events from a list using batch_count (default 125)" do
78
+ key = SecureRandom.hex
79
+ event_count = 1000 + rand(50)
80
+ conf = <<-CONFIG
81
+ input {
82
+ redis_cluster {
83
+ type => "blah"
84
+ fixed_hostname => "host.docker.internal"
85
+ nodes => [
86
+ "redis://host.docker.internal:7000",
87
+ "redis://host.docker.internal:7001",
88
+ "redis://host.docker.internal:7002"
89
+ ]
90
+ key => "#{key}"
91
+ data_type => "list"
92
+ }
93
+ }
94
+ CONFIG
95
+
96
+ populate(key, event_count)
97
+ # process(conf, event_count)
98
+ end
99
+ end
100
+
101
+ # describe LogStash::Inputs::Redis do
102
+ # let(:queue) { Queue.new }
103
+
104
+ # let(:data_type) { 'list' }
105
+ # let(:batch_count) { 1 }
106
+ # let(:config) { {'key' => 'foo', 'data_type' => data_type, 'batch_count' => batch_count} }
107
+ # let(:quit_calls) { [:quit] }
108
+
109
+ # subject do
110
+ # LogStash::Inputs::Redis.new(config)
111
+ # end
112
+
113
+ # context 'construction' do
114
+ # it 'registers the input' do
115
+ # expect { subject.register }.not_to raise_error
116
+ # end
117
+ # end
118
+
119
+ # context 'renamed redis commands' do
120
+ # let(:config) do
121
+ # {
122
+ # 'key' => 'foo',
123
+ # 'data_type' => data_type,
124
+ # 'command_map' => {
125
+ # 'blpop' => 'testblpop',
126
+ # 'evalsha' => 'testevalsha',
127
+ # 'lrange' => 'testlrange',
128
+ # 'ltrim' => 'testltrim',
129
+ # 'script' => 'testscript',
130
+ # 'subscribe' => 'testsubscribe',
131
+ # 'psubscribe' => 'testpsubscribe',
132
+ # },
133
+ # 'batch_count' => 2
134
+ # }
135
+ # end
136
+
137
+ # it 'sets the renamed commands in the command map' do
138
+ # allow_any_instance_of( Redis::Client ).to receive(:call) do |_, command|
139
+ # expect(command[0]).to eql :script
140
+ # expect(command[1]).to eql 'load'
141
+ # end
142
+
143
+ # subject.register
144
+ # redis = subject.send :connect
145
+
146
+ # command_map = redis._client.command_map
147
+
148
+ # expect(command_map[:blpop]).to eq config['command_map']['blpop'].to_sym
149
+ # expect(command_map[:evalsha]).to eq config['command_map']['evalsha'].to_sym
150
+ # expect(command_map[:lrange]).to eq config['command_map']['lrange'].to_sym
151
+ # expect(command_map[:ltrim]).to eq config['command_map']['ltrim'].to_sym
152
+ # expect(command_map[:script]).to eq config['command_map']['script'].to_sym
153
+ # expect(command_map[:subscribe]).to eq config['command_map']['subscribe'].to_sym
154
+ # expect(command_map[:psubscribe]).to eq config['command_map']['psubscribe'].to_sym
155
+ # end
156
+
157
+ # it 'loads the batch script with the renamed command' do
158
+ # expect_any_instance_of( Redis::Client ).to receive(:call) do |_, command|
159
+ # expect(command[0]).to eql :script
160
+ # expect(command[1]).to eql 'load'
161
+
162
+ # script = command[2]
163
+ # expect(script).to include "redis.call('#{config['command_map']['lrange']}', KEYS[1], 0, batchsize)"
164
+ # expect(script).to include "redis.call('#{config['command_map']['ltrim']}', KEYS[1], batchsize + 1, -1)"
165
+ # end
166
+
167
+ # subject.register
168
+ # subject.send :connect
169
+ # end
170
+ # end
171
+
172
+ # context 'runtime for list data_type' do
173
+
174
+ # before do
175
+ # subject.register
176
+ # allow_any_instance_of( Redis::Client ).to receive(:connected?).and_return true
177
+ # allow_any_instance_of( Redis::Client ).to receive(:disconnect)
178
+ # allow_any_instance_of( Redis ).to receive(:quit)
179
+ # end
180
+
181
+ # after do
182
+ # subject.stop
183
+ # end
184
+
185
+ # context 'close when redis is unset' do
186
+
187
+ # it 'does not attempt to quit' do
188
+ # expect_any_instance_of( Redis::Client ).to_not receive(:call)
189
+ # expect_any_instance_of( Redis::Client ).to_not receive(:disconnect)
190
+
191
+ # expect { subject.do_stop }.not_to raise_error
192
+ # end
193
+ # end
194
+
195
+ # it 'calling the run method, adds events to the queue' do
196
+ # allow_any_instance_of( Redis::Client ).to receive(:call_with_timeout) do |_, command, timeout, &block|
197
+ # expect(command[0]).to eql :blpop
198
+ # expect(command[1]).to eql ['foo', 0]
199
+ # end.and_return ['foo', "{\"foo1\":\"bar\""], nil
200
+
201
+ # tt = Thread.new do
202
+ # sleep 0.25
203
+ # subject.do_stop
204
+ # end
205
+
206
+ # subject.run(queue)
207
+
208
+ # tt.join
209
+
210
+ # expect( queue.size ).to be > 0
211
+ # end
212
+
213
+ # it 'keeps running when a connection error occurs' do
214
+ # raised = false
215
+ # allow_any_instance_of( Redis::Client ).to receive(:call_with_timeout) do |_, command, timeout, &block|
216
+ # expect(command[0]).to eql :blpop
217
+ # unless raised
218
+ # raised = true
219
+ # raise Redis::CannotConnectError.new('test')
220
+ # end
221
+ # ['foo', "{\"after\":\"raise\"}"]
222
+ # end
223
+
224
+ # expect(subject.logger).to receive(:warn).with('Redis connection error',
225
+ # hash_including(:message=>"test", :exception=>Redis::CannotConnectError)
226
+ # ).and_call_original
227
+
228
+ # tt = Thread.new do
229
+ # sleep 2.0 # allow for retry (sleep) after handle_error
230
+ # subject.do_stop
231
+ # end
232
+
233
+ # subject.run(queue)
234
+
235
+ # tt.join
236
+
237
+ # try(3) { expect( queue.size ).to be > 0 }
238
+ # end
239
+
240
+ # context 'error handling' do
241
+
242
+ # let(:config) do
243
+ # super().merge 'batch_count' => 2
244
+ # end
245
+
246
+ # it 'keeps running when a (non-Redis) io error occurs' do
247
+ # raised = false
248
+ # allow(subject).to receive(:connect).and_return redis = double('redis')
249
+ # allow(redis).to receive(:blpop).and_return nil
250
+ # expect(redis).to receive(:evalsha) do
251
+ # unless raised
252
+ # raised = true
253
+ # raise IOError.new('closed stream')
254
+ # end
255
+ # []
256
+ # end.at_least(1)
257
+ # redis
258
+ # allow(subject).to receive(:stop)
259
+
260
+ # expect(subject.logger).to receive(:error).with('Unexpected error',
261
+ # hash_including(:message=>'closed stream', :exception=>IOError)
262
+ # ).and_call_original
263
+
264
+ # tt = Thread.new do
265
+ # sleep 2.0 # allow for retry (sleep) after handle_error
266
+ # subject.do_stop
267
+ # end
268
+
269
+ # subject.run(queue)
270
+
271
+ # tt.join
272
+ # end
273
+
274
+ # end
275
+
276
+ # context "when the batch size is greater than 1" do
277
+ # let(:batch_count) { 10 }
278
+
279
+ # it 'calling the run method, adds events to the queue' do
280
+ # allow_any_instance_of( Redis ).to receive(:script)
281
+ # allow_any_instance_of( Redis::Client ).to receive(:call) do |_, command|
282
+ # expect(command[0]).to eql :evalsha
283
+ # end.and_return ['{"a": 1}', '{"b":'], []
284
+
285
+ # tt = Thread.new do
286
+ # sleep 0.25
287
+ # subject.do_stop
288
+ # end
289
+
290
+ # subject.run(queue)
291
+
292
+ # tt.join
293
+
294
+ # expect( queue.size ).to be > 0
295
+ # end
296
+ # end
297
+
298
+ # context "when there is no data" do
299
+ # let(:batch_count) { 10 }
300
+ # let(:rates) { [] }
301
+
302
+ # it 'will throttle the loop' do
303
+ # allow_any_instance_of( Redis ).to receive(:script)
304
+ # allow_any_instance_of( Redis::Client ).to receive(:call) do |_, command|
305
+ # expect(command[0]).to eql :evalsha
306
+ # rates.unshift Time.now.to_f
307
+ # end.and_return []
308
+
309
+ # tt = Thread.new do
310
+ # sleep 0.25
311
+ # subject.do_stop
312
+ # end
313
+
314
+ # subject.run(queue)
315
+
316
+ # tt.join
317
+
318
+ # inters = []
319
+ # rates.each_cons(2) do |x, y|
320
+ # inters << x - y
321
+ # end
322
+
323
+ # expect( queue.size ).to eq(0)
324
+ # inters.each do |delta|
325
+ # expect(delta).to be_within(0.01).of(LogStash::Inputs::Redis::BATCH_EMPTY_SLEEP)
326
+ # end
327
+ # end
328
+ # end
329
+
330
+ # it 'multiple close calls, calls to redis once' do
331
+ # allow_any_instance_of( Redis::Client ).to receive(:connected?).and_return true, false
332
+ # # allow_any_instance_of( Redis::Client ).to receive(:disconnect)
333
+ # quit_calls.each do |call|
334
+ # allow_any_instance_of( Redis ).to receive(call).at_most(:once)
335
+ # end
336
+
337
+ # subject.do_stop
338
+ # expect { subject.do_stop }.not_to raise_error
339
+ # subject.do_stop
340
+ # end
341
+ # end
342
+
343
+ # context 'for the subscribe data_types' do
344
+
345
+ # before { subject.register }
346
+
347
+ # def run_it_thread(inst)
348
+ # Thread.new(inst) do |subj|
349
+ # subj.run(queue)
350
+ # end
351
+ # end
352
+
353
+ # def publish_thread(new_redis, prefix)
354
+ # Thread.new(new_redis, prefix) do |r, p|
355
+ # sleep 0.1
356
+ # 2.times do |i|
357
+ # r.publish('foo', "#{p}#{i.next}")
358
+ # end
359
+ # end
360
+ # end
361
+
362
+ # def close_thread(inst, rt)
363
+ # Thread.new(inst, rt) do |subj, runner|
364
+ # # block for the messages
365
+ # e1 = queue.pop
366
+ # e2 = queue.pop
367
+ # # put em back for the tests
368
+ # queue.push(e1)
369
+ # queue.push(e2)
370
+ # runner.raise(LogStash::ShutdownSignal)
371
+ # subj.close
372
+ # end
373
+ # end
374
+
375
+ # before(:example, type: :mocked) do
376
+ # subject.register
377
+ # allow_any_instance_of( Redis::Client ).to receive(:connected?).and_return true, false
378
+ # quit_calls.each do |call|
379
+ # allow_any_instance_of( Redis ).to receive(call).at_most(:once)
380
+ # end
381
+ # end
382
+
383
+ # context 'runtime for channel data_type' do
384
+ # let(:data_type) { 'channel' }
385
+ # let(:quit_calls) { [:unsubscribe, :connection] }
386
+
387
+ # before { subject.register }
388
+
389
+ # context 'mocked redis' do
390
+ # it 'multiple stop calls, calls to redis once', type: :mocked do
391
+ # subject.do_stop
392
+ # expect { subject.do_stop }.not_to raise_error
393
+ # subject.do_stop
394
+ # end
395
+ # end
396
+
397
+ # context 'real redis', :redis => true do
398
+ # it 'calling the run method, adds events to the queue' do
399
+ # #simulate the input thread
400
+ # rt = run_it_thread(subject)
401
+ # #simulate the other system thread
402
+ # publish_thread(subject.send(:new_redis_instance), 'c').join
403
+ # #simulate the pipeline thread
404
+ # close_thread(subject, rt).join
405
+
406
+ # expect(queue.size).to eq(2)
407
+ # end
408
+ # it 'events had redis_channel' do
409
+ # #simulate the input thread
410
+ # rt = run_it_thread(subject)
411
+ # #simulate the other system thread
412
+ # publish_thread(subject.send(:new_redis_instance), 'c').join
413
+ # #simulate the pipeline thread
414
+ # close_thread(subject, rt).join
415
+ # e1 = queue.pop
416
+ # e2 = queue.pop
417
+ # expect(e1.get('[@metadata][redis_channel]')).to eq('foo')
418
+ # expect(e2.get('[@metadata][redis_channel]')).to eq('foo')
419
+ # end
420
+ # end
421
+ # end
422
+
423
+ # context 'runtime for pattern_channel data_type' do
424
+ # let(:data_type) { 'pattern_channel' }
425
+ # let(:quit_calls) { [:punsubscribe, :connection] }
426
+
427
+ # context 'mocked redis' do
428
+ # it 'multiple stop calls, calls to redis once', type: :mocked do
429
+ # subject.do_stop
430
+ # expect { subject.do_stop }.not_to raise_error
431
+ # subject.do_stop
432
+ # end
433
+ # end
434
+
435
+ # context 'real redis', :redis => true do
436
+ # it 'calling the run method, adds events to the queue' do
437
+ # #simulate the input thread
438
+ # rt = run_it_thread(subject)
439
+ # #simulate the other system thread
440
+ # publish_thread(subject.send(:new_redis_instance), 'pc').join
441
+ # #simulate the pipeline thread
442
+ # close_thread(subject, rt).join
443
+
444
+ # expect(queue.size).to eq(2)
445
+ # end
446
+
447
+ # it 'events had redis_channel' do
448
+ # #simulate the input thread
449
+ # rt = run_it_thread(subject)
450
+ # #simulate the other system thread
451
+ # publish_thread(subject.send(:new_redis_instance), 'pc').join
452
+ # #simulate the pipeline thread
453
+ # close_thread(subject, rt).join
454
+ # e1 = queue.pop
455
+ # e2 = queue.pop
456
+ # expect(e1.get('[@metadata][redis_channel]')).to eq('foo')
457
+ # expect(e2.get('[@metadata][redis_channel]')).to eq('foo')
458
+ # end
459
+ # end
460
+ # end
461
+ # end
462
+
463
+ # context "when using data type" do
464
+
465
+ # ["list", "channel", "pattern_channel"].each do |data_type|
466
+ # context data_type do
467
+ # # TODO pending
468
+ # # redis-rb ends up in a read wait loop since we do not use subscribe_with_timeout
469
+ # next unless data_type == 'list'
470
+
471
+ # it_behaves_like "an interruptible input plugin", :redis => true do
472
+ # let(:config) { { 'key' => 'foo', 'data_type' => data_type } }
473
+ # end
474
+ # end
475
+ # end
476
+
477
+ # end
478
+ # end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-input-redis_cluster
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Elastic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.60'
19
+ - - "<="
20
+ - !ruby/object:Gem::Version
21
+ version: '2.99'
22
+ name: logstash-core-plugin-api
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.60'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ name: logstash-codec-json
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 4.0.1
53
+ - - "<"
54
+ - !ruby/object:Gem::Version
55
+ version: '5'
56
+ name: redis
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 4.0.1
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '5'
67
+ - !ruby/object:Gem::Dependency
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 0.0.16
73
+ name: logstash-devutils
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.0.16
81
+ - !ruby/object:Gem::Dependency
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ name: redis-cluster-client
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ description: This gem is a Logstash plugin required to be installed on top of the
96
+ Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This
97
+ gem is not a stand-alone program
98
+ email: info@elastic.co
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - CONTRIBUTORS
105
+ - Gemfile
106
+ - LICENSE
107
+ - NOTICE.TXT
108
+ - README.md
109
+ - docs/index.asciidoc
110
+ - lib/logstash/inputs/redis_cluster.rb
111
+ - logstash-input-redis_cluster.gemspec
112
+ - spec/inputs/redis_cluster_spec.rb
113
+ homepage: http://www.elastic.co/guide/en/logstash/current/index.html
114
+ licenses:
115
+ - Apache License (2.0)
116
+ metadata:
117
+ logstash_plugin: 'true'
118
+ logstash_group: input
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.3.26
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Reads events from a Redis instance
138
+ test_files:
139
+ - spec/inputs/redis_cluster_spec.rb