bunny 2.13.0 → 2.14.1

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.
@@ -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