bunny 1.7.0 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +6 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +745 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +41 -75
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +4 -10
  13. data/docker-compose.yml +28 -0
  14. data/docker/Dockerfile +24 -0
  15. data/docker/apt/preferences.d/erlang +3 -0
  16. data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
  17. data/docker/docker-entrypoint.sh +26 -0
  18. data/docker/rabbitmq.conf +29 -0
  19. data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
  20. data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
  21. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
  22. data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
  23. data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
  24. data/examples/connection/channel_level_exception.rb +1 -9
  25. data/examples/connection/disabled_automatic_recovery.rb +1 -1
  26. data/examples/connection/heartbeat.rb +1 -1
  27. data/examples/consumers/high_and_low_priority.rb +1 -1
  28. data/examples/guides/extensions/alternate_exchange.rb +2 -0
  29. data/examples/guides/getting_started/hello_world.rb +2 -0
  30. data/examples/guides/getting_started/weathr.rb +2 -0
  31. data/examples/guides/queues/one_off_consumer.rb +2 -0
  32. data/examples/guides/queues/redeliveries.rb +2 -0
  33. data/lib/bunny.rb +6 -2
  34. data/lib/bunny/channel.rb +192 -109
  35. data/lib/bunny/channel_id_allocator.rb +6 -4
  36. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  37. data/lib/bunny/consumer_work_pool.rb +34 -6
  38. data/lib/bunny/cruby/socket.rb +29 -16
  39. data/lib/bunny/cruby/ssl_socket.rb +20 -7
  40. data/lib/bunny/exceptions.rb +7 -1
  41. data/lib/bunny/exchange.rb +11 -7
  42. data/lib/bunny/get_response.rb +1 -1
  43. data/lib/bunny/heartbeat_sender.rb +3 -2
  44. data/lib/bunny/jruby/socket.rb +23 -6
  45. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  46. data/lib/bunny/queue.rb +12 -10
  47. data/lib/bunny/reader_loop.rb +31 -18
  48. data/lib/bunny/session.rb +389 -134
  49. data/lib/bunny/test_kit.rb +14 -0
  50. data/lib/bunny/timeout.rb +1 -12
  51. data/lib/bunny/transport.rb +114 -67
  52. data/lib/bunny/version.rb +1 -1
  53. data/repl +1 -1
  54. data/spec/config/rabbitmq.conf +13 -0
  55. data/spec/higher_level_api/integration/basic_ack_spec.rb +154 -22
  56. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  57. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  58. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  59. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  60. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  61. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  62. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  63. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  64. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  65. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  66. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  67. data/spec/higher_level_api/integration/connection_recovery_spec.rb +412 -286
  68. data/spec/higher_level_api/integration/connection_spec.rb +284 -134
  69. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  70. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  71. data/spec/higher_level_api/integration/dead_lettering_spec.rb +14 -14
  72. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  73. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  74. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  75. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  77. data/spec/higher_level_api/integration/heartbeat_spec.rb +4 -4
  78. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  79. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  80. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +92 -27
  81. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  82. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  83. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  84. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  85. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  86. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  87. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  88. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  89. data/spec/higher_level_api/integration/tls_connection_spec.rb +218 -112
  90. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  91. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  92. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  93. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  94. data/spec/issues/issue100_spec.rb +11 -12
  95. data/spec/issues/issue141_spec.rb +13 -14
  96. data/spec/issues/issue202_spec.rb +1 -1
  97. data/spec/issues/issue224_spec.rb +5 -5
  98. data/spec/issues/issue465_spec.rb +32 -0
  99. data/spec/issues/issue549_spec.rb +30 -0
  100. data/spec/issues/issue78_spec.rb +21 -24
  101. data/spec/issues/issue83_spec.rb +5 -6
  102. data/spec/issues/issue97_spec.rb +44 -45
  103. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  104. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  105. data/spec/spec_helper.rb +2 -19
  106. data/spec/stress/channel_close_stress_spec.rb +3 -3
  107. data/spec/stress/channel_open_stress_spec.rb +4 -4
  108. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  109. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  110. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  111. data/spec/stress/connection_open_close_spec.rb +9 -9
  112. data/spec/stress/merry_go_round_spec.rb +105 -0
  113. data/spec/tls/ca_certificate.pem +27 -16
  114. data/spec/tls/ca_key.pem +52 -27
  115. data/spec/tls/client_certificate.pem +27 -16
  116. data/spec/tls/client_key.pem +49 -25
  117. data/spec/tls/generate-server-cert.sh +8 -0
  118. data/spec/tls/server-openssl.cnf +10 -0
  119. data/spec/tls/server.csr +16 -0
  120. data/spec/tls/server_certificate.pem +27 -16
  121. data/spec/tls/server_key.pem +49 -25
  122. data/spec/toxiproxy_helper.rb +28 -0
  123. data/spec/unit/bunny_spec.rb +5 -5
  124. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  125. data/spec/unit/concurrent/condition_spec.rb +8 -8
  126. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  127. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  128. data/spec/unit/exchange_recovery_spec.rb +39 -0
  129. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  130. metadata +42 -35
  131. data/lib/bunny/system_timer.rb +0 -20
  132. data/spec/config/rabbitmq.config +0 -18
  133. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  134. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  135. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  136. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  137. data/spec/stress/long_running_consumer_spec.rb +0 -83
  138. data/spec/tls/cacert.pem +0 -18
  139. data/spec/tls/client_cert.pem +0 -18
  140. data/spec/tls/server_cert.pem +0 -18
  141. data/spec/unit/system_timer_spec.rb +0 -10
@@ -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)
@@ -1,16 +1,5 @@
1
1
  module Bunny
2
- # Unifies Ruby standard library's Timeout (which is not accurate on
3
- # Ruby 1.8) and SystemTimer (the gem)
4
- Timeout = if RUBY_VERSION < "1.9"
5
- begin
6
- require "bunny/system_timer"
7
- Bunny::SystemTimer
8
- rescue LoadError
9
- Timeout
10
- end
11
- else
12
- Timeout
13
- end
2
+ Timeout = ::Timeout
14
3
 
15
4
  # Backwards compatibility
16
5
  # @private
@@ -4,7 +4,7 @@ require "monitor"
4
4
 
5
5
  begin
6
6
  require "openssl"
7
- rescue LoadError => le
7
+ rescue LoadError => _le
8
8
  $stderr.puts "Could not load OpenSSL"
9
9
  end
10
10
 
@@ -20,19 +20,22 @@ module Bunny
20
20
  #
21
21
 
22
22
  # Default TCP connection timeout
23
- DEFAULT_CONNECTION_TIMEOUT = 25.0
23
+ DEFAULT_CONNECTION_TIMEOUT = 30.0
24
24
 
25
- DEFAULT_READ_TIMEOUT = 5.0
26
- DEFAULT_WRITE_TIMEOUT = 5.0
25
+ DEFAULT_READ_TIMEOUT = 30.0
26
+ DEFAULT_WRITE_TIMEOUT = 30.0
27
27
 
28
28
  attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
- attr_reader :tls_context
29
+ attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
30
 
31
- attr_writer :read_timeout
31
+ def read_timeout=(v)
32
+ @read_timeout = v
33
+ @read_timeout = nil if @read_timeout == 0
34
+ end
32
35
 
33
36
  def initialize(session, host, port, opts)
34
37
  @session = session
35
- @session_thread = opts[:session_thread]
38
+ @session_error_handler = opts[:session_error_handler]
36
39
  @host = host
37
40
  @port = port
38
41
  @opts = opts
@@ -54,6 +57,8 @@ module Bunny
54
57
 
55
58
  @writes_mutex = @session.mutex_impl.new
56
59
 
60
+ @socket = nil
61
+
57
62
  prepare_tls_context(opts) if @tls_enabled
58
63
  end
59
64
 
@@ -77,10 +82,29 @@ module Bunny
77
82
 
78
83
 
79
84
  def connect
80
- if uses_ssl?
81
- @socket.connect
82
- if uses_tls? && @verify_peer
83
- @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
84
108
  end
85
109
 
86
110
  @status = :connected
@@ -92,7 +116,7 @@ module Bunny
92
116
  end
93
117
 
94
118
  def connected?
95
- :not_connected == @status && open?
119
+ :connected == @status && open?
96
120
  end
97
121
 
98
122
  def configure_socket(&block)
@@ -122,7 +146,7 @@ module Bunny
122
146
  if @session.automatically_recover?
123
147
  @session.handle_network_failure(e)
124
148
  else
125
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
149
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
126
150
  end
127
151
  end
128
152
  end
@@ -146,24 +170,25 @@ module Bunny
146
170
  if @session.automatically_recover?
147
171
  @session.handle_network_failure(e)
148
172
  else
149
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
173
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
150
174
  end
151
175
  end
152
176
  end
153
177
  end
154
178
 
155
179
  # Writes data to the socket without timeout checks
156
- def write_without_timeout(data)
180
+ def write_without_timeout(data, raise_exceptions = false)
157
181
  begin
158
182
  @writes_mutex.synchronize { @socket.write(data) }
159
183
  @socket.flush
160
184
  rescue SystemCallError, Bunny::ConnectionError, IOError => e
161
185
  close
186
+ raise e if raise_exceptions
162
187
 
163
188
  if @session.automatically_recover?
164
189
  @session.handle_network_failure(e)
165
190
  else
166
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
191
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
167
192
  end
168
193
  end
169
194
  end
@@ -218,9 +243,9 @@ module Bunny
218
243
  @status = :not_connected
219
244
 
220
245
  if @session.automatically_recover?
221
- @session.handle_network_failure(e)
246
+ raise
222
247
  else
223
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
248
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
224
249
  end
225
250
  end
226
251
  end
@@ -233,17 +258,13 @@ module Bunny
233
258
  # Exposed primarily for Bunny::Channel
234
259
  # @private
235
260
  def read_next_frame(opts = {})
236
- header = read_fully(7)
237
- # TODO: network issues here will sometimes cause
238
- # the socket method return an empty string. We need to log
239
- # and handle this better.
240
- # type, channel, size = begin
241
- # AMQ::Protocol::Frame.decode_header(header)
242
- # rescue AMQ::Protocol::EmptyResponseError => e
243
- # puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
244
- # end
261
+ header = read_fully(7)
245
262
  type, channel, size = AMQ::Protocol::Frame.decode_header(header)
246
- payload = read_fully(size)
263
+ payload = if size > 0
264
+ read_fully(size)
265
+ else
266
+ ''
267
+ end
247
268
  frame_end = read_fully(1)
248
269
 
249
270
  # 1) the size is miscalculated
@@ -263,7 +284,7 @@ module Bunny
263
284
  :connect_timeout => timeout)
264
285
 
265
286
  true
266
- rescue SocketError, Timeout::Error => e
287
+ rescue SocketError, Timeout::Error => _e
267
288
  false
268
289
  ensure
269
290
  s.close if s
@@ -292,7 +313,7 @@ module Bunny
292
313
  end
293
314
 
294
315
  def post_initialize_socket
295
- @socket = if uses_tls?
316
+ @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
296
317
  wrap_in_tls_socket(@socket)
297
318
  else
298
319
  @socket
@@ -302,23 +323,27 @@ module Bunny
302
323
  protected
303
324
 
304
325
  def tls_enabled?(opts)
305
- return opts[:tls] unless opts[:tls].nil?
306
- return opts[:ssl] unless opts[:ssl].nil?
326
+ return !!opts[:tls] unless opts[:tls].nil?
327
+ return !!opts[:ssl] unless opts[:ssl].nil?
307
328
  (opts[:port] == AMQ::Protocol::TLS_PORT) || false
308
329
  end
309
330
 
331
+ def tls_ca_certificates_paths_from(opts)
332
+ Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
333
+ end
334
+
310
335
  def tls_certificate_path_from(opts)
311
- opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
336
+ opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
312
337
  end
313
338
 
314
339
  def tls_key_path_from(opts)
315
- opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
340
+ opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
316
341
  end
317
342
 
318
343
  def tls_certificate_from(opts)
319
344
  begin
320
345
  read_client_certificate!
321
- rescue MissingTLSCertificateFile => e
346
+ rescue MissingTLSCertificateFile => _e
322
347
  inline_client_certificate_from(opts)
323
348
  end
324
349
  end
@@ -326,11 +351,34 @@ module Bunny
326
351
  def tls_key_from(opts)
327
352
  begin
328
353
  read_client_key!
329
- rescue MissingTLSKeyFile => e
354
+ rescue MissingTLSKeyFile => _e
330
355
  inline_client_key_from(opts)
331
356
  end
332
357
  end
333
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
334
382
 
335
383
  def inline_client_certificate_from(opts)
336
384
  opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
@@ -341,7 +389,7 @@ module Bunny
341
389
  end
342
390
 
343
391
  def prepare_tls_context(opts)
344
- if (opts[:verify_ssl] || opts[:verify_peer]).nil?
392
+ if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
345
393
  opts[:verify_peer] = true
346
394
  end
347
395
 
@@ -353,17 +401,32 @@ module Bunny
353
401
  @tls_key = tls_key_from(opts)
354
402
  @tls_certificate_store = opts[:tls_certificate_store]
355
403
 
356
- @tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates)
357
- @verify_peer = (opts[:verify_ssl] || opts[:verify_peer])
404
+ @verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
358
405
 
359
406
  @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
360
407
  end
361
408
 
409
+ def as_boolean(val)
410
+ case val
411
+ when true then true
412
+ when false then false
413
+ when "true" then true
414
+ when "false" then false
415
+ else
416
+ !!val
417
+ end
418
+ end
419
+
362
420
  def wrap_in_tls_socket(socket)
363
421
  raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket
364
422
  raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
365
423
 
366
424
  s = Bunny::SSLSocketImpl.new(socket, @tls_context)
425
+
426
+ # always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
427
+ # that TLS clients supporting the extensions can talk to TLS servers that do not
428
+ s.hostname = @host if s.respond_to?(:hostname)
429
+
367
430
  s.sync_close = true
368
431
  s
369
432
  end
@@ -396,13 +459,15 @@ module Bunny
396
459
  ctx.cert_store = if @tls_certificate_store
397
460
  @tls_certificate_store
398
461
  else
462
+ # this ivar exists so that this value can be exposed in the API
463
+ @tls_ca_certificates = tls_ca_certificates_paths_from(opts)
399
464
  initialize_tls_certificate_store(@tls_ca_certificates)
400
465
  end
401
466
 
402
467
  if !@tls_certificate
403
468
  @logger.warn <<-MSG
404
- Using TLS but no client certificate is provided! If RabbitMQ is configured to verify peer
405
- 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
406
471
  MSG
407
472
  end
408
473
  if @tls_certificate && !@tls_key
@@ -414,12 +479,13 @@ certificate, connection upgrade will fail!
414
479
  else
415
480
  OpenSSL::SSL::VERIFY_NONE
416
481
  end
482
+ @logger.debug { "Will use peer verification mode #{verify_mode}" }
417
483
  ctx.verify_mode = verify_mode
418
484
 
419
485
  if !@verify_peer
420
486
  @logger.warn <<-MSG
421
487
  Using TLS but peer hostname verification is disabled. This is convenient for local development
422
- but prone 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
423
489
  MSG
424
490
  end
425
491
 
@@ -429,41 +495,22 @@ but prone man-in-the-middle attacks. Please set :verify_peer => true in producti
429
495
  ctx
430
496
  end
431
497
 
432
- def default_tls_certificates
433
- if defined?(JRUBY_VERSION)
434
- # see https://github.com/jruby/jruby/issues/1055. MK.
435
- []
436
- else
437
- default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
438
- default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
439
-
440
- [
441
- default_ca_file,
442
- File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
443
- File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
444
- File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
445
- ].uniq
446
- end
447
- end
448
-
449
498
  def initialize_tls_certificate_store(certs)
450
499
  cert_files = []
451
500
  cert_inlines = []
452
501
  certs.each do |cert|
453
- # if it starts with / then it's a file path that may or may not
454
- # exists (e.g. a default OpenSSL path). MK.
455
- if File.readable?(cert) || cert =~ /^\//
502
+ # if it starts with / or C:/ then it's a file path that may or may not
503
+ # exist (e.g. a default OpenSSL path). MK.
504
+ if File.readable?(cert) || cert =~ /^([a-z]:?)?\//i
456
505
  cert_files.push(cert)
457
506
  else
458
507
  cert_inlines.push(cert)
459
508
  end
460
509
  end
461
- @logger.debug "Using CA certificates at #{cert_files.join(', ')}"
462
- @logger.debug "Using #{cert_inlines.count} inline CA certificates"
463
- if certs.empty?
464
- @logger.error "No CA certificates found, add one with :tls_ca_certificates"
465
- end
510
+ @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
511
+ @logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
466
512
  OpenSSL::X509::Store.new.tap do |store|
513
+ store.set_default_paths
467
514
  cert_files.select { |path| File.readable?(path) }.
468
515
  each { |path| store.add_file(path) }
469
516
  cert_inlines.
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.7.0"
5
+ VERSION = "2.17.0"
6
6
  end
data/repl CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/bin/sh
2
2
 
3
- bundle exec irb -Ilib -r'bunny'
3
+ bundle exec ruby `which ripl` -Ilib -r'bunny' -r'ripl/multi_line' -r'ripl/irb'
@@ -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
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Channel, "#ack" do
4
4
  let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
5
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
6
6
  c.start
7
7
  c
8
8
  end
@@ -14,50 +14,98 @@ describe Bunny::Channel, "#ack" do
14
14
  context "with a valid (known) delivery tag" do
15
15
  it "acknowledges a message" do
16
16
  ch = connection.create_channel
17
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
17
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
18
18
  x = ch.default_exchange
19
19
 
20
- x.publish("bunneth", :routing_key => q.name)
20
+ x.publish("bunneth", routing_key: q.name)
21
21
  sleep 0.5
22
- q.message_count.should == 1
23
- delivery_details, properties, content = q.pop(:manual_ack => true)
22
+ expect(q.message_count).to eq 1
23
+ delivery_details, properties, content = q.pop(manual_ack: true)
24
24
 
25
25
  ch.ack(delivery_details.delivery_tag, true)
26
- q.message_count.should == 0
26
+ ch.close
27
+
28
+ ch = connection.create_channel
29
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
30
+ expect(q.message_count).to eq 0
31
+ ch.close
32
+ end
33
+ end
34
+
35
+ context "with a valid (known) delivery tag (multiple = true)" do
36
+ it "acknowledges a message" do
37
+ ch = connection.create_channel
38
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
39
+ x = ch.default_exchange
27
40
 
41
+ x.publish("bunneth", routing_key: q.name)
42
+ x.publish("bunneth", routing_key: q.name)
43
+ sleep 0.5
44
+ expect(q.message_count).to eq 2
45
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
46
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
47
+
48
+ ch.ack(delivery_details_2.delivery_tag, true)
49
+ ch.close
50
+
51
+ ch = connection.create_channel
52
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
53
+ expect(q.message_count).to eq 0
28
54
  ch.close
29
55
  end
30
56
  end
31
57
 
58
+ context "with a valid (known) delivery tag (multiple = false)" do
59
+ it "acknowledges a message" do
60
+ ch = connection.create_channel
61
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
62
+ x = ch.default_exchange
63
+
64
+ x.publish("bunneth", routing_key: q.name)
65
+ x.publish("bunneth", routing_key: q.name)
66
+ sleep 0.5
67
+ expect(q.message_count).to eq 2
68
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
69
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
70
+
71
+ ch.ack(delivery_details_2.delivery_tag, false)
72
+ ch.close
73
+
74
+ ch = connection.create_channel
75
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
76
+ expect(q.message_count).to eq 1
77
+ ch.close
78
+ end
79
+ end
32
80
 
33
81
  context "with a valid (known) delivery tag and automatic ack mode" do
34
82
  it "results in a channel exception" do
35
83
  ch = connection.create_channel
36
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
84
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
37
85
  x = ch.default_exchange
38
86
 
39
- q.subscribe(:manual_ack => false) do |delivery_info, properties, payload|
87
+ q.subscribe(manual_ack: false) do |delivery_info, properties, payload|
40
88
  ch.ack(delivery_info.delivery_tag, false)
41
89
  end
42
90
 
43
- x.publish("bunneth", :routing_key => q.name)
91
+ x.publish("bunneth", routing_key: q.name)
44
92
  sleep 0.5
45
- lambda do
93
+ expect do
46
94
  q.message_count
47
- end.should raise_error(Bunny::ChannelAlreadyClosed)
95
+ end.to raise_error(Bunny::ChannelAlreadyClosed)
48
96
  end
49
97
  end
50
98
 
51
99
  context "with an invalid (random) delivery tag" do
52
100
  it "causes a channel-level error" do
53
101
  ch = connection.create_channel
54
- q = ch.queue("bunny.basic.ack.unknown-delivery-tag", :exclusive => true)
102
+ q = ch.queue("bunny.basic.ack.unknown-delivery-tag", exclusive: true)
55
103
  x = ch.default_exchange
56
104
 
57
- x.publish("bunneth", :routing_key => q.name)
105
+ x.publish("bunneth", routing_key: q.name)
58
106
  sleep 0.5
59
- q.message_count.should == 1
60
- _, _, content = q.pop(:manual_ack => true)
107
+ expect(q.message_count).to eq 1
108
+ _, _, content = q.pop(manual_ack: true)
61
109
 
62
110
  ch.on_error do |ch, channel_close|
63
111
  @channel_close = channel_close
@@ -65,33 +113,117 @@ describe Bunny::Channel, "#ack" do
65
113
  ch.ack(82, true)
66
114
  sleep 0.25
67
115
 
68
- @channel_close.reply_code.should == AMQ::Protocol::PreconditionFailed::VALUE
116
+ expect(@channel_close.reply_code).to eq AMQ::Protocol::PreconditionFailed::VALUE
69
117
  end
70
118
  end
71
119
 
72
120
  context "with a valid (known) delivery tag" do
73
121
  it "gets a depricated message warning for using :ack" do
74
122
  ch = connection.create_channel
75
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
123
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
76
124
  x = ch.default_exchange
77
125
 
78
- x.publish("bunneth", :routing_key => q.name)
126
+ x.publish("bunneth", routing_key: q.name)
79
127
  sleep 0.5
80
- q.message_count.should == 1
128
+ expect(q.message_count).to eq 1
81
129
 
82
130
  orig_stderr = $stderr
83
131
  $stderr = StringIO.new
84
132
 
85
- delivery_details, properties, content = q.pop(:ack => true)
133
+ delivery_details, properties, content = q.pop(ack: true)
86
134
 
87
135
  $stderr.rewind
88
- $stderr.string.chomp.should eq("[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.\n[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.")
136
+ expect($stderr.string.chomp).to eq("[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.\n[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.")
89
137
 
90
138
  $stderr = orig_stderr
91
139
 
92
140
  ch.ack(delivery_details.delivery_tag, true)
93
- q.message_count.should == 0
141
+ ch.close
142
+
143
+ ch = connection.create_channel
144
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
145
+ expect(q.message_count).to eq 0
146
+ ch.close
147
+ end
148
+ end
149
+ end
150
+
151
+ describe Bunny::Channel, "#basic_ack" do
152
+ let(:connection) do
153
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
154
+ c.start
155
+ c
156
+ end
94
157
 
158
+ after :each do
159
+ connection.close if connection.open?
160
+ end
161
+
162
+ context "with a valid (known) delivery tag (multiple = true)" do
163
+ it "acknowledges a message" do
164
+ ch = connection.create_channel
165
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
166
+ x = ch.default_exchange
167
+
168
+ x.publish("bunneth", routing_key: q.name)
169
+ x.publish("bunneth", routing_key: q.name)
170
+ sleep 0.5
171
+ expect(q.message_count).to eq 2
172
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
173
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
174
+
175
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i, true)
176
+ ch.close
177
+
178
+ ch = connection.create_channel
179
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
180
+ expect(q.message_count).to eq 0
181
+ ch.close
182
+ end
183
+ end
184
+
185
+ context "with a valid (known) delivery tag (multiple = false)" do
186
+ it "acknowledges a message" do
187
+ ch = connection.create_channel
188
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
189
+ x = ch.default_exchange
190
+
191
+ x.publish("bunneth", routing_key: q.name)
192
+ x.publish("bunneth", routing_key: q.name)
193
+ sleep 0.5
194
+ expect(q.message_count).to eq 2
195
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
196
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
197
+
198
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i, false)
199
+ ch.close
200
+
201
+ ch = connection.create_channel
202
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
203
+ expect(q.message_count).to eq 1
204
+ ch.close
205
+ end
206
+ end
207
+
208
+ context "with a valid (known) delivery tag (multiple = default)" do
209
+ it "acknowledges a message" do
210
+ ch = connection.create_channel
211
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
212
+ x = ch.default_exchange
213
+
214
+ x.publish("bunneth", routing_key: q.name)
215
+ x.publish("bunneth", routing_key: q.name)
216
+ sleep 0.5
217
+ expect(q.message_count).to eq 2
218
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
219
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
220
+
221
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i)
222
+ ch.close
223
+
224
+ ch = connection.create_channel
225
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
226
+ expect(q.message_count).to eq 1
95
227
  ch.close
96
228
  end
97
229
  end