logstash-integration-rabbitmq 7.0.0-java
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/CHANGELOG.md +9 -0
- data/CONTRIBUTORS +27 -0
- data/Gemfile +11 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +98 -0
- data/docs/index.asciidoc +29 -0
- data/docs/input-rabbitmq.asciidoc +398 -0
- data/docs/output-rabbitmq.asciidoc +272 -0
- data/lib/logstash/inputs/rabbitmq.rb +317 -0
- data/lib/logstash/outputs/rabbitmq.rb +131 -0
- data/lib/logstash/plugin_mixins/rabbitmq_connection.rb +234 -0
- data/lib/logstash_registry.rb +7 -0
- data/logstash-integration-rabbitmq.gemspec +53 -0
- data/spec/fixtures/README.md +150 -0
- data/spec/fixtures/client/cert.pem +69 -0
- data/spec/fixtures/client/key.pem +27 -0
- data/spec/fixtures/client/keycert.p12 +0 -0
- data/spec/fixtures/client/req.pem +15 -0
- data/spec/fixtures/client_untrusted/cert.pem +19 -0
- data/spec/fixtures/client_untrusted/key.pem +28 -0
- data/spec/fixtures/client_untrusted/keycert.p12 +0 -0
- data/spec/fixtures/server/cert.pem +69 -0
- data/spec/fixtures/server/key.pem +27 -0
- data/spec/fixtures/server/key_password +1 -0
- data/spec/fixtures/server/keycert.p12 +0 -0
- data/spec/fixtures/server/rabbitmq.config +10 -0
- data/spec/fixtures/server/req.pem +15 -0
- data/spec/fixtures/testca/cacert.cer +0 -0
- data/spec/fixtures/testca/cacert.pem +17 -0
- data/spec/fixtures/testca/certs/01.pem +18 -0
- data/spec/fixtures/testca/certs/02.pem +18 -0
- data/spec/fixtures/testca/certs/05.pem +69 -0
- data/spec/fixtures/testca/certs/06.pem +69 -0
- data/spec/fixtures/testca/index.txt +4 -0
- data/spec/fixtures/testca/index.txt.attr +1 -0
- data/spec/fixtures/testca/index.txt.attr.old +1 -0
- data/spec/fixtures/testca/index.txt.old +3 -0
- data/spec/fixtures/testca/openssl.cnf +53 -0
- data/spec/fixtures/testca/private/cakey.pem +27 -0
- data/spec/fixtures/testca/serial +1 -0
- data/spec/fixtures/testca/serial.old +1 -0
- data/spec/inputs/rabbitmq_spec.rb +279 -0
- data/spec/outputs/rabbitmq_spec.rb +232 -0
- data/spec/plugin_mixins/rabbitmq_connection_spec.rb +175 -0
- metadata +256 -0
@@ -0,0 +1 @@
|
|
1
|
+
unique_subject = yes
|
@@ -0,0 +1 @@
|
|
1
|
+
unique_subject = yes
|
@@ -0,0 +1,53 @@
|
|
1
|
+
[ ca ]
|
2
|
+
default_ca = testca
|
3
|
+
|
4
|
+
[ testca ]
|
5
|
+
dir = .
|
6
|
+
certificate = $dir/cacert.pem
|
7
|
+
database = $dir/index.txt
|
8
|
+
new_certs_dir = $dir/certs
|
9
|
+
private_key = $dir/private/cakey.pem
|
10
|
+
serial = $dir/serial
|
11
|
+
|
12
|
+
default_crl_days = 7
|
13
|
+
default_days = 10000
|
14
|
+
default_md = sha256
|
15
|
+
|
16
|
+
policy = testca_policy
|
17
|
+
x509_extensions = certificate_extensions
|
18
|
+
|
19
|
+
[ testca_policy ]
|
20
|
+
commonName = supplied
|
21
|
+
stateOrProvinceName = optional
|
22
|
+
countryName = optional
|
23
|
+
emailAddress = optional
|
24
|
+
organizationName = optional
|
25
|
+
organizationalUnitName = optional
|
26
|
+
|
27
|
+
[ certificate_extensions ]
|
28
|
+
basicConstraints = CA:false
|
29
|
+
|
30
|
+
[ req ]
|
31
|
+
default_bits = 2048
|
32
|
+
default_keyfile = ./private/cakey.pem
|
33
|
+
default_md = sha256
|
34
|
+
prompt = yes
|
35
|
+
distinguished_name = root_ca_distinguished_name
|
36
|
+
x509_extensions = root_ca_extensions
|
37
|
+
|
38
|
+
[ root_ca_distinguished_name ]
|
39
|
+
commonName = hostname
|
40
|
+
|
41
|
+
[ root_ca_extensions ]
|
42
|
+
basicConstraints = CA:true
|
43
|
+
keyUsage = keyCertSign, cRLSign
|
44
|
+
|
45
|
+
[ client_ca_extensions ]
|
46
|
+
basicConstraints = CA:false
|
47
|
+
keyUsage = digitalSignature
|
48
|
+
extendedKeyUsage = 1.3.6.1.5.5.7.3.2
|
49
|
+
|
50
|
+
[ server_ca_extensions ]
|
51
|
+
basicConstraints = CA:false
|
52
|
+
keyUsage = keyEncipherment
|
53
|
+
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpQIBAAKCAQEAuP3x9a2NoWisFrddKseOJir4xvvLPP65bWUH5l8KkzvG5c+N
|
3
|
+
PKfcxkzlHme9B3WWdGB3lx1buwyunbAejn9Uuwwc1QQA1kzBEGZgx7tgvmU0BRsg
|
4
|
+
EL1pwd3v9Rc2cZxmNrQCqymGWNUqo4BfaNVlxoCHS23FckjwUJRfck54c3WqsunT
|
5
|
+
jE6CoVtbYiKztp4fA38eJRyLgw1bZLOtNIySj1fRKywrCuxrGjdB7zd+c/ScsB8Y
|
6
|
+
EFXwYEx2a9cL3qtnBrbIEh6V09E2cnIvsrdILzzk6OtNs9NnSA0XGcMMrBWHHs+i
|
7
|
+
POdPX851inr3QFTnrPVtn4blGOGdZdR6QwNo2wIDAQABAoIBADoXZLflfChHmmK3
|
8
|
+
ygX5DGZn8B9sSnIo+0mjBEwPZF6/0sGv34ZAoE+VLg3SPcXt4wVAlc1aZsfiP6M8
|
9
|
+
/xt4WL80Gom57BlfmPDxdUrDSKoBVciUsAkRsfgzHXs1gt9CYcaj2IKvU4Tpy630
|
10
|
+
TgG5oXAoRFQncG1nAjALp71ZbvGyZDOK2+/Vjs0NITyC+4srnafiF8pwLXmgm0Zj
|
11
|
+
FVyCy8H/dwt7/pJKKkDX8Mq6kdvvROg8QDqTyCsTn3qAfDQUjO1BJje7x31E+lXW
|
12
|
+
/FgUqs43ATU2HVczP0fbBZeGeFFTRycypmmSXYb9eZ05mDf8cm93V8KMPkTVh4Pu
|
13
|
+
6/kF8AECgYEA8VpoTggar66f/0rfxX263q+L5YD8fwfuqcjLx/Bnuzpp1POhsTLD
|
14
|
+
Gv0W5FcAf4i1jfnBevrk9q9WFnnUpUvX7Y+e1cvK9M5BuVRs77C4mE5IQ7hcHN6I
|
15
|
+
vNgYihIK/PTVXYrT2j1EFLBwD2QWwO7BcV5aAiShlce7qC8fJeAhRtsCgYEAxDft
|
16
|
+
QIUHm2MIzZD25q6fkki1Rx0ifOjO7AlK0NIyxQ+gsxZQrrUsSqcgC/W3XJSONeCY
|
17
|
+
UjbHRQmhzkexA6ZFjgrL39qHL9x0kzle1dZ+oFGVQKiIMWn1WfEL0baDxMjFKDQt
|
18
|
+
xF0SWRHZsIDPUjZzPTyfaPf8I148pIoSILB7BgECgYEA3679ioxiRz5dlNqM59ku
|
19
|
+
DuK9klfoK8drPzoU+1nomZJ6sV2XFsZIIsQ7qiakFI7cTRgTZGoRODuqWqxRE11m
|
20
|
+
Ywq/l8AHermKGjyPtdmgS7AJs5Gy9SKdsf/JRnWQb35uHQLkc5hid5ZKVUla+TaO
|
21
|
+
XAao/uF6THnPhwEdKho+XQ8CgYEAiyQBiJQM/eIvVt4qRxCjNS975M7DKwJH4VcB
|
22
|
+
h6zWtajMUtJLKmhs3Q9ACVsXyH3LjmcSfJI9ojYfWFC8NJNOlVgQlE+5N3ZD8DZp
|
23
|
+
ioeMyZCwnuYjla7Gfh4RPIgJTpz0OfsuTSWWojSnQqNE4M6dz1nSzLO1RztHE4KZ
|
24
|
+
MjcTNgECgYEAmeEA8KJzfsSgjjbhOmzxEJwRxzq0HTknTmWyEIamQBgFhaQ3AqZ+
|
25
|
+
cZWkqS34foKxjLLXp3AMukpS50yRB98Wzaa7eoA/H0zXlRjA6id9qFuCXt+E9e/5
|
26
|
+
sjPhQphj8473SsviSndT29JadTHgY8nSmXtxx+whO29D9Ods0laGb8A=
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1 @@
|
|
1
|
+
07
|
@@ -0,0 +1 @@
|
|
1
|
+
06
|
@@ -0,0 +1,279 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/inputs/rabbitmq"
|
4
|
+
require "thread"
|
5
|
+
require 'logstash/event'
|
6
|
+
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
|
9
|
+
describe LogStash::Inputs::RabbitMQ do
|
10
|
+
let(:klass) { LogStash::Inputs::RabbitMQ }
|
11
|
+
let(:host) { "localhost" }
|
12
|
+
let(:port) { 5672 }
|
13
|
+
let(:exchange_type) { "topic" }
|
14
|
+
let(:exchange) { "myexchange" }
|
15
|
+
let(:queue) { "myqueue" }
|
16
|
+
let(:rabbitmq_settings) {
|
17
|
+
{
|
18
|
+
"host" => host,
|
19
|
+
"port" => port,
|
20
|
+
"queue" => queue,
|
21
|
+
"prefetch_count" => 123
|
22
|
+
}
|
23
|
+
}
|
24
|
+
let(:instance) { klass.new(rabbitmq_settings) }
|
25
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
26
|
+
|
27
|
+
context "when connected" do
|
28
|
+
let(:connection) { double("MarchHare Connection") }
|
29
|
+
let(:channel) { double("Channel") }
|
30
|
+
let(:exchange) { double("Exchange") }
|
31
|
+
let(:channel) { double("Channel") }
|
32
|
+
let(:queue) { double("queue") }
|
33
|
+
|
34
|
+
# Doing this in a before block doesn't give us enough control over scope
|
35
|
+
before do
|
36
|
+
allow(instance).to receive(:connect!).and_call_original
|
37
|
+
allow(::MarchHare).to receive(:connect).and_return(connection)
|
38
|
+
allow(connection).to receive(:create_channel).and_return(channel)
|
39
|
+
allow(connection).to receive(:on_shutdown)
|
40
|
+
allow(connection).to receive(:on_blocked)
|
41
|
+
allow(connection).to receive(:on_unblocked)
|
42
|
+
allow(channel).to receive(:exchange).and_return(exchange)
|
43
|
+
allow(channel).to receive(:queue).and_return(queue)
|
44
|
+
allow(channel).to receive(:prefetch=)
|
45
|
+
|
46
|
+
allow(queue).to receive(:build_consumer).with(:on_cancellation => anything)
|
47
|
+
allow(queue).to receive(:subscribe_with).with(any_args)
|
48
|
+
allow(queue).to receive(:bind).with(any_args)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should default the codec to JSON" do
|
52
|
+
expect(instance.codec).to_not be_nil
|
53
|
+
expect(instance.codec.config_name).to eq "json"
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#connect!" do
|
57
|
+
subject { hare_info }
|
58
|
+
|
59
|
+
context "without an exchange declared" do
|
60
|
+
before do
|
61
|
+
instance.register
|
62
|
+
instance.setup!
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should set the queue correctly" do
|
66
|
+
expect(subject.queue).to eql(queue)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should set the prefetch value correctly" do
|
70
|
+
expect(channel).to have_received(:prefetch=).with(123)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with an exchange declared" do
|
75
|
+
let(:exchange) { "exchange" }
|
76
|
+
let(:key) { "routing key" }
|
77
|
+
let(:rabbitmq_settings) { super.merge("exchange" => exchange, "key" => key, "exchange_type" => "fanout") }
|
78
|
+
|
79
|
+
before do
|
80
|
+
allow(instance).to receive(:declare_exchange!)
|
81
|
+
end
|
82
|
+
|
83
|
+
context "on run" do
|
84
|
+
before do
|
85
|
+
instance.register
|
86
|
+
instance.setup!
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should bind to the exchange" do
|
90
|
+
expect(queue).to have_received(:bind).with(exchange, :routing_key => key)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should declare the exchange" do
|
94
|
+
expect(instance).to have_received(:declare_exchange!)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "but not immediately available" do
|
99
|
+
before do
|
100
|
+
i = 0
|
101
|
+
allow(queue).to receive(:bind).with(any_args) do
|
102
|
+
i += 1
|
103
|
+
raise "foo" if i == 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should reconnect" do
|
108
|
+
instance.register
|
109
|
+
instance.setup!
|
110
|
+
expect(queue).to have_received(:bind).with(any_args).twice()
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "initially unable to subscribe" do
|
115
|
+
before do
|
116
|
+
i = 0
|
117
|
+
allow(queue).to receive(:subscribe_with).with(any_args) do
|
118
|
+
i += 1
|
119
|
+
raise "sub error" if i == 1
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should retry the subscribe" do
|
123
|
+
expect(queue).to have_receive(:subscribe_with).twice()
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "with a live server", :integration => true do
|
133
|
+
let(:klass) { LogStash::Inputs::RabbitMQ }
|
134
|
+
let(:config) { {"host" => "127.0.0.1", "auto_delete" => true, "codec" => "plain", "add_field" => {"[@metadata][foo]" => "bar"} } }
|
135
|
+
let(:instance) { klass.new(config) }
|
136
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
137
|
+
let(:output_queue) { Queue.new }
|
138
|
+
|
139
|
+
# Spawn a connection in the bg and wait up (n) seconds
|
140
|
+
def spawn_and_wait(instance)
|
141
|
+
instance.register
|
142
|
+
|
143
|
+
output_queue # materialize in this thread
|
144
|
+
|
145
|
+
Thread.new {
|
146
|
+
instance.run(output_queue)
|
147
|
+
}
|
148
|
+
|
149
|
+
20.times do
|
150
|
+
instance.connected? ? break : sleep(0.1)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Extra time to make sure the consumer can attach
|
154
|
+
# Without this there's a chance the shutdown code will execute
|
155
|
+
# before consumption begins. This is tricky to do more elegantly
|
156
|
+
sleep 4
|
157
|
+
end
|
158
|
+
|
159
|
+
let(:test_connection) { MarchHare.connect(instance.send(:rabbitmq_settings)) }
|
160
|
+
let(:test_channel) { test_connection.create_channel }
|
161
|
+
|
162
|
+
before do
|
163
|
+
# Materialize the instance in the current thread to prevent dupes
|
164
|
+
# If you use multiple threads with lazy evaluation weird stuff happens
|
165
|
+
instance
|
166
|
+
spawn_and_wait(instance)
|
167
|
+
|
168
|
+
test_channel # Start up the test client as well
|
169
|
+
end
|
170
|
+
|
171
|
+
after do
|
172
|
+
instance.stop()
|
173
|
+
test_channel.close
|
174
|
+
test_connection.close
|
175
|
+
end
|
176
|
+
|
177
|
+
context "using defaults" do
|
178
|
+
it "should start, connect, and stop cleanly" do
|
179
|
+
expect(instance.connected?).to be_truthy
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should have the correct prefetch value" do
|
184
|
+
expect(instance.instance_variable_get(:@hare_info).channel.prefetch).to eql(256)
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "receiving a message with a queue + exchange specified" do
|
188
|
+
let(:config) { super.merge("queue" => queue_name, "exchange" => exchange_name, "exchange_type" => "fanout", "metadata_enabled" => true) }
|
189
|
+
let(:event) { output_queue.pop }
|
190
|
+
let(:exchange) { test_channel.exchange(exchange_name, :type => "fanout") }
|
191
|
+
let(:exchange_name) { "logstash-input-rabbitmq-#{rand(0xFFFFFFFF)}" }
|
192
|
+
#let(:queue) { test_channel.queue(queue_name, :auto_delete => true) }
|
193
|
+
let(:queue_name) { "logstash-input-rabbitmq-#{rand(0xFFFFFFFF)}" }
|
194
|
+
|
195
|
+
after do
|
196
|
+
exchange.delete
|
197
|
+
end
|
198
|
+
|
199
|
+
context "when the message has a payload but no message headers" do
|
200
|
+
before do
|
201
|
+
exchange.publish(message)
|
202
|
+
end
|
203
|
+
|
204
|
+
let(:message) { "Foo Message" }
|
205
|
+
|
206
|
+
it "should process the message and store the payload" do
|
207
|
+
expect(event.get("message")).to eql(message)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should save an empty message header hash" do
|
211
|
+
expect(event).to include("@metadata")
|
212
|
+
expect(event.get("@metadata")).to include("rabbitmq_headers")
|
213
|
+
expect(event.get("[@metadata][rabbitmq_headers]")).to eq({})
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context "when message properties are available" do
|
218
|
+
before do
|
219
|
+
# Don't test every single property but select a few with
|
220
|
+
# different characteristics to get sufficient coverage.
|
221
|
+
exchange.publish("",
|
222
|
+
:properties => {
|
223
|
+
:app_id => app_id,
|
224
|
+
:timestamp => Java::JavaUtil::Date.new(epoch * 1000),
|
225
|
+
:priority => priority,
|
226
|
+
})
|
227
|
+
end
|
228
|
+
|
229
|
+
let(:app_id) { "myapplication" }
|
230
|
+
# Randomize the epoch we test with but limit its range to signed
|
231
|
+
# ints to not assume all protocols and libraries involved use
|
232
|
+
# unsigned ints for epoch values.
|
233
|
+
let(:epoch) { rand(0x7FFFFFFF) }
|
234
|
+
let(:priority) { 5 }
|
235
|
+
|
236
|
+
it "should save message properties into a @metadata field" do
|
237
|
+
expect(event).to include("@metadata")
|
238
|
+
expect(event.get("@metadata")).to include("rabbitmq_properties")
|
239
|
+
|
240
|
+
props = event.get("[@metadata][rabbitmq_properties")
|
241
|
+
expect(props["app-id"]).to eq(app_id)
|
242
|
+
expect(props["delivery-mode"]).to eq(1)
|
243
|
+
expect(props["exchange"]).to eq(exchange_name)
|
244
|
+
expect(props["priority"]).to eq(priority)
|
245
|
+
expect(props["routing-key"]).to eq("")
|
246
|
+
expect(props["timestamp"]).to eq(epoch)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "when message headers are available" do
|
251
|
+
before do
|
252
|
+
exchange.publish("", :properties => { :headers => headers })
|
253
|
+
end
|
254
|
+
|
255
|
+
let (:headers) {
|
256
|
+
{
|
257
|
+
"arrayvalue" => [true, 123, "foo"],
|
258
|
+
"boolvalue" => true,
|
259
|
+
"intvalue" => 123,
|
260
|
+
"stringvalue" => "foo",
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
it "should save message headers into a @metadata field" do
|
265
|
+
expect(event).to include("@metadata")
|
266
|
+
expect(event.get("@metadata")).to include("rabbitmq_headers")
|
267
|
+
expect(event.get("[@metadata][rabbitmq_headers]")).to include(headers)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should properly decorate the event" do
|
271
|
+
expect(event.get("[@metadata][foo]")).to eq("bar")
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe LogStash::Inputs::RabbitMQ do
|
277
|
+
it_behaves_like "an interruptible input plugin"
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/pipeline"
|
4
|
+
require "logstash/outputs/rabbitmq"
|
5
|
+
java_import java.util.concurrent.TimeoutException
|
6
|
+
|
7
|
+
describe LogStash::Outputs::RabbitMQ do
|
8
|
+
let(:klass) { LogStash::Outputs::RabbitMQ }
|
9
|
+
let(:host) { "localhost" }
|
10
|
+
let(:port) { 5672 }
|
11
|
+
let(:exchange_type) { "topic" }
|
12
|
+
let(:exchange) { "myexchange" }
|
13
|
+
let(:key) { "mykey" }
|
14
|
+
let(:persistent) { true }
|
15
|
+
let(:rabbitmq_settings) {
|
16
|
+
{
|
17
|
+
"host" => host,
|
18
|
+
"port" => port,
|
19
|
+
"exchange_type" => exchange_type,
|
20
|
+
"exchange" => exchange,
|
21
|
+
"key" => key,
|
22
|
+
"persistent" => persistent
|
23
|
+
}
|
24
|
+
}
|
25
|
+
let(:instance) { klass.new(rabbitmq_settings) }
|
26
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
27
|
+
|
28
|
+
shared_examples 'recovers from exception gracefully' do
|
29
|
+
it 'should execute publish twice due to a retry' do
|
30
|
+
expect(exchange).to have_received(:publish).twice
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should sleep for the retry' do
|
34
|
+
expect(instance).to have_received(:sleep_for_retry).once
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should send the correct message (twice)' do
|
38
|
+
expect(exchange).to have_received(:publish).with(encoded_event, anything).twice
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should send the correct metadata (twice)' do
|
42
|
+
expected_metadata = {:routing_key => event.sprintf(key), :properties => {:persistent => persistent }}
|
43
|
+
expect(exchange).to have_received(:publish).with(anything, expected_metadata).twice
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should register as an output plugin" do
|
48
|
+
expect(LogStash::Plugin.lookup("output", "rabbitmq")).to eql(LogStash::Outputs::RabbitMQ)
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when connected" do
|
52
|
+
let(:connection) { double("MarchHare Connection") }
|
53
|
+
let(:channel) { double("Channel") }
|
54
|
+
let(:exchange) { double("Exchange") }
|
55
|
+
|
56
|
+
before do
|
57
|
+
allow(instance).to receive(:connect!).and_call_original
|
58
|
+
allow(::MarchHare).to receive(:connect).and_return(connection)
|
59
|
+
allow(connection).to receive(:create_channel).and_return(channel)
|
60
|
+
allow(connection).to receive(:on_blocked)
|
61
|
+
allow(connection).to receive(:on_unblocked)
|
62
|
+
allow(connection).to receive(:on_shutdown)
|
63
|
+
allow(connection).to receive(:on_recovery_started)
|
64
|
+
allow(connection).to receive(:on_recovery)
|
65
|
+
allow(channel).to receive(:exchange).and_return(exchange)
|
66
|
+
|
67
|
+
instance.register
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#connect!" do
|
71
|
+
subject { hare_info }
|
72
|
+
|
73
|
+
it "should set the exchange correctly" do
|
74
|
+
expect(subject.exchange).to eql(exchange)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#publish_encoded" do
|
79
|
+
let(:event) { LogStash::Event.new("foo" => "bar") }
|
80
|
+
let(:sprinted_key) { double("sprinted key") }
|
81
|
+
let(:encoded_event) { LogStash::Json.dump(event) }
|
82
|
+
|
83
|
+
describe "issuing the publish" do
|
84
|
+
before do
|
85
|
+
allow(exchange).to receive(:publish).with(any_args)
|
86
|
+
allow(event).to receive(:sprintf).with(key).and_return(sprinted_key)
|
87
|
+
instance.send(:publish, event, encoded_event)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should send the correct message" do
|
91
|
+
expect(exchange).to have_received(:publish).with(encoded_event, anything)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should send the correct metadata" do
|
95
|
+
expected_metadata = {:routing_key => sprinted_key, :properties => {:persistent => persistent }}
|
96
|
+
|
97
|
+
expect(exchange).to have_received(:publish).with(anything, expected_metadata)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when an exception is encountered' do
|
102
|
+
let(:exception) { nil }
|
103
|
+
|
104
|
+
before do
|
105
|
+
i = 0
|
106
|
+
allow(instance).to receive(:connect!)
|
107
|
+
allow(instance).to receive(:sleep_for_retry)
|
108
|
+
allow(exchange).to receive(:publish).with(any_args) do
|
109
|
+
i += 1
|
110
|
+
raise exception if i == 1
|
111
|
+
end
|
112
|
+
|
113
|
+
instance.send(:publish, event, encoded_event)
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when it is a MarchHare exception' do
|
117
|
+
let(:exception) { MarchHare::Exception }
|
118
|
+
it_behaves_like 'recovers from exception gracefully'
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when it is a MarchHare::ChannelAlreadyClosed' do
|
122
|
+
let(:exception) { MarchHare::ChannelAlreadyClosed }
|
123
|
+
it_behaves_like 'recovers from exception gracefully'
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when it is a TimeoutException' do
|
127
|
+
let(:exception) { TimeoutException.new }
|
128
|
+
it_behaves_like 'recovers from exception gracefully'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
describe "with a live server", :integration => true do
|
137
|
+
let(:klass) { LogStash::Outputs::RabbitMQ }
|
138
|
+
let(:exchange) { "myexchange" }
|
139
|
+
let(:exchange_type) { "topic" }
|
140
|
+
let(:priority) { 34 }
|
141
|
+
let(:default_plugin_config) {
|
142
|
+
{
|
143
|
+
"host" => "127.0.0.1",
|
144
|
+
"exchange" => exchange,
|
145
|
+
"exchange_type" => exchange_type,
|
146
|
+
"key" => "foo",
|
147
|
+
"message_properties" => {
|
148
|
+
"priority" => priority
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
let(:config) { default_plugin_config }
|
153
|
+
let(:instance) { klass.new(config) }
|
154
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
155
|
+
|
156
|
+
# Spawn a connection in the bg and wait up (n) seconds
|
157
|
+
def spawn_and_wait(instance)
|
158
|
+
instance.register
|
159
|
+
|
160
|
+
20.times do
|
161
|
+
instance.connected? ? break : sleep(0.1)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Extra time to make sure the output can attach
|
165
|
+
sleep 1
|
166
|
+
end
|
167
|
+
let(:message) { LogStash::Event.new("message" => "Foo Message", "extra_field" => "Blah") }
|
168
|
+
let(:encoded) { message.to_json }
|
169
|
+
let(:test_connection) { MarchHare.connect(instance.send(:rabbitmq_settings)) }
|
170
|
+
let(:test_channel) { test_connection.create_channel }
|
171
|
+
let(:test_queue) {
|
172
|
+
test_channel.queue("testq", :auto_delete => true).bind(exchange, :key => config["key"])
|
173
|
+
}
|
174
|
+
|
175
|
+
before do
|
176
|
+
# Materialize the instance in the current thread to prevent dupes
|
177
|
+
# If you use multiple threads with lazy evaluation weird stuff happens
|
178
|
+
instance
|
179
|
+
spawn_and_wait(instance)
|
180
|
+
|
181
|
+
test_channel # Start up the test client as well
|
182
|
+
test_queue
|
183
|
+
end
|
184
|
+
|
185
|
+
after do
|
186
|
+
instance.close()
|
187
|
+
test_channel.close
|
188
|
+
test_connection.close
|
189
|
+
end
|
190
|
+
|
191
|
+
context "using defaults" do
|
192
|
+
it "should start, connect, and stop cleanly" do
|
193
|
+
expect(instance.connected?).to be_truthy
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should close cleanly" do
|
197
|
+
instance.close
|
198
|
+
expect(instance.connected?).to be_falsey
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'applies per message settings' do
|
202
|
+
instance.multi_receive_encoded([[message, encoded]])
|
203
|
+
sleep 1.0
|
204
|
+
|
205
|
+
message, payload = test_queue.pop
|
206
|
+
expect(message.properties.to_s).to include("priority=#{priority}")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "sending a message with an exchange specified" do
|
211
|
+
let(:message) { LogStash::Event.new("message" => "Foo Message", "extra_field" => "Blah") }
|
212
|
+
|
213
|
+
before do
|
214
|
+
@received = nil
|
215
|
+
test_queue.subscribe do |metadata,payload|
|
216
|
+
@received = payload
|
217
|
+
end
|
218
|
+
|
219
|
+
instance.multi_receive_encoded([[message, encoded]])
|
220
|
+
|
221
|
+
until @received
|
222
|
+
sleep 1
|
223
|
+
end
|
224
|
+
|
225
|
+
@decoded = LogStash::Json.load(@received)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should process the message fully using the default (JSON) codec" do
|
229
|
+
expect(@decoded).to eql(LogStash::Json.load(LogStash::Json.dump(message)))
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|