logstash-input-openwhisk 0.1.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/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +106 -0
- data/lib/logstash/inputs/openwhisk.rb +291 -0
- data/logstash-input-openwhisk.gemspec +31 -0
- data/spec/inputs/openwhisk_spec.rb +532 -0
- metadata +190 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'logstash-input-openwhisk'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.licenses = ['Apache License (2.0)']
|
5
|
+
s.summary = "Retrieve OpenWhisk logs with Logstash."
|
6
|
+
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
7
|
+
s.authors = [ "James Thomas" ]
|
8
|
+
s.email = 'james@jamesthom.as'
|
9
|
+
s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
|
10
|
+
s.require_paths = ["lib"]
|
11
|
+
|
12
|
+
# Files
|
13
|
+
s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
|
14
|
+
# Tests
|
15
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
16
|
+
|
17
|
+
# Special flag to let us know this is actually a logstash plugin
|
18
|
+
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
|
19
|
+
|
20
|
+
# Gem dependencies
|
21
|
+
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
22
|
+
s.add_runtime_dependency 'logstash-codec-plain'
|
23
|
+
s.add_runtime_dependency 'logstash-mixin-http_client', ">= 2.2.4", "< 5.0.0"
|
24
|
+
s.add_runtime_dependency 'stud', "~> 0.0.22"
|
25
|
+
s.add_runtime_dependency 'rufus-scheduler', "~>3.0.9"
|
26
|
+
|
27
|
+
s.add_development_dependency 'logstash-codec-json'
|
28
|
+
s.add_development_dependency 'logstash-devutils'
|
29
|
+
s.add_development_dependency 'flores'
|
30
|
+
s.add_development_dependency 'timecop'
|
31
|
+
end
|
@@ -0,0 +1,532 @@
|
|
1
|
+
require "logstash/devutils/rspec/spec_helper"
|
2
|
+
require 'logstash/inputs/openwhisk'
|
3
|
+
require 'flores/random'
|
4
|
+
require "timecop"
|
5
|
+
|
6
|
+
describe LogStash::Inputs::OpenWhisk do
|
7
|
+
let(:metadata_target) { "_openwhisk_metadata" }
|
8
|
+
let(:queue) { Queue.new }
|
9
|
+
let(:default_schedule) {
|
10
|
+
{ "cron" => "* * * * * UTC" }
|
11
|
+
}
|
12
|
+
let(:default_name) { "openwhisk" }
|
13
|
+
let(:default_hostname) { "localhost" }
|
14
|
+
let(:default_username) { "user@email.com" }
|
15
|
+
let(:default_password) { "my_password" }
|
16
|
+
let(:default_namespace) { "user_namespace" }
|
17
|
+
let(:default_opts) {
|
18
|
+
{
|
19
|
+
"schedule" => default_schedule,
|
20
|
+
"hostname" => default_hostname,
|
21
|
+
"username" => default_username,
|
22
|
+
"password" => default_password,
|
23
|
+
"namespace" => default_namespace,
|
24
|
+
"codec" => "json",
|
25
|
+
"metadata_target" => metadata_target
|
26
|
+
}
|
27
|
+
}
|
28
|
+
let(:klass) { LogStash::Inputs::OpenWhisk }
|
29
|
+
|
30
|
+
describe "instances" do
|
31
|
+
subject { klass.new(default_opts) }
|
32
|
+
|
33
|
+
before do
|
34
|
+
subject.register
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#register" do
|
38
|
+
it "should set logs since to time since epoch" do
|
39
|
+
expect(subject.instance_variable_get("@logs_since")).to eql(Time.now.to_i * 1000)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#run" do
|
44
|
+
it "should setup a scheduler" do
|
45
|
+
runner = Thread.new do
|
46
|
+
subject.run(double("queue"))
|
47
|
+
expect(subject.instance_variable_get("@scheduler")).to be_a_kind_of(Rufus::Scheduler)
|
48
|
+
end
|
49
|
+
runner.kill
|
50
|
+
runner.join
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#run_once" do
|
55
|
+
it "should issue an async request for each url" do
|
56
|
+
constructed_request = subject.send(:construct_request, default_opts)
|
57
|
+
expect(subject).to receive(:request_async).with(queue, default_name, constructed_request).once
|
58
|
+
|
59
|
+
subject.send(:run_once, queue) # :run_once is a private method
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#update_logs_since" do
|
64
|
+
context "given current time less than five minutes ahead of last poll activation" do
|
65
|
+
let(:now) { Time.now.to_i * 1000 }
|
66
|
+
let(:previous) {
|
67
|
+
now - (5 * 60 * 1000) + 1
|
68
|
+
}
|
69
|
+
before do
|
70
|
+
subject.instance_variable_set("@logs_since", previous)
|
71
|
+
subject.send(:update_logs_since, now)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not update logs since" do
|
75
|
+
expect(subject.instance_variable_get("@logs_since")).to eql(previous)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "given current time more than five minutes ahead of last poll activation" do
|
80
|
+
let(:now) { Time.now.to_i * 1000 }
|
81
|
+
let(:previous) {
|
82
|
+
now - (5 * 60 * 1000) - 1
|
83
|
+
}
|
84
|
+
before do
|
85
|
+
subject.instance_variable_set("@logs_since", previous)
|
86
|
+
subject.send(:update_logs_since, now)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should update logs since x" do
|
90
|
+
expect(subject.instance_variable_get("@logs_since")).to eql(now - 5 * 60 * 1000)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "constructor" do
|
96
|
+
context "given options missing hostname" do
|
97
|
+
let(:opts) {
|
98
|
+
opts = default_opts.clone
|
99
|
+
opts.delete("hostname")
|
100
|
+
opts
|
101
|
+
}
|
102
|
+
|
103
|
+
it "should raise ConfigurationError" do
|
104
|
+
expect { klass.new(opts) }.to raise_error(LogStash::ConfigurationError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "given options missing username" do
|
109
|
+
let(:opts) {
|
110
|
+
opts = default_opts.clone
|
111
|
+
opts.delete("username")
|
112
|
+
opts
|
113
|
+
}
|
114
|
+
|
115
|
+
it "should raise ConfigurationError" do
|
116
|
+
expect { klass.new(opts) }.to raise_error(LogStash::ConfigurationError)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "given options missing password" do
|
121
|
+
let(:opts) {
|
122
|
+
opts = default_opts.clone
|
123
|
+
opts.delete("password")
|
124
|
+
opts
|
125
|
+
}
|
126
|
+
|
127
|
+
it "should raise ConfigurationError" do
|
128
|
+
expect { klass.new(opts) }.to raise_error(LogStash::ConfigurationError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "given options missing namespace" do
|
133
|
+
let(:opts) {
|
134
|
+
opts = default_opts.clone
|
135
|
+
opts.delete("namespace")
|
136
|
+
opts
|
137
|
+
}
|
138
|
+
|
139
|
+
it "should use default namespace" do
|
140
|
+
instance = klass.new(opts)
|
141
|
+
expect(instance.namespace).to eql("_")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "given options with namespace" do
|
146
|
+
it "should use options namespace" do
|
147
|
+
instance = klass.new(default_opts)
|
148
|
+
expect(instance.namespace).to eql(default_namespace)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "construct request spec" do
|
154
|
+
context "with normal options" do
|
155
|
+
let(:result) { subject.send(:construct_request, default_opts) }
|
156
|
+
|
157
|
+
it "should set method correctly" do
|
158
|
+
expect(result[0]).to eql(:get)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should set url correctly" do
|
162
|
+
expect(result[1]).to eql("https://#{default_hostname}/api/v1/namespaces/#{default_namespace}/activations")
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should set auth correctly" do
|
166
|
+
expect(result[2][:auth]).to eql({user: default_username, pass: default_password})
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should set query string correctly" do
|
170
|
+
expect(result[2][:query]).to eql({docs: true, limit: 0, skip: 0, since: subject.instance_variable_get('@logs_since')})
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "#structure_request" do
|
176
|
+
it "Should turn a simple request into the expected structured request" do
|
177
|
+
expected = {"url" => "http://example.net", "method" => "get"}
|
178
|
+
expect(subject.send(:structure_request, ["get", "http://example.net"])).to eql(expected)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should turn a complex request into the expected structured one" do
|
182
|
+
headers = {
|
183
|
+
"X-Fry" => " Like a balloon, and... something bad happens! "
|
184
|
+
}
|
185
|
+
expected = {
|
186
|
+
"url" => "http://example.net",
|
187
|
+
"method" => "get",
|
188
|
+
"headers" => headers
|
189
|
+
}
|
190
|
+
expect(subject.send(:structure_request, ["get", "http://example.net", {"headers" => headers}])).to eql(expected)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "scheduler configuration" do
|
196
|
+
context "given an interval" do
|
197
|
+
let(:opts) {
|
198
|
+
{
|
199
|
+
"interval" => 2,
|
200
|
+
"hostname" => default_hostname,
|
201
|
+
"username" => default_username,
|
202
|
+
"password" => default_password,
|
203
|
+
"codec" => "json",
|
204
|
+
"metadata_target" => metadata_target
|
205
|
+
}
|
206
|
+
}
|
207
|
+
it "should run once in each interval" do
|
208
|
+
instance = klass.new(opts)
|
209
|
+
instance.register
|
210
|
+
queue = Queue.new
|
211
|
+
runner = Thread.new do
|
212
|
+
instance.run(queue)
|
213
|
+
end
|
214
|
+
#T 0123456
|
215
|
+
#events x x x x
|
216
|
+
#expects 3 events at T=5
|
217
|
+
sleep 5
|
218
|
+
instance.stop
|
219
|
+
runner.kill
|
220
|
+
runner.join
|
221
|
+
expect(queue.size).to eq(3)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "given both interval and schedule options" do
|
226
|
+
let(:opts) {
|
227
|
+
{
|
228
|
+
"interval" => 1,
|
229
|
+
"schedule" => { "every" => "5s" },
|
230
|
+
"hostname" => default_hostname,
|
231
|
+
"username" => default_username,
|
232
|
+
"password" => default_password,
|
233
|
+
"codec" => "json",
|
234
|
+
"metadata_target" => metadata_target
|
235
|
+
}
|
236
|
+
}
|
237
|
+
it "should raise ConfigurationError" do
|
238
|
+
instance = klass.new(opts)
|
239
|
+
instance.register
|
240
|
+
queue = Queue.new
|
241
|
+
runner = Thread.new do
|
242
|
+
expect{instance.run(queue)}.to raise_error(LogStash::ConfigurationError)
|
243
|
+
end
|
244
|
+
instance.stop
|
245
|
+
runner.kill
|
246
|
+
runner.join
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "given 'cron' expression" do
|
251
|
+
let(:opts) {
|
252
|
+
{
|
253
|
+
"schedule" => { "cron" => "* * * * * UTC" },
|
254
|
+
"hostname" => default_hostname,
|
255
|
+
"username" => default_username,
|
256
|
+
"password" => default_password,
|
257
|
+
"codec" => "json",
|
258
|
+
"metadata_target" => metadata_target
|
259
|
+
}
|
260
|
+
}
|
261
|
+
it "should run at the schedule" do
|
262
|
+
instance = klass.new(opts)
|
263
|
+
instance.register
|
264
|
+
Timecop.travel(Time.new(2000,1,1,0,0,0,'+00:00'))
|
265
|
+
Timecop.scale(60)
|
266
|
+
queue = Queue.new
|
267
|
+
runner = Thread.new do
|
268
|
+
instance.run(queue)
|
269
|
+
end
|
270
|
+
sleep 3
|
271
|
+
instance.stop
|
272
|
+
runner.kill
|
273
|
+
runner.join
|
274
|
+
expect(queue.size).to eq(2)
|
275
|
+
Timecop.return
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context "given 'at' expression" do
|
280
|
+
let(:opts) {
|
281
|
+
{
|
282
|
+
"schedule" => { "at" => "2000-01-01 00:05:00 +0000"},
|
283
|
+
"hostname" => default_hostname,
|
284
|
+
"username" => default_username,
|
285
|
+
"password" => default_password,
|
286
|
+
"codec" => "json",
|
287
|
+
"metadata_target" => metadata_target
|
288
|
+
}
|
289
|
+
}
|
290
|
+
it "should run at the schedule" do
|
291
|
+
instance = klass.new(opts)
|
292
|
+
instance.register
|
293
|
+
Timecop.travel(Time.new(2000,1,1,0,0,0,'+00:00'))
|
294
|
+
Timecop.scale(60 * 5)
|
295
|
+
queue = Queue.new
|
296
|
+
runner = Thread.new do
|
297
|
+
instance.run(queue)
|
298
|
+
end
|
299
|
+
sleep 2
|
300
|
+
instance.stop
|
301
|
+
runner.kill
|
302
|
+
runner.join
|
303
|
+
expect(queue.size).to eq(1)
|
304
|
+
Timecop.return
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context "given 'every' expression" do
|
309
|
+
let(:opts) {
|
310
|
+
{
|
311
|
+
"schedule" => { "every" => "2s"},
|
312
|
+
"hostname" => default_hostname,
|
313
|
+
"username" => default_username,
|
314
|
+
"password" => default_password,
|
315
|
+
"codec" => "json",
|
316
|
+
"metadata_target" => metadata_target
|
317
|
+
}
|
318
|
+
}
|
319
|
+
it "should run at the schedule" do
|
320
|
+
instance = klass.new(opts)
|
321
|
+
instance.register
|
322
|
+
queue = Queue.new
|
323
|
+
runner = Thread.new do
|
324
|
+
instance.run(queue)
|
325
|
+
end
|
326
|
+
#T 0123456
|
327
|
+
#events x x x x
|
328
|
+
#expects 3 events at T=5
|
329
|
+
sleep 5
|
330
|
+
instance.stop
|
331
|
+
runner.kill
|
332
|
+
runner.join
|
333
|
+
expect(queue.size).to eq(3)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context "given 'in' expression" do
|
338
|
+
let(:opts) {
|
339
|
+
{
|
340
|
+
"schedule" => { "in" => "2s"},
|
341
|
+
"hostname" => default_hostname,
|
342
|
+
"username" => default_username,
|
343
|
+
"password" => default_password,
|
344
|
+
"codec" => "json",
|
345
|
+
"metadata_target" => metadata_target
|
346
|
+
}
|
347
|
+
}
|
348
|
+
it "should run at the schedule" do
|
349
|
+
instance = klass.new(opts)
|
350
|
+
instance.register
|
351
|
+
queue = Queue.new
|
352
|
+
runner = Thread.new do
|
353
|
+
instance.run(queue)
|
354
|
+
end
|
355
|
+
sleep 3
|
356
|
+
instance.stop
|
357
|
+
runner.kill
|
358
|
+
runner.join
|
359
|
+
expect(queue.size).to eq(1)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe "events" do
|
365
|
+
shared_examples("matching metadata") {
|
366
|
+
let(:metadata) { event.get(metadata_target) }
|
367
|
+
|
368
|
+
it "should have the correct name" do
|
369
|
+
expect(metadata["name"]).to eql(name)
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should have the correct request hostname" do
|
373
|
+
expect(metadata["hostname"]).to eql(hostname)
|
374
|
+
end
|
375
|
+
|
376
|
+
it "should have the correct code" do
|
377
|
+
expect(metadata["code"]).to eql(code)
|
378
|
+
end
|
379
|
+
}
|
380
|
+
|
381
|
+
shared_examples "unprocessable_requests" do
|
382
|
+
let(:poller) { LogStash::Inputs::OpenWhisk.new(settings) }
|
383
|
+
subject(:event) {
|
384
|
+
poller.send(:run_once, queue)
|
385
|
+
queue.pop(true)
|
386
|
+
}
|
387
|
+
|
388
|
+
before do
|
389
|
+
poller.register
|
390
|
+
allow(poller).to receive(:handle_failure).and_call_original
|
391
|
+
allow(poller).to receive(:handle_success)
|
392
|
+
event # materialize the subject
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should enqueue a message" do
|
396
|
+
expect(event).to be_a(LogStash::Event)
|
397
|
+
end
|
398
|
+
|
399
|
+
it "should enqueue a message with 'http_request_failure' set" do
|
400
|
+
expect(event.get("http_request_failure")).to be_a(Hash)
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should tag the event with '_http_request_failure'" do
|
404
|
+
expect(event.get("tags")).to include('_http_request_failure')
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should invoke handle failure exactly once" do
|
408
|
+
expect(poller).to have_received(:handle_failure).once
|
409
|
+
end
|
410
|
+
|
411
|
+
it "should not invoke handle success at all" do
|
412
|
+
expect(poller).not_to have_received(:handle_success)
|
413
|
+
end
|
414
|
+
|
415
|
+
include_examples("matching metadata")
|
416
|
+
end
|
417
|
+
|
418
|
+
context "with a non responsive server" do
|
419
|
+
context "due to a non-existant hostname" do # Fail with handlers
|
420
|
+
let(:name) { default_name }
|
421
|
+
let(:hostname) { "http://thouetnhoeu89ueoueohtueohtneuohn" }
|
422
|
+
let(:code) { nil } # no response expected
|
423
|
+
|
424
|
+
let(:settings) { default_opts.merge("hostname" => hostname) }
|
425
|
+
|
426
|
+
include_examples("unprocessable_requests")
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
describe "a valid request and decoded response" do
|
431
|
+
let(:payload) { [{"start" => 1476818509288, "end" => 1476818509888, "activationId" => "some_id"}] }
|
432
|
+
let(:opts) { default_opts }
|
433
|
+
let(:instance) {
|
434
|
+
klass.new(opts)
|
435
|
+
}
|
436
|
+
let(:name) { default_name }
|
437
|
+
let(:code) { 202 }
|
438
|
+
let(:hostname) { default_hostname }
|
439
|
+
|
440
|
+
subject(:event) {
|
441
|
+
queue.pop(true)
|
442
|
+
}
|
443
|
+
|
444
|
+
before do
|
445
|
+
instance.register
|
446
|
+
instance.instance_variable_set("@logs_since", 0)
|
447
|
+
# match any response
|
448
|
+
instance.client.stub(%r{.},
|
449
|
+
:body => LogStash::Json.dump(payload),
|
450
|
+
:code => code
|
451
|
+
)
|
452
|
+
allow(instance).to receive(:decorate)
|
453
|
+
instance.send(:run_once, queue)
|
454
|
+
end
|
455
|
+
|
456
|
+
it "should have a matching message" do
|
457
|
+
expect(event.to_hash).to include(payload[0])
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should decorate the event" do
|
461
|
+
expect(instance).to have_received(:decorate).once
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should update the time since" do
|
465
|
+
expect(instance.instance_variable_get("@logs_since")).to eql(payload[0]["end"] - (5 * 60 * 1000))
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should retain activation ids" do
|
469
|
+
expect(instance.instance_variable_get("@activation_ids")).to eql(Set.new ["some_id"])
|
470
|
+
end
|
471
|
+
|
472
|
+
include_examples("matching metadata")
|
473
|
+
|
474
|
+
context "with metadata omitted" do
|
475
|
+
let(:opts) {
|
476
|
+
opts = default_opts.clone
|
477
|
+
opts.delete("metadata_target")
|
478
|
+
opts
|
479
|
+
}
|
480
|
+
|
481
|
+
it "should not have any metadata on the event" do
|
482
|
+
instance.send(:run_once, queue)
|
483
|
+
expect(event.get(metadata_target)).to be_nil
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
context "with a specified target" do
|
488
|
+
let(:target) { "mytarget" }
|
489
|
+
let(:opts) { default_opts.merge("target" => target) }
|
490
|
+
|
491
|
+
it "should store the event info in the target" do
|
492
|
+
# When events go through the pipeline they are java-ified
|
493
|
+
# this normalizes the payload to java types
|
494
|
+
payload_normalized = LogStash::Json.load(LogStash::Json.dump(payload))
|
495
|
+
expect(event.get(target)).to include(payload_normalized[0])
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context "with multiple activations" do
|
500
|
+
let(:payload) { [{"end" => 1476818509288, "activationId" => "1"},{"end" => 1476818509289, "activationId" => "2"},{"end" => 1476818509287, "activationId" => "3"} ] }
|
501
|
+
|
502
|
+
it "should update logs since to latest epoch" do
|
503
|
+
instance.instance_variable_set("@logs_since", 0)
|
504
|
+
instance.instance_variable_set("@activation_ids", Set.new)
|
505
|
+
instance.send(:run_once, queue)
|
506
|
+
expect(instance.instance_variable_get("@logs_since")).to eql(payload[1]["end"] - (5 * 60 * 1000))
|
507
|
+
expect(instance.instance_variable_get("@activation_ids")).to eql(Set.new ["1", "2", "3"])
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
context "with previous activations" do
|
512
|
+
let(:payload) { [{"end" => 1476818509288, "activationId" => "some_id"}] }
|
513
|
+
|
514
|
+
subject(:size) {
|
515
|
+
queue.size()
|
516
|
+
}
|
517
|
+
it "should not add activation to queue" do
|
518
|
+
instance.instance_variable_set("@activation_ids", Set.new(["some_id"]))
|
519
|
+
queue.clear()
|
520
|
+
instance.send(:run_once, queue)
|
521
|
+
expect(subject).to eql(0)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
describe "stopping" do
|
529
|
+
let(:config) { default_opts }
|
530
|
+
it_behaves_like "an interruptible input plugin"
|
531
|
+
end
|
532
|
+
end
|