logstash-integration-rabbitmq 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTORS +26 -0
- data/Gemfile +11 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +98 -0
- data/docs/index-input.asciidoc +395 -0
- data/docs/index-output.asciidoc +266 -0
- data/lib/logstash/inputs/rabbitmq.rb +317 -0
- data/lib/logstash/outputs/rabbitmq.rb +100 -0
- data/lib/logstash/plugin_mixins/rabbitmq_connection.rb +235 -0
- data/lib/logstash_registry.rb +7 -0
- data/logstash-integration-rabbitmq.gemspec +37 -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 +278 -0
- data/spec/outputs/rabbitmq_spec.rb +228 -0
- data/spec/plugin_mixins/rabbitmq_connection_spec.rb +175 -0
- metadata +241 -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,278 @@
|
|
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 be_a(LogStash::Codecs::JSON)
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#connect!" do
|
56
|
+
subject { hare_info }
|
57
|
+
|
58
|
+
context "without an exchange declared" do
|
59
|
+
before do
|
60
|
+
instance.register
|
61
|
+
instance.setup!
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should set the queue correctly" do
|
65
|
+
expect(subject.queue).to eql(queue)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should set the prefetch value correctly" do
|
69
|
+
expect(channel).to have_received(:prefetch=).with(123)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with an exchange declared" do
|
74
|
+
let(:exchange) { "exchange" }
|
75
|
+
let(:key) { "routing key" }
|
76
|
+
let(:rabbitmq_settings) { super.merge("exchange" => exchange, "key" => key, "exchange_type" => "fanout") }
|
77
|
+
|
78
|
+
before do
|
79
|
+
allow(instance).to receive(:declare_exchange!)
|
80
|
+
end
|
81
|
+
|
82
|
+
context "on run" do
|
83
|
+
before do
|
84
|
+
instance.register
|
85
|
+
instance.setup!
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should bind to the exchange" do
|
89
|
+
expect(queue).to have_received(:bind).with(exchange, :routing_key => key)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should declare the exchange" do
|
93
|
+
expect(instance).to have_received(:declare_exchange!)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "but not immediately available" do
|
98
|
+
before do
|
99
|
+
i = 0
|
100
|
+
allow(queue).to receive(:bind).with(any_args) do
|
101
|
+
i += 1
|
102
|
+
raise "foo" if i == 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should reconnect" do
|
107
|
+
instance.register
|
108
|
+
instance.setup!
|
109
|
+
expect(queue).to have_received(:bind).with(any_args).twice()
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "initially unable to subscribe" do
|
114
|
+
before do
|
115
|
+
i = 0
|
116
|
+
allow(queue).to receive(:subscribe_with).with(any_args) do
|
117
|
+
i += 1
|
118
|
+
raise "sub error" if i == 1
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should retry the subscribe" do
|
122
|
+
expect(queue).to have_receive(:subscribe_with).twice()
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "with a live server", :integration => true do
|
131
|
+
let(:klass) { LogStash::Inputs::RabbitMQ }
|
132
|
+
let(:config) { {"host" => "127.0.0.1", "auto_delete" => true, "codec" => "plain", "add_field" => {"[@metadata][foo]" => "bar"} } }
|
133
|
+
let(:instance) { klass.new(config) }
|
134
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
135
|
+
let(:output_queue) { Queue.new }
|
136
|
+
|
137
|
+
# Spawn a connection in the bg and wait up (n) seconds
|
138
|
+
def spawn_and_wait(instance)
|
139
|
+
instance.register
|
140
|
+
|
141
|
+
output_queue # materialize in this thread
|
142
|
+
|
143
|
+
Thread.new {
|
144
|
+
instance.run(output_queue)
|
145
|
+
}
|
146
|
+
|
147
|
+
20.times do
|
148
|
+
instance.connected? ? break : sleep(0.1)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Extra time to make sure the consumer can attach
|
152
|
+
# Without this there's a chance the shutdown code will execute
|
153
|
+
# before consumption begins. This is tricky to do more elegantly
|
154
|
+
sleep 4
|
155
|
+
end
|
156
|
+
|
157
|
+
let(:test_connection) { MarchHare.connect(instance.send(:rabbitmq_settings)) }
|
158
|
+
let(:test_channel) { test_connection.create_channel }
|
159
|
+
|
160
|
+
before do
|
161
|
+
# Materialize the instance in the current thread to prevent dupes
|
162
|
+
# If you use multiple threads with lazy evaluation weird stuff happens
|
163
|
+
instance
|
164
|
+
spawn_and_wait(instance)
|
165
|
+
|
166
|
+
test_channel # Start up the test client as well
|
167
|
+
end
|
168
|
+
|
169
|
+
after do
|
170
|
+
instance.stop()
|
171
|
+
test_channel.close
|
172
|
+
test_connection.close
|
173
|
+
end
|
174
|
+
|
175
|
+
context "using defaults" do
|
176
|
+
it "should start, connect, and stop cleanly" do
|
177
|
+
expect(instance.connected?).to be_truthy
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should have the correct prefetch value" do
|
182
|
+
expect(instance.instance_variable_get(:@hare_info).channel.prefetch).to eql(256)
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "receiving a message with a queue + exchange specified" do
|
186
|
+
let(:config) { super.merge("queue" => queue_name, "exchange" => exchange_name, "exchange_type" => "fanout", "metadata_enabled" => true) }
|
187
|
+
let(:event) { output_queue.pop }
|
188
|
+
let(:exchange) { test_channel.exchange(exchange_name, :type => "fanout") }
|
189
|
+
let(:exchange_name) { "logstash-input-rabbitmq-#{rand(0xFFFFFFFF)}" }
|
190
|
+
#let(:queue) { test_channel.queue(queue_name, :auto_delete => true) }
|
191
|
+
let(:queue_name) { "logstash-input-rabbitmq-#{rand(0xFFFFFFFF)}" }
|
192
|
+
|
193
|
+
after do
|
194
|
+
exchange.delete
|
195
|
+
end
|
196
|
+
|
197
|
+
context "when the message has a payload but no message headers" do
|
198
|
+
before do
|
199
|
+
exchange.publish(message)
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:message) { "Foo Message" }
|
203
|
+
|
204
|
+
it "should process the message and store the payload" do
|
205
|
+
expect(event.get("message")).to eql(message)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should save an empty message header hash" do
|
209
|
+
expect(event).to include("@metadata")
|
210
|
+
expect(event.get("@metadata")).to include("rabbitmq_headers")
|
211
|
+
expect(event.get("[@metadata][rabbitmq_headers]")).to eq({})
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when message properties are available" do
|
216
|
+
before do
|
217
|
+
# Don't test every single property but select a few with
|
218
|
+
# different characteristics to get sufficient coverage.
|
219
|
+
exchange.publish("",
|
220
|
+
:properties => {
|
221
|
+
:app_id => app_id,
|
222
|
+
:timestamp => Java::JavaUtil::Date.new(epoch * 1000),
|
223
|
+
:priority => priority,
|
224
|
+
})
|
225
|
+
end
|
226
|
+
|
227
|
+
let(:app_id) { "myapplication" }
|
228
|
+
# Randomize the epoch we test with but limit its range to signed
|
229
|
+
# ints to not assume all protocols and libraries involved use
|
230
|
+
# unsigned ints for epoch values.
|
231
|
+
let(:epoch) { rand(0x7FFFFFFF) }
|
232
|
+
let(:priority) { 5 }
|
233
|
+
|
234
|
+
it "should save message properties into a @metadata field" do
|
235
|
+
expect(event).to include("@metadata")
|
236
|
+
expect(event.get("@metadata")).to include("rabbitmq_properties")
|
237
|
+
|
238
|
+
props = event.get("[@metadata][rabbitmq_properties")
|
239
|
+
expect(props["app-id"]).to eq(app_id)
|
240
|
+
expect(props["delivery-mode"]).to eq(1)
|
241
|
+
expect(props["exchange"]).to eq(exchange_name)
|
242
|
+
expect(props["priority"]).to eq(priority)
|
243
|
+
expect(props["routing-key"]).to eq("")
|
244
|
+
expect(props["timestamp"]).to eq(epoch)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when message headers are available" do
|
249
|
+
before do
|
250
|
+
exchange.publish("", :properties => { :headers => headers })
|
251
|
+
end
|
252
|
+
|
253
|
+
let (:headers) {
|
254
|
+
{
|
255
|
+
"arrayvalue" => [true, 123, "foo"],
|
256
|
+
"boolvalue" => true,
|
257
|
+
"intvalue" => 123,
|
258
|
+
"stringvalue" => "foo",
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
it "should save message headers into a @metadata field" do
|
263
|
+
expect(event).to include("@metadata")
|
264
|
+
expect(event.get("@metadata")).to include("rabbitmq_headers")
|
265
|
+
expect(event.get("[@metadata][rabbitmq_headers]")).to include(headers)
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should properly decorate the event" do
|
269
|
+
expect(event.get("[@metadata][foo]")).to eq("bar")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe LogStash::Inputs::RabbitMQ do
|
275
|
+
it_behaves_like "an interruptible input plugin"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,228 @@
|
|
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(channel).to receive(:exchange).and_return(exchange)
|
64
|
+
|
65
|
+
instance.register
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#connect!" do
|
69
|
+
subject { hare_info }
|
70
|
+
|
71
|
+
it "should set the exchange correctly" do
|
72
|
+
expect(subject.exchange).to eql(exchange)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#publish_encoded" do
|
77
|
+
let(:event) { LogStash::Event.new("foo" => "bar") }
|
78
|
+
let(:sprinted_key) { double("sprinted key") }
|
79
|
+
let(:encoded_event) { LogStash::Json.dump(event) }
|
80
|
+
|
81
|
+
describe "issuing the publish" do
|
82
|
+
before do
|
83
|
+
allow(exchange).to receive(:publish).with(any_args)
|
84
|
+
allow(event).to receive(:sprintf).with(key).and_return(sprinted_key)
|
85
|
+
instance.send(:publish, event, encoded_event)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should send the correct message" do
|
89
|
+
expect(exchange).to have_received(:publish).with(encoded_event, anything)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should send the correct metadata" do
|
93
|
+
expected_metadata = {:routing_key => sprinted_key, :properties => {:persistent => persistent }}
|
94
|
+
|
95
|
+
expect(exchange).to have_received(:publish).with(anything, expected_metadata)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when an exception is encountered' do
|
100
|
+
let(:exception) { nil }
|
101
|
+
|
102
|
+
before do
|
103
|
+
i = 0
|
104
|
+
allow(instance).to receive(:connect!)
|
105
|
+
allow(instance).to receive(:sleep_for_retry)
|
106
|
+
allow(exchange).to receive(:publish).with(any_args) do
|
107
|
+
i += 1
|
108
|
+
raise exception if i == 1
|
109
|
+
end
|
110
|
+
|
111
|
+
instance.send(:publish, event, encoded_event)
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'when it is a MarchHare exception' do
|
115
|
+
let(:exception) { MarchHare::Exception }
|
116
|
+
it_behaves_like 'recovers from exception gracefully'
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when it is a MarchHare::ChannelAlreadyClosed' do
|
120
|
+
let(:exception) { MarchHare::ChannelAlreadyClosed }
|
121
|
+
it_behaves_like 'recovers from exception gracefully'
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when it is a TimeoutException' do
|
125
|
+
let(:exception) { TimeoutException.new }
|
126
|
+
it_behaves_like 'recovers from exception gracefully'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
describe "with a live server", :integration => true do
|
132
|
+
let(:klass) { LogStash::Outputs::RabbitMQ }
|
133
|
+
let(:exchange) { "myexchange" }
|
134
|
+
let(:exchange_type) { "topic" }
|
135
|
+
let(:priority) { 34 }
|
136
|
+
let(:default_plugin_config) {
|
137
|
+
{
|
138
|
+
"host" => "127.0.0.1",
|
139
|
+
"exchange" => exchange,
|
140
|
+
"exchange_type" => exchange_type,
|
141
|
+
"key" => "foo",
|
142
|
+
"message_properties" => {
|
143
|
+
"priority" => priority
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
let(:config) { default_plugin_config }
|
148
|
+
let(:instance) { klass.new(config) }
|
149
|
+
let(:hare_info) { instance.instance_variable_get(:@hare_info) }
|
150
|
+
|
151
|
+
# Spawn a connection in the bg and wait up (n) seconds
|
152
|
+
def spawn_and_wait(instance)
|
153
|
+
instance.register
|
154
|
+
|
155
|
+
20.times do
|
156
|
+
instance.connected? ? break : sleep(0.1)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Extra time to make sure the output can attach
|
160
|
+
sleep 1
|
161
|
+
end
|
162
|
+
let(:message) { LogStash::Event.new("message" => "Foo Message", "extra_field" => "Blah") }
|
163
|
+
let(:encoded) { message.to_json }
|
164
|
+
let(:test_connection) { MarchHare.connect(instance.send(:rabbitmq_settings)) }
|
165
|
+
let(:test_channel) { test_connection.create_channel }
|
166
|
+
let(:test_queue) {
|
167
|
+
test_channel.queue("testq", :auto_delete => true).bind(exchange, :key => config["key"])
|
168
|
+
}
|
169
|
+
|
170
|
+
before do
|
171
|
+
# Materialize the instance in the current thread to prevent dupes
|
172
|
+
# If you use multiple threads with lazy evaluation weird stuff happens
|
173
|
+
instance
|
174
|
+
spawn_and_wait(instance)
|
175
|
+
|
176
|
+
test_channel # Start up the test client as well
|
177
|
+
test_queue
|
178
|
+
end
|
179
|
+
|
180
|
+
after do
|
181
|
+
instance.close()
|
182
|
+
test_channel.close
|
183
|
+
test_connection.close
|
184
|
+
end
|
185
|
+
|
186
|
+
context "using defaults" do
|
187
|
+
it "should start, connect, and stop cleanly" do
|
188
|
+
expect(instance.connected?).to be_truthy
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should close cleanly" do
|
192
|
+
instance.close
|
193
|
+
expect(instance.connected?).to be_falsey
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'applies per message settings' do
|
197
|
+
instance.multi_receive_encoded([[message, encoded]])
|
198
|
+
sleep 1.0
|
199
|
+
|
200
|
+
message, payload = test_queue.pop
|
201
|
+
expect(message.properties.to_s).to include("priority=#{priority}")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "sending a message with an exchange specified" do
|
206
|
+
let(:message) { LogStash::Event.new("message" => "Foo Message", "extra_field" => "Blah") }
|
207
|
+
|
208
|
+
before do
|
209
|
+
@received = nil
|
210
|
+
test_queue.subscribe do |metadata,payload|
|
211
|
+
@received = payload
|
212
|
+
end
|
213
|
+
|
214
|
+
instance.multi_receive_encoded([[message, encoded]])
|
215
|
+
|
216
|
+
until @received
|
217
|
+
sleep 1
|
218
|
+
end
|
219
|
+
|
220
|
+
@decoded = LogStash::Json.load(@received)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should process the message fully using the default (JSON) codec" do
|
224
|
+
expect(@decoded).to eql(LogStash::Json.load(LogStash::Json.dump(message)))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|