right_agent 0.13.5 → 0.14.0
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.
- data/lib/right_agent/actors/agent_manager.rb +1 -32
- data/lib/right_agent/agent.rb +243 -230
- data/lib/right_agent/dispatched_cache.rb +4 -5
- data/lib/right_agent/dispatcher.rb +146 -157
- data/lib/right_agent/pid_file.rb +1 -1
- data/lib/right_agent/platform.rb +14 -14
- data/lib/right_agent/scripts/agent_controller.rb +2 -4
- data/lib/right_agent/sender.rb +214 -223
- data/lib/right_agent/serialize/secure_serializer.rb +2 -2
- data/right_agent.gemspec +3 -3
- data/spec/agent_spec.rb +50 -171
- data/spec/dispatched_cache_spec.rb +13 -19
- data/spec/dispatcher_spec.rb +192 -254
- data/spec/sender_spec.rb +212 -168
- metadata +7 -4
data/lib/right_agent/agent.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2009-
|
2
|
+
# Copyright (c) 2009-2012 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -129,9 +129,6 @@ module RightScale
|
|
129
129
|
# :abnormal_terminate_callback(Proc):: Called at end of termination when terminate abnormally (no argument)
|
130
130
|
# :services(Symbol):: List of services provided by this agent. Defaults to all methods exposed by actors.
|
131
131
|
# :secure(Boolean):: true indicates to use security features of RabbitMQ to restrict agents to themselves
|
132
|
-
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
133
|
-
# to do requested work on EM defer thread and all else on main thread
|
134
|
-
# :threadpool_size(Integer):: Number of threads in EM thread pool
|
135
132
|
# :vhost(String):: AMQP broker virtual host
|
136
133
|
# :user(String):: AMQP broker user
|
137
134
|
# :pass(String):: AMQP broker password
|
@@ -164,8 +161,6 @@ module RightScale
|
|
164
161
|
@tags << opts[:tag] if opts[:tag]
|
165
162
|
@tags.flatten!
|
166
163
|
@options.freeze
|
167
|
-
@deferred_tasks = []
|
168
|
-
@history = History.new(@identity)
|
169
164
|
@last_stat_reset_time = Time.now
|
170
165
|
reset_agent_stats
|
171
166
|
true
|
@@ -200,38 +195,13 @@ module RightScale
|
|
200
195
|
# Initiate AMQP broker connection, wait for connection before proceeding
|
201
196
|
# otherwise messages published on failed connection will be lost
|
202
197
|
@broker = RightAMQP::HABrokerClient.new(Serializer.new(:secure), @options)
|
203
|
-
@
|
198
|
+
@queues.each { |s| @remaining_queue_setup[s] = @broker.all }
|
204
199
|
@broker.connection_status(:one_off => @options[:connect_timeout]) do |status|
|
205
200
|
if status == :connected
|
206
201
|
# Need to give EM (on Windows) a chance to respond to the AMQP handshake
|
207
202
|
# before doing anything interesting to prevent AMQP handshake from
|
208
203
|
# timing-out; delay post-connected activity a second.
|
209
|
-
EM.add_timer(1)
|
210
|
-
begin
|
211
|
-
@registry = ActorRegistry.new
|
212
|
-
@dispatcher = create_dispatcher
|
213
|
-
@sender = create_sender
|
214
|
-
load_actors
|
215
|
-
setup_traps
|
216
|
-
setup_queues
|
217
|
-
@history.update("run")
|
218
|
-
start_console if @options[:console] && !@options[:daemonize]
|
219
|
-
|
220
|
-
# Need to keep reconnect interval at least :connect_timeout in size,
|
221
|
-
# otherwise connection_status callback will not timeout prior to next
|
222
|
-
# reconnect attempt, which can result in repeated attempts to setup
|
223
|
-
# queues when finally do connect
|
224
|
-
interval = [@options[:check_interval], @options[:connect_timeout]].max
|
225
|
-
@check_status_count = 0
|
226
|
-
@check_status_brokers = @broker.all
|
227
|
-
EM.next_tick { @options[:ready_callback].call } if @options[:ready_callback]
|
228
|
-
@check_status_timer = EM::PeriodicTimer.new(interval) { check_status }
|
229
|
-
rescue SystemExit
|
230
|
-
raise
|
231
|
-
rescue Exception => e
|
232
|
-
terminate("failed startup after connecting to a broker", e, &terminate_callback)
|
233
|
-
end
|
234
|
-
end
|
204
|
+
EM.add_timer(1) { start_service(&terminate_callback) }
|
235
205
|
elsif status == :failed
|
236
206
|
terminate("failed to connect to any brokers during startup", &terminate_callback)
|
237
207
|
elsif status == :timeout
|
@@ -265,58 +235,6 @@ module RightScale
|
|
265
235
|
@registry.register(actor, prefix)
|
266
236
|
end
|
267
237
|
|
268
|
-
# Tune connection heartbeat frequency for all brokers
|
269
|
-
# Causes a reconnect to each broker
|
270
|
-
#
|
271
|
-
# === Parameters
|
272
|
-
# heartbeat(Integer):: Number of seconds between AMQP connection heartbeats used to keep
|
273
|
-
# connection alive (e.g., when AMQP broker is behind a firewall), nil or 0 means disable
|
274
|
-
#
|
275
|
-
# === Return
|
276
|
-
# res(String|nil):: Error message if failed, otherwise nil
|
277
|
-
def tune_heartbeat(heartbeat)
|
278
|
-
res = nil
|
279
|
-
begin
|
280
|
-
Log.info("[setup] Reconnecting each broker to tune heartbeat to #{heartbeat}")
|
281
|
-
@broker.heartbeat = heartbeat
|
282
|
-
update_configuration(:heartbeat => heartbeat)
|
283
|
-
ids = []
|
284
|
-
all = @broker.all
|
285
|
-
all.each do |id|
|
286
|
-
begin
|
287
|
-
host, port, index, priority = @broker.identity_parts(id)
|
288
|
-
@broker.connect(host, port, index, priority, force = true) do |id|
|
289
|
-
@broker.connection_status(:one_off => @options[:connect_timeout], :brokers => [id]) do |status|
|
290
|
-
begin
|
291
|
-
if status == :connected
|
292
|
-
setup_queues([id])
|
293
|
-
tuned = (heartbeat && heartbeat != 0) ? "Tuned heartbeat to #{heartbeat} seconds" : "Disabled heartbeat"
|
294
|
-
Log.info("[setup] #{tuned} for broker #{id}")
|
295
|
-
else
|
296
|
-
Log.error("Failed to reconnect to broker #{id} to tune heartbeat, status #{status.inspect}")
|
297
|
-
end
|
298
|
-
rescue Exception => e
|
299
|
-
Log.error("Failed to setup queues for broker #{id} when tuning heartbeat", e, :trace)
|
300
|
-
@exceptions.track("tune heartbeat", e)
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
ids << id
|
305
|
-
rescue Exception => e
|
306
|
-
res = Log.format("Failed to reconnect to broker #{id} to tune heartbeat", e)
|
307
|
-
Log.error("Failed to reconnect to broker #{id} to tune heartbeat", e, :trace)
|
308
|
-
@exceptions.track("tune heartbeat", e)
|
309
|
-
end
|
310
|
-
end
|
311
|
-
res = "Failed to tune heartbeat for brokers #{(all - ids).inspect}" unless (all - ids).empty?
|
312
|
-
rescue Exception => e
|
313
|
-
res = Log.format("Failed tuning broker connection heartbeat", e)
|
314
|
-
Log.error("Failed tuning broker connection heartbeat", e, :trace)
|
315
|
-
@exceptions.track("tune heartbeat", e)
|
316
|
-
end
|
317
|
-
res
|
318
|
-
end
|
319
|
-
|
320
238
|
# Connect to an additional broker or reconnect it if connection has failed
|
321
239
|
# Subscribe to identity queue on this broker
|
322
240
|
# Update config file if this is a new broker
|
@@ -333,7 +251,7 @@ module RightScale
|
|
333
251
|
# === Return
|
334
252
|
# res(String|nil):: Error message if failed, otherwise nil
|
335
253
|
def connect(host, port, index, priority = nil, force = false)
|
336
|
-
@
|
254
|
+
@connect_request_stats.update("connect b#{index}")
|
337
255
|
even_if = " even if already connected" if force
|
338
256
|
Log.info("Connecting to broker at host #{host.inspect} port #{port.inspect} " +
|
339
257
|
"index #{index.inspect} priority #{priority.inspect}#{even_if}")
|
@@ -346,7 +264,7 @@ module RightScale
|
|
346
264
|
if status == :connected
|
347
265
|
setup_queues([id])
|
348
266
|
remaining = 0
|
349
|
-
@
|
267
|
+
@remaining_queue_setup.each_value { |ids| remaining += ids.size }
|
350
268
|
Log.info("[setup] Finished subscribing to queues after reconnecting to broker #{id}") if remaining == 0
|
351
269
|
unless update_configuration(:host => @broker.hosts, :port => @broker.ports)
|
352
270
|
Log.warning("Successfully connected to broker #{id} but failed to update config file")
|
@@ -356,13 +274,13 @@ module RightScale
|
|
356
274
|
end
|
357
275
|
rescue Exception => e
|
358
276
|
Log.error("Failed to connect to broker #{id}, status #{status.inspect}", e)
|
359
|
-
@
|
277
|
+
@exception_stats.track("connect", e)
|
360
278
|
end
|
361
279
|
end
|
362
280
|
end
|
363
281
|
rescue Exception => e
|
364
282
|
res = Log.format("Failed to connect to broker at host #{host.inspect} and port #{port.inspect}", e)
|
365
|
-
@
|
283
|
+
@exception_stats.track("connect", e)
|
366
284
|
end
|
367
285
|
Log.error(res) if res
|
368
286
|
res
|
@@ -384,7 +302,7 @@ module RightScale
|
|
384
302
|
Log.info("Disconnecting#{and_remove} broker at host #{host.inspect} port #{port.inspect}")
|
385
303
|
Log.info("Current broker configuration: #{@broker.status.inspect}")
|
386
304
|
id = RightAMQP::HABrokerClient.identity(host, port)
|
387
|
-
@
|
305
|
+
@connect_request_stats.update("disconnect #{@broker.alias_(id)}")
|
388
306
|
connected = @broker.connected
|
389
307
|
res = nil
|
390
308
|
if connected.include?(id) && connected.size == 1
|
@@ -402,7 +320,7 @@ module RightScale
|
|
402
320
|
end
|
403
321
|
rescue Exception => e
|
404
322
|
res = Log.format("Failed to disconnect from broker #{id}", e)
|
405
|
-
@
|
323
|
+
@exception_stats.track("disconnect", e)
|
406
324
|
end
|
407
325
|
else
|
408
326
|
res = "Cannot disconnect from broker #{id} because not configured for this agent"
|
@@ -422,7 +340,7 @@ module RightScale
|
|
422
340
|
# res(String|nil):: Error message if failed, otherwise nil
|
423
341
|
def connect_failed(ids)
|
424
342
|
aliases = @broker.aliases(ids).join(", ")
|
425
|
-
@
|
343
|
+
@connect_request_stats.update("enroll failed #{aliases}")
|
426
344
|
res = nil
|
427
345
|
begin
|
428
346
|
Log.info("Received indication that service initialization for this agent for brokers #{ids.inspect} has failed")
|
@@ -434,49 +352,11 @@ module RightScale
|
|
434
352
|
rescue Exception => e
|
435
353
|
res = Log.format("Failed handling broker connection failure indication for #{ids.inspect}", e)
|
436
354
|
Log.error(res)
|
437
|
-
@
|
355
|
+
@exception_stats.track("connect failed", e)
|
438
356
|
end
|
439
357
|
res
|
440
358
|
end
|
441
359
|
|
442
|
-
# Handle packet received
|
443
|
-
# Delegate packet acknowledgement to dispatcher/sender
|
444
|
-
# Ignore requests if in the process of terminating but continue to accept responses
|
445
|
-
#
|
446
|
-
# === Parameters
|
447
|
-
# packet(Request|Push|Result):: Packet received
|
448
|
-
# header(AMQP::Frame::Header|nil):: Request header containing ack control
|
449
|
-
#
|
450
|
-
# === Return
|
451
|
-
# true:: Always return true
|
452
|
-
def receive(packet, header = nil)
|
453
|
-
begin
|
454
|
-
case packet
|
455
|
-
when Push, Request then @dispatcher.dispatch(packet, header) unless @terminating
|
456
|
-
when Result then @sender.handle_response(packet, header)
|
457
|
-
else header.ack if header
|
458
|
-
end
|
459
|
-
@sender.message_received
|
460
|
-
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
461
|
-
Log.error("Identity queue processing error", e)
|
462
|
-
rescue Exception => e
|
463
|
-
Log.error("Identity queue processing error", e, :trace)
|
464
|
-
@exceptions.track("identity queue", e, packet)
|
465
|
-
end
|
466
|
-
true
|
467
|
-
end
|
468
|
-
|
469
|
-
# Defer task until next status check
|
470
|
-
#
|
471
|
-
# === Block
|
472
|
-
# Required block to be activated on next status check
|
473
|
-
#
|
474
|
-
# === Return
|
475
|
-
# true:: Always return true
|
476
|
-
def defer_task(&task)
|
477
|
-
@deferred_tasks << task
|
478
|
-
end
|
479
|
-
|
480
360
|
# Gracefully terminate execution by allowing unfinished tasks to complete
|
481
361
|
# Immediately terminate if called a second time
|
482
362
|
# Report reason for termination if it is abnormal
|
@@ -506,45 +386,8 @@ module RightScale
|
|
506
386
|
@terminating = true
|
507
387
|
@check_status_timer.cancel if @check_status_timer
|
508
388
|
@check_status_timer = nil
|
509
|
-
timeout = @options[:grace_timeout]
|
510
389
|
Log.info("[stop] Agent #{@identity} terminating")
|
511
|
-
|
512
|
-
stop_gracefully(timeout) do
|
513
|
-
if @sender
|
514
|
-
dispatch_age = @dispatcher.dispatch_age
|
515
|
-
request_count, request_age = @sender.terminate
|
516
|
-
|
517
|
-
finish = lambda do
|
518
|
-
request_count, request_age = @sender.terminate
|
519
|
-
Log.info("[stop] The following #{request_count} requests initiated as recently as #{request_age} " +
|
520
|
-
"seconds ago are being dropped:\n " + @sender.dump_requests.join("\n ")) if request_age
|
521
|
-
@broker.close { block.call }
|
522
|
-
end
|
523
|
-
|
524
|
-
wait_time = [timeout - (request_age || timeout), timeout - (dispatch_age || timeout), 0].max
|
525
|
-
if wait_time == 0
|
526
|
-
finish.call
|
527
|
-
else
|
528
|
-
reason = ""
|
529
|
-
reason = "completion of #{request_count} requests initiated as recently as #{request_age} seconds ago" if request_age
|
530
|
-
reason += " and " if request_age && dispatch_age
|
531
|
-
reason += "requests received as recently as #{dispatch_age} seconds ago" if dispatch_age
|
532
|
-
Log.info("[stop] Termination waiting #{wait_time} seconds for #{reason}")
|
533
|
-
@termination_timer = EM::Timer.new(wait_time) do
|
534
|
-
begin
|
535
|
-
Log.info("[stop] Continuing with termination")
|
536
|
-
finish.call
|
537
|
-
rescue Exception => e
|
538
|
-
Log.error("Failed while finishing termination", e, :trace)
|
539
|
-
begin block.call; rescue Exception; end
|
540
|
-
end
|
541
|
-
end
|
542
|
-
end
|
543
|
-
else
|
544
|
-
block.call
|
545
|
-
end
|
546
|
-
@history.update("graceful exit")
|
547
|
-
end
|
390
|
+
stop_gracefully(@options[:grace_timeout], &block)
|
548
391
|
end
|
549
392
|
rescue SystemExit
|
550
393
|
raise
|
@@ -604,11 +447,17 @@ module RightScale
|
|
604
447
|
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
605
448
|
# "non-deliveries"(Hash):: Message non-delivery activity stats with keys "total", "percent", "last", and "rate"
|
606
449
|
# with percentage breakdown by request type, or nil if none
|
450
|
+
# "request failures"(Hash|nil):: Request dispatch failure activity stats with keys "total", "percent", "last", and "rate"
|
451
|
+
# with percentage breakdown per failure type, or nil if none
|
452
|
+
# "response failures"(Hash|nil):: Response delivery failure activity stats with keys "total", "percent", "last", and "rate"
|
453
|
+
# with percentage breakdown per failure type, or nil if none
|
607
454
|
def agent_stats(reset = false)
|
608
455
|
stats = {
|
609
|
-
"connect requests"
|
610
|
-
"exceptions"
|
611
|
-
"non-deliveries"
|
456
|
+
"connect requests" => @connect_request_stats.all,
|
457
|
+
"exceptions" => @exception_stats.stats,
|
458
|
+
"non-deliveries" => @non_delivery_stats.all,
|
459
|
+
"request failures" => @request_failure_stats.all,
|
460
|
+
"response failures" => @response_failure_stats.all
|
612
461
|
}
|
613
462
|
reset_agent_stats if reset
|
614
463
|
stats
|
@@ -619,9 +468,11 @@ module RightScale
|
|
619
468
|
# === Return
|
620
469
|
# true:: Always return true
|
621
470
|
def reset_agent_stats
|
622
|
-
@
|
623
|
-
@
|
624
|
-
@
|
471
|
+
@connect_request_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
472
|
+
@non_delivery_stats = RightSupport::Stats::Activity.new
|
473
|
+
@request_failure_stats = RightSupport::Stats::Activity.new
|
474
|
+
@response_failure_stats = RightSupport::Stats::Activity.new
|
475
|
+
@exception_stats = RightSupport::Stats::Exceptions.new(self, @options[:exception_callback])
|
625
476
|
true
|
626
477
|
end
|
627
478
|
|
@@ -631,7 +482,7 @@ module RightScale
|
|
631
482
|
# opts(Hash):: Configuration options
|
632
483
|
#
|
633
484
|
# === Return
|
634
|
-
#
|
485
|
+
# true:: Always return true
|
635
486
|
def set_configuration(opts)
|
636
487
|
@options = DEFAULT_OPTIONS.clone
|
637
488
|
@options.update(opts)
|
@@ -651,10 +502,9 @@ module RightScale
|
|
651
502
|
@agent_name = @options[:agent_name]
|
652
503
|
@stats_routing_key = "stats.#{@agent_type}.#{parsed_identity.base_id}"
|
653
504
|
@revision = revision
|
654
|
-
|
655
|
-
@
|
656
|
-
@
|
657
|
-
@identity
|
505
|
+
@queues = [@identity]
|
506
|
+
@remaining_queue_setup = {}
|
507
|
+
@history = History.new(@identity)
|
658
508
|
end
|
659
509
|
|
660
510
|
# Update agent's persisted configuration
|
@@ -679,6 +529,39 @@ module RightScale
|
|
679
529
|
false
|
680
530
|
end
|
681
531
|
|
532
|
+
# Start service now that connected to at least one broker
|
533
|
+
#
|
534
|
+
# === Block
|
535
|
+
# Optional block to be executed if terminate abnormally
|
536
|
+
#
|
537
|
+
# === Return
|
538
|
+
# true:: Always return true
|
539
|
+
def start_service(&terminate_callback)
|
540
|
+
begin
|
541
|
+
@registry = ActorRegistry.new
|
542
|
+
@dispatcher = create_dispatcher
|
543
|
+
@sender = create_sender
|
544
|
+
load_actors
|
545
|
+
setup_traps
|
546
|
+
setup_non_delivery
|
547
|
+
setup_queues
|
548
|
+
@history.update("run")
|
549
|
+
start_console if @options[:console] && !@options[:daemonize]
|
550
|
+
EM.next_tick { @options[:ready_callback].call } if @options[:ready_callback]
|
551
|
+
|
552
|
+
# Need to keep reconnect interval at least :connect_timeout in size,
|
553
|
+
# otherwise connection_status callback will not timeout prior to next
|
554
|
+
# reconnect attempt, which can result in repeated attempts to setup
|
555
|
+
# queues when finally do connect
|
556
|
+
setup_status_checks([@options[:check_interval], @options[:connect_timeout]].max)
|
557
|
+
rescue SystemExit
|
558
|
+
raise
|
559
|
+
rescue Exception => e
|
560
|
+
terminate("failed startup after connecting to a broker", e, &terminate_callback)
|
561
|
+
end
|
562
|
+
true
|
563
|
+
end
|
564
|
+
|
682
565
|
# Create dispatcher for handling incoming requests
|
683
566
|
#
|
684
567
|
# === Return
|
@@ -726,18 +609,36 @@ module RightScale
|
|
726
609
|
true
|
727
610
|
end
|
728
611
|
|
729
|
-
# Setup
|
730
|
-
# Also configure message non-delivery handling
|
612
|
+
# Setup signal traps
|
731
613
|
#
|
732
|
-
# ===
|
733
|
-
#
|
614
|
+
# === Return
|
615
|
+
# true:: Always return true
|
616
|
+
def setup_traps
|
617
|
+
['INT', 'TERM'].each do |sig|
|
618
|
+
old = trap(sig) do
|
619
|
+
EM.next_tick do
|
620
|
+
begin
|
621
|
+
terminate do
|
622
|
+
DEFAULT_TERMINATE_BLOCK.call
|
623
|
+
old.call if old.is_a? Proc
|
624
|
+
end
|
625
|
+
rescue Exception => e
|
626
|
+
Log.error("Failed in termination", e, :trace)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
true
|
632
|
+
end
|
633
|
+
|
634
|
+
# Setup non-delivery handler
|
734
635
|
#
|
735
636
|
# === Return
|
736
637
|
# true:: Always return true
|
737
|
-
def
|
638
|
+
def setup_non_delivery
|
738
639
|
@broker.non_delivery do |reason, type, token, from, to|
|
739
640
|
begin
|
740
|
-
@
|
641
|
+
@non_delivery_stats.update(type)
|
741
642
|
reason = case reason
|
742
643
|
when "NO_ROUTE" then OperationResult::NO_ROUTE_TO_TARGET
|
743
644
|
when "NO_CONSUMERS" then OperationResult::TARGET_NOT_CONNECTED
|
@@ -747,46 +648,108 @@ module RightScale
|
|
747
648
|
@sender.handle_response(result)
|
748
649
|
rescue Exception => e
|
749
650
|
Log.error("Failed handling non-delivery for <#{token}>", e, :trace)
|
750
|
-
@
|
651
|
+
@exception_stats.track("message return", e)
|
751
652
|
end
|
752
653
|
end
|
753
|
-
|
754
|
-
|
654
|
+
end
|
655
|
+
|
656
|
+
# Setup the queues on the specified brokers for this agent
|
657
|
+
# Do the setup regardless of whether remaining setup is empty since may be reconnecting
|
658
|
+
#
|
659
|
+
# === Parameters
|
660
|
+
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
661
|
+
#
|
662
|
+
# === Return
|
663
|
+
# true:: Always return true
|
664
|
+
def setup_queues(ids = nil)
|
665
|
+
@queues.each { |q| @remaining_queue_setup[q] -= setup_queue(q, ids) }
|
755
666
|
true
|
756
667
|
end
|
757
668
|
|
758
|
-
# Setup
|
669
|
+
# Setup queue for this agent
|
759
670
|
#
|
760
671
|
# === Parameters
|
672
|
+
# name(String):: Queue name
|
761
673
|
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
762
674
|
#
|
763
675
|
# === Return
|
764
|
-
#
|
765
|
-
def
|
766
|
-
queue = {:name =>
|
676
|
+
# (Array):: Identity of brokers to which subscribe submitted (although may still fail)
|
677
|
+
def setup_queue(name, ids = nil)
|
678
|
+
queue = {:name => name, :options => {:durable => true, :no_declare => @options[:secure]}}
|
767
679
|
filter = [:from, :tags, :tries, :persistent]
|
768
|
-
options = {:ack => true,
|
769
|
-
|
680
|
+
options = {:ack => true, Push => filter, Request => filter, Result => [:from], :brokers => ids}
|
681
|
+
@broker.subscribe(queue, nil, options) { |_, packet, header| handle_packet(name, packet, header) }
|
770
682
|
end
|
771
683
|
|
772
|
-
#
|
684
|
+
# Handle packet from queue
|
685
|
+
#
|
686
|
+
# === Parameters
|
687
|
+
# queue(String):: Name of queue from which message was received
|
688
|
+
# packet(Packet):: Packet received
|
689
|
+
# header(AMQP::Frame::Header):: Packet header containing ack control
|
773
690
|
#
|
774
691
|
# === Return
|
775
692
|
# true:: Always return true
|
776
|
-
def
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
old.call if old.is_a? Proc
|
784
|
-
end
|
785
|
-
rescue Exception => e
|
786
|
-
Log.error("Failed in termination", e, :trace)
|
787
|
-
end
|
788
|
-
end
|
693
|
+
def handle_packet(queue, packet, header)
|
694
|
+
begin
|
695
|
+
# Continue to dispatch/ack requests even when terminating otherwise will block results
|
696
|
+
# Ideally would reject requests when terminating but broker client does not yet support that
|
697
|
+
case packet
|
698
|
+
when Push, Request then dispatch_request(packet, queue)
|
699
|
+
when Result then deliver_response(packet)
|
789
700
|
end
|
701
|
+
@sender.message_received
|
702
|
+
rescue Exception => e
|
703
|
+
Log.error("#{queue} queue processing error", e, :trace)
|
704
|
+
@exception_stats.track("#{queue} queue", e, packet)
|
705
|
+
ensure
|
706
|
+
# Relying on fact that all dispatches/deliveries are synchronous and therefore
|
707
|
+
# need to have completed or failed by now, thus allowing packet acknowledgement
|
708
|
+
header.ack
|
709
|
+
end
|
710
|
+
true
|
711
|
+
end
|
712
|
+
|
713
|
+
# Dispatch request and then send response if any
|
714
|
+
#
|
715
|
+
# === Parameters
|
716
|
+
# request(Push|Request):: Packet containing request
|
717
|
+
# queue(String):: Name of queue from which message was received
|
718
|
+
#
|
719
|
+
# === Return
|
720
|
+
# true:: Always return true
|
721
|
+
def dispatch_request(request, queue)
|
722
|
+
begin
|
723
|
+
if result = @dispatcher.dispatch(request)
|
724
|
+
exchange = {:type => :queue, :name => "response", :options => {:durable => true, :no_declare => @options[:secure]}}
|
725
|
+
@broker.publish(exchange, result, :persistent => true, :mandatory => true, :log_filter => [:tries, :persistent, :duration])
|
726
|
+
end
|
727
|
+
rescue Dispatcher::DuplicateRequest
|
728
|
+
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
729
|
+
Log.error("Failed to publish result of dispatched request #{request.trace} from queue #{queue}", e)
|
730
|
+
@request_failure_stats.update("NoConnectedBrokers")
|
731
|
+
rescue Exception => e
|
732
|
+
Log.error("Failed to dispatch request #{request.trace} from queue #{queue}", e, :trace)
|
733
|
+
@request_failure_stats.update(e.class.name)
|
734
|
+
@exception_stats.track("request", e)
|
735
|
+
end
|
736
|
+
true
|
737
|
+
end
|
738
|
+
|
739
|
+
# Deliver response to request sender
|
740
|
+
#
|
741
|
+
# === Parameters
|
742
|
+
# result(Result):: Packet containing response
|
743
|
+
#
|
744
|
+
# === Return
|
745
|
+
# true:: Always return true
|
746
|
+
def deliver_response(result)
|
747
|
+
begin
|
748
|
+
@sender.handle_response(result)
|
749
|
+
rescue Exception => e
|
750
|
+
Log.error("Failed to deliver response #{result.trace}", e, :trace)
|
751
|
+
@response_failure_stats.update(e.class.name)
|
752
|
+
@exception_stats.track("response", e)
|
790
753
|
end
|
791
754
|
true
|
792
755
|
end
|
@@ -804,45 +767,51 @@ module RightScale
|
|
804
767
|
true
|
805
768
|
end
|
806
769
|
|
807
|
-
#
|
808
|
-
#
|
770
|
+
# Setup periodic status check
|
771
|
+
#
|
772
|
+
# === Parameters
|
773
|
+
# interval(Integer):: Number of seconds between status checks
|
774
|
+
#
|
775
|
+
# === Return
|
776
|
+
# true:: Always return true
|
777
|
+
def setup_status_checks(interval)
|
778
|
+
@check_status_count = 0
|
779
|
+
@check_status_brokers = @broker.all
|
780
|
+
@check_status_timer = EM::PeriodicTimer.new(interval) { check_status }
|
781
|
+
true
|
782
|
+
end
|
783
|
+
|
784
|
+
# Check status of agent by gathering current operation statistics and publishing them
|
785
|
+
# and finishing any queue setup
|
786
|
+
# Although agent termination cancels the check_status_timer, this method could induce
|
787
|
+
# termination, therefore the termination status needs to be checked before each step
|
809
788
|
#
|
810
789
|
# === Return
|
811
790
|
# true:: Always return true
|
812
791
|
def check_status
|
813
792
|
begin
|
814
|
-
finish_setup
|
793
|
+
finish_setup unless @terminating
|
815
794
|
rescue Exception => e
|
816
795
|
Log.error("Failed finishing setup", e)
|
817
|
-
@
|
796
|
+
@exception_stats.track("check status", e)
|
818
797
|
end
|
819
798
|
|
820
799
|
begin
|
821
|
-
if @stats_routing_key
|
800
|
+
if @stats_routing_key && !@terminating
|
822
801
|
exchange = {:type => :topic, :name => "stats", :options => {:no_declare => true}}
|
823
802
|
@broker.publish(exchange, Stats.new(stats.content, @identity), :no_log => true,
|
824
803
|
:routing_key => @stats_routing_key, :brokers => @check_status_brokers.rotate!)
|
825
804
|
end
|
826
805
|
rescue Exception => e
|
827
806
|
Log.error("Failed publishing stats", e)
|
828
|
-
@
|
829
|
-
end
|
830
|
-
|
831
|
-
@deferred_tasks.reject! do |t|
|
832
|
-
begin
|
833
|
-
t.call
|
834
|
-
rescue Exception => e
|
835
|
-
Log.error("Failed to perform deferred task", e)
|
836
|
-
@exceptions.track("check status", e)
|
837
|
-
end
|
838
|
-
true
|
807
|
+
@exception_stats.track("check status", e)
|
839
808
|
end
|
840
809
|
|
841
810
|
begin
|
842
|
-
check_other(@check_status_count)
|
811
|
+
check_other(@check_status_count) unless @terminating
|
843
812
|
rescue Exception => e
|
844
813
|
Log.error("Failed to perform other check status check", e)
|
845
|
-
@
|
814
|
+
@exception_stats.track("check status", e)
|
846
815
|
end
|
847
816
|
|
848
817
|
@check_status_count += 1
|
@@ -879,13 +848,57 @@ module RightScale
|
|
879
848
|
# terminating regardless of whether there are still unfinished requests
|
880
849
|
#
|
881
850
|
# === Block
|
882
|
-
#
|
851
|
+
# Optional block to be executed after stopping message processing wherever possible
|
883
852
|
#
|
884
853
|
# === Return
|
885
854
|
# true:: Always return true
|
886
|
-
def stop_gracefully(timeout)
|
855
|
+
def stop_gracefully(timeout, &block)
|
887
856
|
@broker.unusable.each { |id| @broker.close_one(id, propagate = false) }
|
888
|
-
|
857
|
+
finish_terminating(timeout, &block)
|
858
|
+
end
|
859
|
+
|
860
|
+
# Finish termination after all requests have been processed
|
861
|
+
#
|
862
|
+
# === Parameters
|
863
|
+
# timeout(Integer):: Maximum number of seconds to wait after last request received before
|
864
|
+
# terminating regardless of whether there are still unfinished requests
|
865
|
+
#
|
866
|
+
# === Block
|
867
|
+
# Optional block to be executed after stopping message processing wherever possible
|
868
|
+
#
|
869
|
+
# === Return
|
870
|
+
# true:: Always return true
|
871
|
+
def finish_terminating(timeout, &block)
|
872
|
+
if @sender
|
873
|
+
request_count, request_age = @sender.terminate
|
874
|
+
|
875
|
+
finish = lambda do
|
876
|
+
request_count, request_age = @sender.terminate
|
877
|
+
Log.info("[stop] The following #{request_count} requests initiated as recently as #{request_age} " +
|
878
|
+
"seconds ago are being dropped:\n " + @sender.dump_requests.join("\n ")) if request_age
|
879
|
+
@broker.close { block.call }
|
880
|
+
end
|
881
|
+
|
882
|
+
if (wait_time = [timeout - (request_age || timeout), 0].max) > 0
|
883
|
+
Log.info("[stop] Termination waiting #{wait_time} seconds for completion of #{request_count} " +
|
884
|
+
"requests initiated as recently as #{request_age} seconds ago")
|
885
|
+
@termination_timer = EM::Timer.new(wait_time) do
|
886
|
+
begin
|
887
|
+
Log.info("[stop] Continuing with termination")
|
888
|
+
finish.call
|
889
|
+
rescue Exception => e
|
890
|
+
Log.error("Failed while finishing termination", e, :trace)
|
891
|
+
begin block.call; rescue Exception; end
|
892
|
+
end
|
893
|
+
end
|
894
|
+
else
|
895
|
+
finish.call
|
896
|
+
end
|
897
|
+
else
|
898
|
+
block.call
|
899
|
+
end
|
900
|
+
@history.update("graceful exit")
|
901
|
+
true
|
889
902
|
end
|
890
903
|
|
891
904
|
# Determine current revision of software
|