bunny 2.13.0 → 2.14.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
+
3
+ require "timeout"
4
+
2
5
  module Bunny
3
6
  # Unit, integration and stress testing toolkit
4
7
  class TestKit
5
8
  class << self
6
9
 
10
+ def poll_while(timeout = 60, &probe)
11
+ Timeout.timeout(timeout) {
12
+ sleep 0.1 while probe.call
13
+ }
14
+ end
15
+ def poll_until(timeout = 60, &probe)
16
+ Timeout.timeout(timeout) {
17
+ sleep 0.1 until probe.call
18
+ }
19
+ end
20
+
7
21
  # @return [Integer] Random integer in the range of [a, b]
8
22
  # @api private
9
23
  def random_in_range(a, b)
@@ -82,10 +82,29 @@ module Bunny
82
82
 
83
83
 
84
84
  def connect
85
- if uses_ssl?
86
- @socket.connect
87
- if uses_tls? && @verify_peer
88
- @socket.post_connection_check(host)
85
+ if uses_tls?
86
+ begin
87
+ @socket.connect
88
+ rescue OpenSSL::SSL::SSLError => e
89
+ @logger.error { "TLS connection failed: #{e.message}" }
90
+ raise e
91
+ end
92
+
93
+ log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
94
+ log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
95
+
96
+ begin
97
+ @socket.post_connection_check(host) if @verify_peer
98
+ rescue OpenSSL::SSL::SSLError => e
99
+ @logger.error do
100
+ msg = "Peer verification of target server failed: #{e.message}. "
101
+ msg += "Target hostname: #{hostname}, see peer certificate chain details below."
102
+ msg
103
+ end
104
+ log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
105
+ log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
106
+
107
+ raise e
89
108
  end
90
109
 
91
110
  @status = :connected
@@ -337,6 +356,29 @@ module Bunny
337
356
  end
338
357
  end
339
358
 
359
+ def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
360
+ exts = peer_cert.extensions.map { |x| x.value }
361
+ # Subject Alternative Names
362
+ sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
363
+
364
+ msg = "#{prefix} subject: #{peer_cert.subject}, "
365
+ msg += "subject alternative names: #{sans.join(', ')}, "
366
+ msg += "issuer: #{peer_cert.issuer}, "
367
+ msg += "not valid after: #{peer_cert.not_after}, "
368
+ msg += "X.509 usage extensions: #{exts.join(', ')}"
369
+
370
+ msg
371
+ end
372
+
373
+ def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
374
+ @logger.add(severity, peer_certificate_info(peer_cert, prefix))
375
+ end
376
+
377
+ def log_peer_certificate_chain_info(severity, chain)
378
+ chain.each do |cert|
379
+ self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
380
+ end
381
+ end
340
382
 
341
383
  def inline_client_certificate_from(opts)
342
384
  opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
@@ -424,8 +466,8 @@ module Bunny
424
466
 
425
467
  if !@tls_certificate
426
468
  @logger.warn <<-MSG
427
- Using TLS but no client certificate is provided! If RabbitMQ is configured to verify peer
428
- certificate, connection upgrade will fail!
469
+ Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
470
+ certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
429
471
  MSG
430
472
  end
431
473
  if @tls_certificate && !@tls_key
@@ -437,12 +479,13 @@ certificate, connection upgrade will fail!
437
479
  else
438
480
  OpenSSL::SSL::VERIFY_NONE
439
481
  end
482
+ @logger.debug { "Will use peer verification mode #{verify_mode}" }
440
483
  ctx.verify_mode = verify_mode
441
484
 
442
485
  if !@verify_peer
443
486
  @logger.warn <<-MSG
444
487
  Using TLS but peer hostname verification is disabled. This is convenient for local development
445
- but prone to man-in-the-middle attacks. Please set verify_peer: true in production!
488
+ but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
446
489
  MSG
447
490
  end
448
491
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "2.13.0"
5
+ VERSION = "2.14.1"
6
6
  end
@@ -0,0 +1,13 @@
1
+ listeners.tcp.1 = 0.0.0.0:5672
2
+
3
+ listeners.ssl.default = 5671
4
+
5
+ # mounted by docker-compose
6
+ ssl_options.cacertfile = /spec/tls/ca_certificate.pem
7
+ ssl_options.certfile = /spec/tls/server_certificate.pem
8
+ ssl_options.keyfile = /spec/tls/server_key.pem
9
+
10
+ ssl_options.verify = verify_none
11
+ ssl_options.fail_if_no_peer_cert = false
12
+
13
+ loopback_users = none
@@ -46,10 +46,10 @@ describe Bunny::Channel do
46
46
  s = "bunny-temp-q-#{rand}"
47
47
 
48
48
  expect(ch).to be_open
49
- ch.queue_declare(s, durable: false, exclusive: true)
49
+ ch.queue_declare(s, durable: false)
50
50
 
51
51
  expect do
52
- ch.queue_declare(s, durable: true, exclusive: false)
52
+ ch.queue_declare(s, durable: true)
53
53
  end.to raise_error(Bunny::PreconditionFailed)
54
54
 
55
55
  # channel.close is sent and handled concurrently with the test
@@ -57,6 +57,10 @@ describe Bunny::Channel do
57
57
  expect(ch).to be_closed
58
58
 
59
59
  expect { ch.close }.to raise_error(Bunny::ChannelAlreadyClosed)
60
+
61
+ cleanup_ch = connection.create_channel
62
+ cleanup_ch.queue_delete(s)
63
+ cleanup_ch.close
60
64
  end
61
65
  end
62
66
  end
@@ -1,6 +1,8 @@
1
1
  require "spec_helper"
2
2
  require "rabbitmq/http/client"
3
3
 
4
+ require "bunny/concurrent/condition"
5
+
4
6
  describe "Connection recovery" do
5
7
  let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
6
8
  let(:logger) { Logger.new($stderr).tap {|logger| logger.level = ENV["BUNNY_LOG_LEVEL"] || Logger::WARN } }
@@ -40,6 +42,21 @@ describe "Connection recovery" do
40
42
  end
41
43
  end
42
44
 
45
+ it "provides a recovery completion callback" do
46
+ with_open do |c|
47
+ latch = Bunny::Concurrent::Condition.new
48
+ c.after_recovery_completed do
49
+ latch.notify
50
+ end
51
+
52
+ ch = c.create_channel
53
+ sleep 1.0
54
+ close_all_connections!
55
+ poll_until { c.open? && ch.open? }
56
+ poll_until { latch.none_threads_waiting? }
57
+ end
58
+ end
59
+
43
60
  it "recovers channels (with multiple hosts)" do
44
61
  with_open_multi_host do |c|
45
62
  ch1 = c.create_channel
@@ -270,7 +287,6 @@ describe "Connection recovery" do
270
287
  destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
271
288
 
272
289
  source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true)
273
- destination2 = ch2.fanout("destination.exchange.recovery.example", no_declare: true)
274
290
 
275
291
  destination.bind(source)
276
292
 
@@ -413,15 +429,11 @@ describe "Connection recovery" do
413
429
  end
414
430
 
415
431
  def poll_while(&probe)
416
- Timeout.timeout(20) {
417
- sleep 0.1 while probe.call
418
- }
432
+ Bunny::TestKit.poll_while(&probe)
419
433
  end
420
434
 
421
435
  def poll_until(&probe)
422
- Timeout.timeout(20) {
423
- sleep 0.1 until probe.call
424
- }
436
+ Bunny::TestKit.poll_until(&probe)
425
437
  end
426
438
 
427
439
  def with_open(c = Bunny.new(network_recovery_interval: recovery_interval,
@@ -430,7 +442,7 @@ describe "Connection recovery" do
430
442
  c.start
431
443
  block.call(c)
432
444
  ensure
433
- c.close
445
+ c.close(false) rescue nil
434
446
  end
435
447
 
436
448
  def with_open_multi_host(&block)
@@ -107,18 +107,71 @@ describe Bunny::Queue do
107
107
  end
108
108
 
109
109
 
110
-
111
- context "when queue is declared with a different set of attributes" do
110
+ context "when queue is declared with a mismatching auto-delete property value" do
112
111
  it "raises an exception" do
113
112
  ch = connection.create_channel
114
113
 
115
114
  q = ch.queue("bunny.tests.queues.auto-delete", auto_delete: true, durable: false)
116
115
  expect {
117
116
  # force re-declaration
118
- ch.queue_declare("bunny.tests.queues.auto-delete", auto_delete: false, durable: true)
117
+ ch.queue_declare(q.name, auto_delete: false, durable: false)
119
118
  }.to raise_error(Bunny::PreconditionFailed)
120
119
 
121
120
  expect(ch).to be_closed
121
+
122
+ cleanup_ch = connection.create_channel
123
+ cleanup_ch.queue_delete(q.name)
124
+ end
125
+ end
126
+
127
+ context "when queue is declared with a mismatching durable property value" do
128
+ it "raises an exception" do
129
+ ch = connection.create_channel
130
+
131
+ q = ch.queue("bunny.tests.queues.durable", durable: true)
132
+ expect {
133
+ # force re-declaration
134
+ ch.queue_declare(q.name, durable: false)
135
+ }.to raise_error(Bunny::PreconditionFailed)
136
+
137
+ expect(ch).to be_closed
138
+
139
+ cleanup_ch = connection.create_channel
140
+ cleanup_ch.queue_delete(q.name)
141
+ end
142
+ end
143
+
144
+ context "when queue is declared with a mismatching exclusive property value" do
145
+ it "raises an exception" do
146
+ ch = connection.create_channel
147
+
148
+ q = ch.queue("bunny.tests.queues.exclusive.#{rand}", exclusive: true)
149
+ # when there's an exclusivity property mismatch, a different error
150
+ # (405 RESOURCE_LOCKED) is used. This is a leaked queue exclusivity/ownership
151
+ # implementation detail that's now basically a feature. MK.
152
+ expect {
153
+ # force re-declaration
154
+ ch.queue_declare(q.name, exclusive: false)
155
+ }.to raise_error(Bunny::ResourceLocked)
156
+
157
+ expect(ch).to be_closed
158
+ end
159
+ end
160
+
161
+ context "when queue is declared with a set of mismatching values" do
162
+ it "raises an exception" do
163
+ ch = connection.create_channel
164
+
165
+ q = ch.queue("bunny.tests.queues.proprty-equivalence", auto_delete: true, durable: false)
166
+ expect {
167
+ # force re-declaration
168
+ ch.queue_declare(q.name, auto_delete: false, durable: true)
169
+ }.to raise_error(Bunny::PreconditionFailed)
170
+
171
+ expect(ch).to be_closed
172
+
173
+ cleanup_ch = connection.create_channel
174
+ cleanup_ch.queue_delete(q.name)
122
175
  end
123
176
  end
124
177
 
@@ -5,7 +5,6 @@ if ::Toxiproxy.running?
5
5
  describe Bunny::Channel, "#basic_publish" do
6
6
  include RabbitMQ::Toxiproxy
7
7
 
8
-
9
8
  after :each do
10
9
  @connection.close if @connection.open?
11
10
  end
@@ -53,7 +52,8 @@ if ::Toxiproxy.running?
53
52
  setup_toxiproxy
54
53
  @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed",
55
54
  host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: true, network_recovery_interval: 1,
56
- recovery_attempts: 3, reset_recovery_attempts_after_reconnection: true)
55
+ recovery_attempts: 2, reset_recovery_attempts_after_reconnection: true,
56
+ disconnect_timeout: 1)
57
57
  @connection.start
58
58
  end
59
59
 
@@ -61,11 +61,12 @@ if ::Toxiproxy.running?
61
61
  expect(@connection.open?).to be(true)
62
62
 
63
63
  rabbitmq_toxiproxy.down do
64
- sleep 6
64
+ sleep 5
65
65
  end
66
- # give the connection oen last chance to recover
66
+ # give the connection one last chance to recover
67
67
  sleep 3
68
68
 
69
+ expect(@connection.open?).to be(false)
69
70
  expect(@connection.closed?).to be(true)
70
71
  end
71
72
  end # context
@@ -6,6 +6,7 @@ require 'bundler'
6
6
  Bundler.require(:default, :test)
7
7
 
8
8
  require "bunny"
9
+ require "bunny/test_kit"
9
10
 
10
11
  puts "Using Ruby #{RUBY_VERSION}, amq-protocol #{AMQ::Protocol::VERSION}"
11
12
 
@@ -0,0 +1,105 @@
1
+ require "spec_helper"
2
+
3
+ describe "A batch of messages proxied by multiple intermediate consumers" do
4
+ let(:c1) do
5
+ c = Bunny.new(username: "bunny_gem",
6
+ password: "bunny_password",
7
+ vhost: "bunny_testbed",
8
+ heartbeat_timeout: 6)
9
+ c.start
10
+ c
11
+ end
12
+
13
+ let(:c2) do
14
+ c = Bunny.new(username: "bunny_gem",
15
+ password: "bunny_password",
16
+ vhost: "bunny_testbed",
17
+ heartbeat_timeout: 6)
18
+ c.start
19
+ c
20
+ end
21
+
22
+ let(:c3) do
23
+ c = Bunny.new(username: "bunny_gem",
24
+ password: "bunny_password",
25
+ vhost: "bunny_testbed",
26
+ heartbeat_timeout: 6)
27
+ c.start
28
+ c
29
+ end
30
+
31
+ let(:c4) do
32
+ c = Bunny.new(username: "bunny_gem",
33
+ password: "bunny_password",
34
+ vhost: "bunny_testbed",
35
+ heartbeat_timeout: 6)
36
+ c.start
37
+ c
38
+ end
39
+
40
+ let(:c5) do
41
+ c = Bunny.new(username: "bunny_gem",
42
+ password: "bunny_password",
43
+ vhost: "bunny_testbed",
44
+ heartbeat_timeout: 6)
45
+ c.start
46
+ c
47
+ end
48
+
49
+ after :each do
50
+ [c1, c2, c3, c4, c5].each do |c|
51
+ c.close if c.open?
52
+ end
53
+ end
54
+
55
+ [1000, 5_000, 10_000, 20_000, 30_000, 50_000].each do |n|
56
+ # message flow is as follows:
57
+ #
58
+ # x => q4 => q3 => q2 => q1 => xs (results)
59
+ it "successfully reaches its final destination (batch size: #{n})" do
60
+ xs = []
61
+
62
+ ch1 = c1.create_channel
63
+ q1 = ch1.queue("", exclusive: true)
64
+ q1.subscribe(manual_ack: true) do |delivery_info, _, payload|
65
+ xs << payload
66
+ ch1.ack(delivery_info.delivery_tag)
67
+ end
68
+
69
+ ch2 = c2.create_channel
70
+ q2 = ch2.queue("", exclusive: true)
71
+ q2.subscribe do |_, _, payload|
72
+ q1.publish(payload)
73
+ end
74
+
75
+ ch3 = c3.create_channel(nil, 2)
76
+ q3 = ch3.queue("", exclusive: true)
77
+ q3.subscribe(manual_ack: true) do |delivery_info, _, payload|
78
+ q2.publish(payload)
79
+ ch3.ack(delivery_info.delivery_tag)
80
+ end
81
+
82
+ ch4 = c4.create_channel(nil, 4)
83
+ q4 = ch4.queue("", exclusive: true)
84
+ q4.subscribe do |_, _, payload|
85
+ q3.publish(payload)
86
+ end
87
+
88
+ ch5 = c5.create_channel(nil, 8)
89
+ x = ch5.default_exchange
90
+
91
+ n.times do |i|
92
+ x.publish("msg #{i}", routing_key: q4.name)
93
+ end
94
+
95
+ Bunny::TestKit.poll_until(90) do
96
+ xs.size >= n
97
+ end
98
+
99
+ expect(xs.size).to eq n
100
+ expect(xs.last).to eq "msg #{n - 1}"
101
+
102
+ [q1, q2, q3, q4].each { |q| q.delete }
103
+ end
104
+ end
105
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2018-12-25 00:00:00.000000000 Z
15
+ date: 2019-02-26 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -34,10 +34,9 @@ dependencies:
34
34
  - - "~>"
35
35
  - !ruby/object:Gem::Version
36
36
  version: '2.3'
37
- description: Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later
38
- versions.
37
+ description: Easy to use, feature complete Ruby client for RabbitMQ
39
38
  email:
40
- - michael.s.klishin@gmail.com
39
+ - mklishin@pivotal.io
41
40
  executables: []
42
41
  extensions: []
43
42
  extra_rdoc_files:
@@ -65,11 +64,13 @@ files:
65
64
  - benchmarks/queue_declare_bind_and_delete.rb
66
65
  - benchmarks/synchronized_sorted_set.rb
67
66
  - benchmarks/write_vs_write_nonblock.rb
68
- - bin/ci/before_build
69
67
  - bunny.gemspec
70
68
  - docker-compose.yml
71
69
  - docker/Dockerfile
70
+ - docker/apt/preferences.d/erlang
71
+ - docker/apt/sources.list.d/bintray.rabbitmq.list
72
72
  - docker/docker-entrypoint.sh
73
+ - docker/rabbitmq.conf
73
74
  - examples/connection/authentication_failure.rb
74
75
  - examples/connection/automatic_recovery_with_basic_get.rb
75
76
  - examples/connection/automatic_recovery_with_client_named_queues.rb
@@ -143,7 +144,7 @@ files:
143
144
  - profiling/basic_publish/with_4K_messages.rb
144
145
  - repl
145
146
  - spec/config/enabled_plugins
146
- - spec/config/rabbitmq.config
147
+ - spec/config/rabbitmq.conf
147
148
  - spec/higher_level_api/integration/basic_ack_spec.rb
148
149
  - spec/higher_level_api/integration/basic_cancel_spec.rb
149
150
  - spec/higher_level_api/integration/basic_consume_spec.rb
@@ -167,7 +168,6 @@ files:
167
168
  - spec/higher_level_api/integration/exchange_unbind_spec.rb
168
169
  - spec/higher_level_api/integration/exclusive_queue_spec.rb
169
170
  - spec/higher_level_api/integration/heartbeat_spec.rb
170
- - spec/higher_level_api/integration/merry_go_round_spec.rb
171
171
  - spec/higher_level_api/integration/message_properties_access_spec.rb
172
172
  - spec/higher_level_api/integration/predeclared_exchanges_spec.rb
173
173
  - spec/higher_level_api/integration/publisher_confirms_spec.rb
@@ -203,7 +203,7 @@ files:
203
203
  - spec/stress/concurrent_consumers_stress_spec.rb
204
204
  - spec/stress/concurrent_publishers_stress_spec.rb
205
205
  - spec/stress/connection_open_close_spec.rb
206
- - spec/stress/long_running_consumer_spec.rb
206
+ - spec/stress/merry_go_round_spec.rb
207
207
  - spec/tls/ca_certificate.pem
208
208
  - spec/tls/ca_key.pem
209
209
  - spec/tls/client_certificate.pem
@@ -240,14 +240,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
240
240
  - !ruby/object:Gem::Version
241
241
  version: '0'
242
242
  requirements: []
243
- rubyforge_project:
244
- rubygems_version: 2.7.8
243
+ rubygems_version: 3.0.2
245
244
  signing_key:
246
245
  specification_version: 4
247
- summary: Popular easy to use Ruby client for RabbitMQ
246
+ summary: Easy to use Ruby client for RabbitMQ
248
247
  test_files:
249
248
  - spec/config/enabled_plugins
250
- - spec/config/rabbitmq.config
249
+ - spec/config/rabbitmq.conf
251
250
  - spec/higher_level_api/integration/basic_ack_spec.rb
252
251
  - spec/higher_level_api/integration/basic_cancel_spec.rb
253
252
  - spec/higher_level_api/integration/basic_consume_spec.rb
@@ -271,7 +270,6 @@ test_files:
271
270
  - spec/higher_level_api/integration/exchange_unbind_spec.rb
272
271
  - spec/higher_level_api/integration/exclusive_queue_spec.rb
273
272
  - spec/higher_level_api/integration/heartbeat_spec.rb
274
- - spec/higher_level_api/integration/merry_go_round_spec.rb
275
273
  - spec/higher_level_api/integration/message_properties_access_spec.rb
276
274
  - spec/higher_level_api/integration/predeclared_exchanges_spec.rb
277
275
  - spec/higher_level_api/integration/publisher_confirms_spec.rb
@@ -307,7 +305,7 @@ test_files:
307
305
  - spec/stress/concurrent_consumers_stress_spec.rb
308
306
  - spec/stress/concurrent_publishers_stress_spec.rb
309
307
  - spec/stress/connection_open_close_spec.rb
310
- - spec/stress/long_running_consumer_spec.rb
308
+ - spec/stress/merry_go_round_spec.rb
311
309
  - spec/tls/ca_certificate.pem
312
310
  - spec/tls/ca_key.pem
313
311
  - spec/tls/client_certificate.pem