right_agent 0.6.6 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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