right_agent 0.6.6 → 0.9.3

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.
Files changed (46) hide show
  1. data/lib/right_agent/agent.rb +26 -25
  2. data/lib/right_agent/agent_config.rb +28 -2
  3. data/lib/right_agent/command/command_constants.rb +2 -2
  4. data/lib/right_agent/core_payload_types/executable_bundle.rb +3 -21
  5. data/lib/right_agent/core_payload_types/login_user.rb +19 -4
  6. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +7 -1
  7. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +7 -1
  8. data/lib/right_agent/dispatcher.rb +6 -19
  9. data/lib/right_agent/idempotent_request.rb +72 -17
  10. data/lib/right_agent/monkey_patches/ruby_patch.rb +0 -1
  11. data/lib/right_agent/monkey_patches.rb +0 -1
  12. data/lib/right_agent/operation_result.rb +27 -4
  13. data/lib/right_agent/packets.rb +47 -23
  14. data/lib/right_agent/platform/darwin.rb +33 -2
  15. data/lib/right_agent/platform/linux.rb +98 -2
  16. data/lib/right_agent/platform/windows.rb +41 -6
  17. data/lib/right_agent/platform.rb +11 -2
  18. data/lib/right_agent/scripts/agent_controller.rb +2 -1
  19. data/lib/right_agent/scripts/agent_deployer.rb +2 -2
  20. data/lib/right_agent/scripts/stats_manager.rb +7 -3
  21. data/lib/right_agent/sender.rb +45 -28
  22. data/lib/right_agent.rb +2 -5
  23. data/right_agent.gemspec +5 -3
  24. data/spec/agent_config_spec.rb +1 -1
  25. data/spec/agent_spec.rb +26 -20
  26. data/spec/core_payload_types/login_user_spec.rb +7 -3
  27. data/spec/idempotent_request_spec.rb +218 -48
  28. data/spec/operation_result_spec.rb +19 -0
  29. data/spec/packets_spec.rb +42 -1
  30. data/spec/platform/darwin.rb +11 -0
  31. data/spec/platform/linux.rb +23 -0
  32. data/spec/platform/linux_volume_manager_spec.rb +43 -43
  33. data/spec/platform/platform_spec.rb +35 -32
  34. data/spec/platform/windows.rb +11 -0
  35. data/spec/sender_spec.rb +21 -25
  36. metadata +47 -40
  37. data/lib/right_agent/broker_client.rb +0 -686
  38. data/lib/right_agent/ha_broker_client.rb +0 -1327
  39. data/lib/right_agent/monkey_patches/amqp_patch.rb +0 -274
  40. data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +0 -107
  41. data/lib/right_agent/stats_helper.rb +0 -745
  42. data/spec/broker_client_spec.rb +0 -962
  43. data/spec/ha_broker_client_spec.rb +0 -1695
  44. data/spec/monkey_patches/amqp_patch_spec.rb +0 -100
  45. data/spec/monkey_patches/string_patch_spec.rb +0 -99
  46. data/spec/stats_helper_spec.rb +0 -686
@@ -1,686 +0,0 @@
1
- #
2
- # Copyright (c) 2009-2011 RightScale Inc
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
-
23
- module RightScale
24
-
25
- # Client for accessing AMQP broker
26
- class BrokerClient
27
-
28
- include StatsHelper
29
-
30
- # Set of possible broker connection status values
31
- STATUS = [
32
- :connecting, # Initiated AMQP connection but not yet confirmed that connected
33
- :connected, # Confirmed AMQP connection
34
- :stopping, # Broker is stopping service and, although still connected, is no longer usable
35
- :disconnected, # Notified by AMQP that connection has been lost and attempting to reconnect
36
- :closed, # AMQP connection closed explicitly or because of too many failed connect attempts
37
- :failed # Failed to connect due to internal failure or AMQP failure to connect
38
- ]
39
-
40
- # (AMQP::Channel) Channel of AMQP connection used by this client
41
- attr_reader :channel
42
-
43
- # (String) Broker identity
44
- attr_reader :identity
45
-
46
- # (String) Broker alias, used in logs
47
- attr_reader :alias
48
-
49
- # (String) Host name
50
- attr_reader :host
51
-
52
- # (Integer) Port number
53
- attr_reader :port
54
-
55
- # (Integer) Unique index for broker within given island, used in alias
56
- attr_reader :index
57
-
58
- # (Symbol) AMQP connection STATUS value
59
- attr_reader :status
60
-
61
- # (Array) List of MQ::Queue queues currently subscribed
62
- attr_reader :queues
63
-
64
- # (Boolean) Whether last connect attempt failed
65
- attr_reader :last_failed
66
-
67
- # (ActivityStats) AMQP lost connection statistics
68
- attr_reader :disconnects
69
-
70
- # (ActivityStats) AMQP connection failure statistics
71
- attr_reader :failures
72
-
73
- # (Integer) Number of attempts to connect after failure
74
- attr_reader :retries
75
-
76
- # (Integer) Identifier for island containing this broker
77
- attr_reader :island_id
78
-
79
- # (Integer) Island alias, used in logs
80
- attr_reader :island_alias
81
-
82
- # (Boolean) Whether this broker is in the same island as the creator of this client
83
- attr_reader :in_home_island
84
-
85
- # Create broker client
86
- #
87
- # === Parameters
88
- # identity(String):: Broker identity
89
- # address(Hash):: Broker address
90
- # :host(String:: IP host name or address
91
- # :port(Integer):: TCP port number for individual broker
92
- # :index(String):: Unique index for broker within given island for use in forming alias
93
- # serializer(Serializer):: Serializer used for marshaling packets being published; if nil,
94
- # has same effect as setting options :no_serialize and :no_unserialize
95
- # exceptions(ExceptionStats):: Exception statistics container
96
- # options(Hash):: AMQP connection configuration options
97
- # :user(String):: User name
98
- # :pass(String):: Password
99
- # :vhost(String):: Virtual host path name
100
- # :insist(Boolean):: Whether to suppress redirection of connection
101
- # :reconnect_interval(Integer):: Number of seconds between reconnect attempts
102
- # :heartbeat(Integer):: Number of seconds between AMQP connection heartbeats used to keep
103
- # connection alive, e.g., when AMQP broker is behind a firewall
104
- # :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for the agent
105
- # before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
106
- # if the agent crashes. Value 0 means unlimited prefetch.
107
- # :home_island(Integer):: Identifier for home island of creator of this client
108
- # :exception_on_receive_callback(Proc):: Callback activated on a receive exception with parameters
109
- # message(Object):: Message received
110
- # exception(Exception):: Exception raised
111
- # :update_status_callback(Proc):: Callback activated on a connection status change with parameters
112
- # broker(BrokerClient):: Broker client
113
- # connected_before(Boolean):: Whether was connected prior to this status change
114
- # island(IslandData|nil):: Island containing this broker, or nil if unknown
115
- # existing(BrokerClient|nil):: Existing broker client for this address, or nil if none
116
- def initialize(identity, address, serializer, exceptions, options, island = nil, existing = nil)
117
- @options = options
118
- @identity = identity
119
- @island_id = island && island.id
120
- @island_alias = island ? "i#{island.id}" : ""
121
- @in_home_island = @island_id == @options[:home_island]
122
- @host = address[:host]
123
- @port = address[:port].to_i
124
- @index = address[:index].to_i
125
- @alias = (@in_home_island ? "" : @island_alias) + "b#{@index}"
126
- @serializer = serializer
127
- @exceptions = exceptions
128
- @queues = []
129
- @last_failed = false
130
- @disconnects = ActivityStats.new(measure_rate = false)
131
- @failures = ActivityStats.new(measure_rate = false)
132
- @retries = 0
133
-
134
- connect(address, @options[:reconnect_interval])
135
-
136
- if existing
137
- @disconnects = existing.disconnects
138
- @failures = existing.failures
139
- @last_failed = existing.last_failed
140
- @retries = existing.retries
141
- update_failure if @status == :failed
142
- end
143
- end
144
-
145
- # Determine whether the broker connection is usable, i.e., connecting or confirmed connected
146
- #
147
- # === Return
148
- # (Boolean):: true if usable, otherwise false
149
- def usable?
150
- [:connected, :connecting].include?(@status)
151
- end
152
-
153
- # Determine whether this client is currently connected to the broker
154
- #
155
- # === Return
156
- # (Boolean):: true if connected, otherwise false
157
- def connected?
158
- @status == :connected
159
- end
160
-
161
- # Determine whether the broker connection has failed
162
- #
163
- # === Return
164
- # (Boolean):: true if failed, otherwise false
165
- def failed?(backoff = false)
166
- @status == :failed
167
- end
168
-
169
- # Subscribe an AMQP queue to an AMQP exchange
170
- # Do not wait for confirmation from broker that subscription is complete
171
- # When a message is received, acknowledge, unserialize, and log it as specified
172
- # If the message is unserialized and it is not of the right type, it is dropped after logging a warning
173
- #
174
- # === Parameters
175
- # queue(Hash):: AMQP queue being subscribed with keys :name and :options,
176
- # which are the standard AMQP ones plus
177
- # :no_declare(Boolean):: Whether to skip declaring this queue on the broker
178
- # to cause its creation; for use when client does not have permission to create or
179
- # knows the queue already exists and wants to avoid declare overhead
180
- # exchange(Hash|nil):: AMQP exchange to subscribe to with keys :type, :name, and :options,
181
- # nil means use empty exchange by directly subscribing to queue; the :options are the
182
- # standard AMQP ones plus
183
- # :no_declare(Boolean):: Whether to skip declaring this exchange on the broker
184
- # to cause its creation; for use when client does not have create permission or
185
- # knows the exchange already exists and wants to avoid declare overhead
186
- # options(Hash):: Subscribe options:
187
- # :ack(Boolean):: Explicitly acknowledge received messages to AMQP
188
- # :no_unserialize(Boolean):: Do not unserialize message, this is an escape for special
189
- # situations like enrollment, also implicitly disables receive filtering and logging;
190
- # this option is implicitly invoked if initialize without a serializer
191
- # (packet class)(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info,
192
- # only packet classes specified are accepted, others are not processed but are logged with error
193
- # :category(String):: Packet category description to be used in error messages
194
- # :log_data(String):: Additional data to display at end of log entry
195
- # :no_log(Boolean):: Disable receive logging unless debug level
196
- # :exchange2(Hash):: Additional exchange to which same queue is to be bound
197
- # :brokers(Array):: Identity of brokers for which to subscribe, defaults to all usable if nil or empty
198
- #
199
- # === Block
200
- # Block with following parameters to be called each time exchange matches a message to the queue:
201
- # identity(String):: Serialized identity of broker delivering the message
202
- # message(Packet|String):: Message received, which is unserialized unless :no_unserialize was specified
203
- #
204
- # === Return
205
- # (Boolean):: true if subscribe successfully or if already subscribed, otherwise false
206
- def subscribe(queue, exchange = nil, options = {}, &blk)
207
- return false unless usable?
208
- return true unless @queues.select { |q| q.name == queue[:name] }.empty?
209
-
210
- to_exchange = if exchange
211
- if options[:exchange2]
212
- " to exchanges #{exchange[:name]} and #{options[:exchange2][:name]}"
213
- else
214
- " to exchange #{exchange[:name]}"
215
- end
216
- end
217
- queue_options = queue[:options] || {}
218
- exchange_options = (exchange && exchange[:options]) || {}
219
-
220
- begin
221
- Log.info("[setup] Subscribing queue #{queue[:name]}#{to_exchange} on broker #{@alias}")
222
- q = @channel.queue(queue[:name], queue_options)
223
- @queues << q
224
- if exchange
225
- x = @channel.__send__(exchange[:type], exchange[:name], exchange_options)
226
- binding = q.bind(x, options[:key] ? {:key => options[:key]} : {})
227
- if exchange2 = options[:exchange2]
228
- q.bind(@channel.__send__(exchange2[:type], exchange2[:name], exchange2[:options] || {}))
229
- end
230
- q = binding
231
- end
232
- if options[:ack]
233
- q.subscribe(:ack => true) do |info, message|
234
- begin
235
- # Ack now before processing to avoid risk of duplication after a crash
236
- info.ack
237
- if options[:no_unserialize] || @serializer.nil?
238
- execute_callback(blk, @identity, message)
239
- elsif message == "nil"
240
- # This happens as part of connecting an instance agent to a broker prior to version 13
241
- Log.debug("RECV #{@alias} nil message ignored")
242
- elsif
243
- packet = receive(queue[:name], message, options)
244
- execute_callback(blk, @identity, packet) if packet
245
- end
246
- true
247
- rescue Exception => e
248
- Log.error("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
249
- "on broker #{@alias}", e, :trace)
250
- @exceptions.track("receive", e)
251
- false
252
- end
253
- end
254
- else
255
- q.subscribe do |message|
256
- begin
257
- if options[:no_unserialize] || @serializer.nil?
258
- execute_callback(blk, @identity, message)
259
- elsif message == "nil"
260
- # This happens as part of connecting an instance agent to a broker
261
- Log.debug("RECV #{@alias} nil message ignored")
262
- elsif
263
- packet = receive(queue[:name], message, options)
264
- execute_callback(blk, @identity, packet) if packet
265
- end
266
- true
267
- rescue Exception => e
268
- Log.error("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
269
- "on broker #{@alias}", e, :trace)
270
- @exceptions.track("receive", e)
271
- false
272
- end
273
- end
274
- end
275
- rescue Exception => e
276
- Log.error("Failed subscribing queue #{queue.inspect}#{to_exchange} on broker #{@alias}", e, :trace)
277
- @exceptions.track("subscribe", e)
278
- false
279
- end
280
- end
281
-
282
- # Unsubscribe from the specified queues
283
- # Silently ignore unknown queues
284
- #
285
- # === Parameters
286
- # queue_names(Array):: Names of queues previously subscribed to
287
- #
288
- # === Block
289
- # Optional block to be called with no parameters when each unsubscribe completes
290
- #
291
- # === Return
292
- # true:: Always return true
293
- def unsubscribe(queue_names, &blk)
294
- if usable?
295
- @queues.each do |q|
296
- if queue_names.include?(q.name)
297
- begin
298
- Log.info("[stop] Unsubscribing queue #{q.name} on broker #{@alias}")
299
- q.unsubscribe { blk.call if blk }
300
- rescue Exception => e
301
- Log.error("Failed unsubscribing queue #{q.name} on broker #{@alias}", e, :trace)
302
- @exceptions.track("unsubscribe", e)
303
- blk.call if blk
304
- end
305
- end
306
- end
307
- end
308
- true
309
- end
310
-
311
- # Declare queue or exchange object but do not subscribe to it
312
- #
313
- # === Parameters
314
- # type(Symbol):: Type of object: :queue, :direct, :fanout or :topic
315
- # name(String):: Name of object
316
- # options(Hash):: Standard AMQP declare options
317
- #
318
- # === Return
319
- # (Boolean):: true if declare successfully, otherwise false
320
- def declare(type, name, options = {})
321
- return false unless usable?
322
- begin
323
- Log.info("[setup] Declaring #{name} #{type.to_s} on broker #{@alias}")
324
- delete_amqp_resources(:queue, name)
325
- @channel.__send__(type, name, options)
326
- true
327
- rescue Exception => e
328
- Log.error("Failed declaring #{type.to_s} #{name} on broker #{@alias}", e, :trace)
329
- @exceptions.track("declare", e)
330
- false
331
- end
332
- end
333
-
334
- # Publish message to AMQP exchange
335
- #
336
- # === Parameters
337
- # exchange(Hash):: AMQP exchange to subscribe to with keys :type, :name, and :options,
338
- # which are the standard AMQP ones plus
339
- # :no_declare(Boolean):: Whether to skip declaring this exchange or queue on the broker
340
- # to cause its creation; for use when client does not have create permission or
341
- # knows the object already exists and wants to avoid declare overhead
342
- # :declare(Boolean):: Whether to delete this exchange or queue from the AMQP cache
343
- # to force it to be declared on the broker and thus be created if it does not exist
344
- # packet(Packet):: Message to serialize and publish
345
- # message(String):: Serialized message to be published
346
- # options(Hash):: Publish options -- standard AMQP ones plus
347
- # :no_serialize(Boolean):: Do not serialize packet because it is already serialized
348
- # :log_filter(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info
349
- # :log_data(String):: Additional data to display at end of log entry
350
- # :no_log(Boolean):: Disable publish logging unless debug level
351
- #
352
- # === Return
353
- # (Boolean):: true if publish successfully, otherwise false
354
- def publish(exchange, packet, message, options = {})
355
- return false unless connected?
356
- begin
357
- exchange_options = exchange[:options] || {}
358
- unless (options[:no_log] && Log.level != :debug) || options[:no_serialize]
359
- re = "RE-" if packet.respond_to?(:tries) && !packet.tries.empty?
360
- log_filter = options[:log_filter] unless Log.level == :debug
361
- Log.info("#{re}SEND #{@alias} #{packet.to_s(log_filter, :send_version)} " +
362
- "#{options[:log_data]}")
363
- end
364
- Log.debug("... publish options #{options.inspect}, exchange #{exchange[:name]}, " +
365
- "type #{exchange[:type]}, options #{exchange[:options].inspect}")
366
- delete_amqp_resources(exchange[:type], exchange[:name]) if exchange_options[:declare]
367
- @channel.__send__(exchange[:type], exchange[:name], exchange_options).publish(message, options)
368
- true
369
- rescue Exception => e
370
- Log.error("Failed publishing to exchange #{exchange.inspect} on broker #{@alias}", e, :trace)
371
- @exceptions.track("publish", e)
372
- false
373
- end
374
- end
375
-
376
- # Provide callback to be activated when broker returns a message that could not be delivered
377
- # A message published with :mandatory => true is returned if the exchange does not have any associated queues
378
- # or if all the associated queues do not have any consumers
379
- # A message published with :immediate => true is returned for the same reasons as :mandatory plus if all
380
- # of the queues associated with the exchange are not immediately ready to consume the message
381
- #
382
- # === Block
383
- # Optional block with following parameters to be called when a message is returned
384
- # to(String):: Queue to which message was published
385
- # reason(String):: Reason for return
386
- # "NO_ROUTE" - queue does not exist
387
- # "NO_CONSUMERS" - queue exists but it has no consumers, or if :immediate was specified,
388
- # all consumers are not immediately ready to consume
389
- # "ACCESS_REFUSED" - queue not usable because broker is in the process of stopping service
390
- # message(String):: Returned serialized message
391
- #
392
- # === Return
393
- # true:: Always return true
394
- def return_message
395
- @channel.return_message do |info, message|
396
- begin
397
- to = if info.exchange && !info.exchange.empty? then info.exchange else info.routing_key end
398
- reason = info.reply_text
399
- Log.debug("RETURN #{@alias} because #{reason} for #{to}")
400
- yield(to, reason, message) if block_given?
401
- rescue Exception => e
402
- Log.error("Failed return #{info.inspect} of message from broker #{@alias}", e, :trace)
403
- @exceptions.track("return", e)
404
- end
405
- end
406
- true
407
- end
408
-
409
- # Delete queue
410
- #
411
- # === Parameters
412
- # name(String):: Queue name
413
- # options(Hash):: Queue declare options
414
- #
415
- # === Return
416
- # (Boolean):: true if queue was successfully deleted, otherwise false
417
- def delete(name, options = {})
418
- deleted = false
419
- if usable?
420
- begin
421
- @queues.reject! do |q|
422
- if q.name == name
423
- @channel.queue(name, options.merge(:no_declare => true)).delete
424
- deleted = true
425
- end
426
- end
427
- unless deleted
428
- # Allowing declare to happen since queue may not exist and do not want NOT_FOUND
429
- # failure to cause AMQP channel to close
430
- @channel.queue(name, options).delete
431
- deleted = true
432
- end
433
- rescue Exception => e
434
- Log.error("Failed deleting queue #{name.inspect} on broker #{@alias}", e, :trace)
435
- @exceptions.track("delete", e)
436
- end
437
- end
438
- deleted
439
- end
440
-
441
- # Delete resources from local AMQP cache
442
- #
443
- # === Parameters
444
- # type(Symbol):: Type of AMQP object
445
- # name(String):: Name of object
446
- #
447
- # === Return
448
- # true:: Always return true
449
- def delete_amqp_resources(type, name)
450
- @channel.__send__(type == :queue ? :queues : :exchanges).delete(name)
451
- true
452
- end
453
-
454
- # Close broker connection
455
- #
456
- # === Parameters
457
- # propagate(Boolean):: Whether to propagate connection status updates, defaults to true
458
- # normal(Boolean):: Whether this is a normal close vs. a failed connection, defaults to true
459
- # log(Boolean):: Whether to log that closing, defaults to true
460
- #
461
- # === Block
462
- # Optional block with no parameters to be called after connection closed
463
- #
464
- # === Return
465
- # true:: Always return true
466
- def close(propagate = true, normal = true, log = true, &blk)
467
- final_status = normal ? :closed : :failed
468
- if ![:closed, :failed].include?(@status)
469
- begin
470
- Log.info("[stop] Closed connection to broker #{@alias}") if log
471
- update_status(final_status) if propagate
472
- @connection.close do
473
- @status = final_status
474
- yield if block_given?
475
- end
476
- rescue Exception => e
477
- Log.error("Failed to close broker #{@alias}", e, :trace)
478
- @exceptions.track("close", e)
479
- @status = final_status
480
- yield if block_given?
481
- end
482
- else
483
- @status = final_status
484
- yield if block_given?
485
- end
486
- true
487
- end
488
-
489
- # Get broker client information summarizing its status
490
- #
491
- # === Return
492
- # (Hash):: Status of broker with keys
493
- # :identity(String):: Serialized identity
494
- # :alias(String):: Alias used in logs
495
- # :status(Symbol):: Status of connection
496
- # :disconnects(Integer):: Number of times lost connection
497
- # :failures(Integer):: Number of times connect failed
498
- # :retries(Integer):: Number of attempts to connect after failure
499
- def summary
500
- {
501
- :identity => @identity,
502
- :alias => @alias,
503
- :status => @status,
504
- :retries => @retries,
505
- :disconnects => @disconnects.total,
506
- :failures => @failures.total,
507
- }
508
- end
509
-
510
- # Get broker client statistics
511
- #
512
- # === Return
513
- # (Hash):: Broker client stats with keys
514
- # "alias"(String):: Broker alias
515
- # "identity"(String):: Broker identity
516
- # "status"(Status):: Status of connection
517
- # "disconnect last"(Hash|nil):: Last disconnect information with key "elapsed", or nil if none
518
- # "disconnects"(Integer|nil):: Number of times lost connection, or nil if none
519
- # "failure last"(Hash|nil):: Last connect failure information with key "elapsed", or nil if none
520
- # "failures"(Integer|nil):: Number of failed attempts to connect to broker, or nil if none
521
- def stats
522
- {
523
- "alias" => @alias,
524
- "identity" => @identity,
525
- "status" => @status.to_s,
526
- "disconnect last" => @disconnects.last,
527
- "disconnects" => nil_if_zero(@disconnects.total),
528
- "failure last" => @failures.last,
529
- "failures" => nil_if_zero(@failures.total),
530
- "retries" => nil_if_zero(@retries)
531
- }
532
- end
533
-
534
- # Callback from AMQP with connection status or from HABrokerClient
535
- # Makes client callback with :connected or :disconnected status if boundary crossed
536
- #
537
- # === Parameters
538
- # status(Symbol):: Status of connection (:connected, :disconnected, :stopping, :failed, :closed)
539
- #
540
- # === Return
541
- # true:: Always return true
542
- def update_status(status)
543
- # Do not let closed connection regress to failed
544
- return true if status == :failed && @status == :closed
545
-
546
- # Wait until connection is ready (i.e. handshake with broker is completed) before
547
- # changing our status to connected
548
- return true if status == :connected
549
- status = :connected if status == :ready
550
-
551
- before = @status
552
- @status = status
553
-
554
- if status == :connected
555
- update_success
556
- elsif status == :failed
557
- update_failure
558
- elsif status == :disconnected && before != :disconnected
559
- @disconnects.update
560
- end
561
-
562
- unless status == before || @options[:update_status_callback].nil?
563
- @options[:update_status_callback].call(self, before == :connected)
564
- end
565
- true
566
- end
567
-
568
- protected
569
-
570
- # Connect to broker and register for status updates
571
- # Also set prefetch value if specified
572
- #
573
- # === Parameters
574
- # address(Hash):: Broker address
575
- # :host(String:: IP host name or address
576
- # :port(Integer):: TCP port number for individual broker
577
- # :index(String):: Unique index for broker within given island for use in forming alias
578
- # reconnect_interval(Integer):: Number of seconds between reconnect attempts
579
- #
580
- # === Return
581
- # true:: Always return true
582
- def connect(address, reconnect_interval)
583
- begin
584
- Log.info("[setup] Connecting to broker #{@identity}, alias #{@alias}")
585
- @status = :connecting
586
- @connection = AMQP.connect(:user => @options[:user],
587
- :pass => @options[:pass],
588
- :vhost => @options[:vhost],
589
- :host => address[:host],
590
- :port => address[:port],
591
- :insist => @options[:insist] || false,
592
- :heartbeat => @options[:heartbeat],
593
- :reconnect_delay => lambda { rand(reconnect_interval) },
594
- :reconnect_interval => reconnect_interval)
595
- @channel = MQ.new(@connection)
596
- @channel.__send__(:connection).connection_status { |status| update_status(status) }
597
- @channel.prefetch(@options[:prefetch]) if @options[:prefetch]
598
- rescue Exception => e
599
- @status = :failed
600
- @failures.update
601
- Log.error("Failed connecting to broker #{@alias}", e, :trace)
602
- @exceptions.track("connect", e)
603
- @connection.close if @connection
604
- end
605
- end
606
-
607
- # Receive message by unserializing it, checking that it is an acceptable type, and logging accordingly
608
- #
609
- # === Parameters
610
- # queue(String):: Name of queue
611
- # message(String):: Serialized packet
612
- # options(Hash):: Subscribe options:
613
- # (packet class)(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info,
614
- # only packet classes specified are accepted, others are not processed but are logged with error
615
- # :category(String):: Packet category description to be used in error messages
616
- # :log_data(String):: Additional data to display at end of log entry
617
- # :no_log(Boolean):: Disable receive logging unless debug level
618
- #
619
- # === Return
620
- # (Packet|nil):: Unserialized packet or nil if not of right type or if there is an exception
621
- def receive(queue, message, options = {})
622
- begin
623
- packet = @serializer.load(message)
624
- if options.key?(packet.class)
625
- unless options[:no_log] && Log.level != :debug
626
- re = "RE-" if packet.respond_to?(:tries) && !packet.tries.empty?
627
- log_filter = options[packet.class] unless Log.level == :debug
628
- Log.info("#{re}RECV #{@alias} #{packet.to_s(log_filter, :recv_version)} " +
629
- "#{options[:log_data]}")
630
- end
631
- packet
632
- else
633
- category = options[:category] + " " if options[:category]
634
- Log.warning("Received invalid #{category}packet type from queue #{queue} on broker #{@alias}: #{packet.class}\n" + caller.join("\n"))
635
- nil
636
- end
637
- rescue Exception => e
638
- trace = e.is_a?(Serializer::SerializationError) ? :caller : :trace
639
- Log.error("Failed receiving from queue #{queue} on #{@alias}", e, trace)
640
- @exceptions.track("receive", e)
641
- @options[:exception_on_receive_callback].call(message, e) if @options[:exception_on_receive_callback]
642
- nil
643
- end
644
- end
645
-
646
- # Make status updates for connect success
647
- #
648
- # === Return
649
- # true:: Always return true
650
- def update_success
651
- @last_failed = false
652
- @retries = 0
653
- true
654
- end
655
-
656
- # Make status updates for connect failure
657
- #
658
- # === Return
659
- # true:: Always return true
660
- def update_failure
661
- Log.error("Failed to connect to broker #{@alias}")
662
- if @last_failed
663
- @retries += 1
664
- else
665
- @last_failed = true
666
- @retries = 0
667
- @failures.update
668
- end
669
- true
670
- end
671
-
672
- # Execute packet receive callback, make it a separate method to ease instrumentation
673
- #
674
- # === Parameters
675
- # callback(Proc):: Proc to run
676
- # args(Array):: Array of pass-through arguments
677
- #
678
- # === Return
679
- # (Object):: Callback return value
680
- def execute_callback(callback, *args)
681
- callback.call(*args) if callback
682
- end
683
-
684
- end # BrokerClient
685
-
686
- end # RightScale