right_agent 0.13.5 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/sender.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
|
@@ -28,6 +28,9 @@ module RightScale
|
|
28
28
|
# All requests go through the mapper for security purposes
|
29
29
|
class Sender
|
30
30
|
|
31
|
+
class SendFailure < Exception; end
|
32
|
+
class TemporarilyOffline < Exception; end
|
33
|
+
|
31
34
|
# Request that is waiting for a response
|
32
35
|
class PendingRequest
|
33
36
|
|
@@ -432,7 +435,6 @@ module RightScale
|
|
432
435
|
# Attempt to reconnect if ping does not respond in PING_TIMEOUT seconds and
|
433
436
|
# if have reached timeout limit
|
434
437
|
# Ignore request if already checking a connection
|
435
|
-
# Only to be called from primary thread
|
436
438
|
#
|
437
439
|
# === Parameters
|
438
440
|
# id(String):: Identity of specific broker to use to send ping, defaults to any
|
@@ -484,7 +486,7 @@ module RightScale
|
|
484
486
|
request = Request.new("/mapper/ping", nil, {:from => @sender.identity, :token => AgentIdentity.generate})
|
485
487
|
@sender.pending_requests[request.token] = PendingRequest.new(:send_persistent_request, Time.now, handler)
|
486
488
|
ids = [@ping_id] if @ping_id
|
487
|
-
@ping_id = @sender.publish
|
489
|
+
@ping_id = @sender.__send__(:publish, request, ids).first
|
488
490
|
end
|
489
491
|
true
|
490
492
|
end
|
@@ -549,7 +551,7 @@ module RightScale
|
|
549
551
|
# (Agent) Associated agent
|
550
552
|
attr_reader :agent
|
551
553
|
|
552
|
-
#
|
554
|
+
# For direct access to current sender
|
553
555
|
#
|
554
556
|
# === Return
|
555
557
|
# (Sender):: This sender instance if defined, otherwise nil
|
@@ -577,19 +579,14 @@ module RightScale
|
|
577
579
|
# :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
|
578
580
|
# by the receiver, 0 means never expire
|
579
581
|
# :secure(Boolean):: true indicates to use Security features of rabbitmq to restrict agents to themselves
|
580
|
-
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
581
|
-
# to do requested work on EM defer thread and all else, such as pings on main thread
|
582
582
|
def initialize(agent)
|
583
583
|
@agent = agent
|
584
584
|
@identity = @agent.identity
|
585
585
|
@options = @agent.options || {}
|
586
586
|
@broker = @agent.broker
|
587
587
|
@secure = @options[:secure]
|
588
|
-
@single_threaded = @options[:single_threaded]
|
589
588
|
@retry_timeout = RightSupport::Stats.nil_if_zero(@options[:retry_timeout])
|
590
589
|
@retry_interval = RightSupport::Stats.nil_if_zero(@options[:retry_interval])
|
591
|
-
|
592
|
-
# Only to be accessed from primary thread
|
593
590
|
@pending_requests = PendingRequests.new
|
594
591
|
|
595
592
|
reset_stats
|
@@ -637,6 +634,14 @@ module RightScale
|
|
637
634
|
@offline_handler.disable if @options[:offline_queueing]
|
638
635
|
end
|
639
636
|
|
637
|
+
# Determine whether currently offline
|
638
|
+
#
|
639
|
+
# === Return
|
640
|
+
# (Boolean):: true if offline or if not connected to any brokers, otherwise false
|
641
|
+
def offline?
|
642
|
+
(@options[:offline_queueing] && @offline_handler.offline?) || @broker.connected.size == 0
|
643
|
+
end
|
644
|
+
|
640
645
|
# Update the time this agent last received a request or response message
|
641
646
|
# Also forward this message receipt notification to any callbacks that have registered
|
642
647
|
#
|
@@ -678,8 +683,13 @@ module RightScale
|
|
678
683
|
#
|
679
684
|
# === Return
|
680
685
|
# true:: Always return true
|
686
|
+
#
|
687
|
+
# === Raise
|
688
|
+
# SendFailure:: If publishing of request failed unexpectedly
|
689
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
690
|
+
# to any brokers and offline queueing is disabled
|
681
691
|
def send_push(type, payload = nil, target = nil, &callback)
|
682
|
-
|
692
|
+
build_and_send_packet(:send_push, type, payload, target, callback)
|
683
693
|
end
|
684
694
|
|
685
695
|
# Send a request to a single target or multiple targets with no response expected other
|
@@ -712,8 +722,13 @@ module RightScale
|
|
712
722
|
#
|
713
723
|
# === Return
|
714
724
|
# true:: Always return true
|
725
|
+
#
|
726
|
+
# === Raise
|
727
|
+
# SendFailure:: If publishing of request failed unexpectedly
|
728
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
729
|
+
# to any brokers and offline queueing is disabled
|
715
730
|
def send_persistent_push(type, payload = nil, target = nil, &callback)
|
716
|
-
|
731
|
+
build_and_send_packet(:send_persistent_push, type, payload, target, callback)
|
717
732
|
end
|
718
733
|
|
719
734
|
# Send a request to a single target with a response expected
|
@@ -721,8 +736,7 @@ module RightScale
|
|
721
736
|
# or if there is a non-delivery response indicating the target is not currently available
|
722
737
|
# Timeout the request if a response is not received in time, typically configured to 2 minutes
|
723
738
|
# Because of retries there is the possibility of duplicated requests, and these are detected and
|
724
|
-
# discarded automatically
|
725
|
-
# method should not be used for actions that are non-idempotent
|
739
|
+
# discarded automatically for non-idempotent actions
|
726
740
|
# Allow the request to expire per the agent's configured time-to-live, typically 1 minute
|
727
741
|
# Note that receiving a response does not guarantee that the request activity has actually
|
728
742
|
# completed since the request processing may involve other asynchronous requests
|
@@ -745,8 +759,15 @@ module RightScale
|
|
745
759
|
#
|
746
760
|
# === Return
|
747
761
|
# true:: Always return true
|
762
|
+
#
|
763
|
+
# === Raise
|
764
|
+
# ArgumentError:: If block missing
|
765
|
+
# SendFailure:: If publishing of request failed unexpectedly
|
766
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
767
|
+
# to any brokers and offline queueing is disabled
|
748
768
|
def send_retryable_request(type, payload = nil, target = nil, &callback)
|
749
|
-
|
769
|
+
raise ArgumentError, "Missing block for response callback" unless callback
|
770
|
+
build_and_send_packet(:send_retryable_request, type, payload, target, callback)
|
750
771
|
end
|
751
772
|
|
752
773
|
# Send a request to a single target with a response expected
|
@@ -776,83 +797,99 @@ module RightScale
|
|
776
797
|
#
|
777
798
|
# === Return
|
778
799
|
# true:: Always return true
|
800
|
+
#
|
801
|
+
# === Raise
|
802
|
+
# ArgumentError:: If block missing
|
803
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
804
|
+
# to any brokers and offline queueing is disabled
|
805
|
+
# SendFailure:: If publishing of request failed unexpectedly
|
779
806
|
def send_persistent_request(type, payload = nil, target = nil, &callback)
|
780
|
-
|
807
|
+
raise ArgumentError, "Missing block for response callback" unless callback
|
808
|
+
build_and_send_packet(:send_persistent_request, type, payload, target, callback)
|
809
|
+
end
|
810
|
+
|
811
|
+
# Build packet
|
812
|
+
#
|
813
|
+
# === Parameters
|
814
|
+
# kind(Symbol):: Kind of send request: :send_push, :send_persistent_push, :send_retryable_request,
|
815
|
+
# or :send_persistent_request
|
816
|
+
# type(String):: Dispatch route for the request; typically identifies actor and action
|
817
|
+
# payload(Object):: Data to be sent with marshalling en route
|
818
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets
|
819
|
+
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
820
|
+
# :scope(Hash):: Scoping to be used to restrict routing
|
821
|
+
# :account(Integer):: Restrict to agents with this account id
|
822
|
+
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
823
|
+
# ones with no shard id
|
824
|
+
# :selector(Symbol):: Which of the matched targets to be selected: :any or :all
|
825
|
+
# callback(Boolean):: Whether this request has an associated response callback
|
826
|
+
#
|
827
|
+
# === Return
|
828
|
+
# (Push|Request):: Packet created
|
829
|
+
def build_packet(kind, type, payload, target, callback = false)
|
830
|
+
kind_str = kind.to_s
|
831
|
+
persistent = !!(kind_str =~ /persistent/)
|
832
|
+
if kind_str =~ /push/
|
833
|
+
packet = Push.new(type, payload)
|
834
|
+
packet.selector = target[:selector] || :any if target.is_a?(Hash)
|
835
|
+
packet.confirm = true if callback
|
836
|
+
else
|
837
|
+
packet = Request.new(type, payload)
|
838
|
+
ttl = @options[:time_to_live]
|
839
|
+
packet.expires_at = Time.now.to_i + ttl if !persistent && ttl && ttl != 0
|
840
|
+
packet.selector = :any
|
841
|
+
end
|
842
|
+
packet.from = @identity
|
843
|
+
packet.token = AgentIdentity.generate
|
844
|
+
packet.persistent = persistent
|
845
|
+
if target.is_a?(Hash)
|
846
|
+
packet.tags = target[:tags] || []
|
847
|
+
packet.scope = target[:scope]
|
848
|
+
else
|
849
|
+
packet.target = target
|
850
|
+
end
|
851
|
+
packet
|
781
852
|
end
|
782
853
|
|
783
854
|
# Handle response to a request
|
784
|
-
# Acknowledge response after delivering it
|
785
|
-
# Only to be called from primary thread
|
786
855
|
#
|
787
856
|
# === Parameters
|
788
857
|
# response(Result):: Packet received as result of request
|
789
|
-
# header(AMQP::Frame::Header|nil):: Request header containing ack control
|
790
858
|
#
|
791
859
|
# === Return
|
792
860
|
# true:: Always return true
|
793
|
-
def handle_response(response
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
elsif result.error?
|
802
|
-
@result_error_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
803
|
-
end
|
804
|
-
@result_stats.update(result.status)
|
805
|
-
else
|
806
|
-
@result_stats.update(response.results.nil? ? "nil" : response.results)
|
861
|
+
def handle_response(response)
|
862
|
+
token = response.token
|
863
|
+
if response.is_a?(Result)
|
864
|
+
if result = OperationResult.from_results(response)
|
865
|
+
if result.non_delivery?
|
866
|
+
@non_delivery_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
867
|
+
elsif result.error?
|
868
|
+
@result_error_stats.update(result.content.nil? ? "nil" : result.content.inspect)
|
807
869
|
end
|
870
|
+
@result_stats.update(result.status)
|
871
|
+
else
|
872
|
+
@result_stats.update(response.results.nil? ? "nil" : response.results)
|
873
|
+
end
|
808
874
|
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
815
|
-
else
|
816
|
-
ack_deferred = true
|
817
|
-
deliver(response, handler, header)
|
818
|
-
end
|
819
|
-
elsif result && result.non_delivery?
|
875
|
+
if handler = @pending_requests[token]
|
876
|
+
if result && result.non_delivery? && handler.kind == :send_retryable_request &&
|
877
|
+
[OperationResult::TARGET_NOT_CONNECTED, OperationResult::TTL_EXPIRATION].include?(result.content)
|
878
|
+
# Log and ignore so that timeout retry mechanism continues
|
879
|
+
# Leave purging of associated request until final response, i.e., success response or retry timeout
|
820
880
|
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
821
881
|
else
|
822
|
-
|
882
|
+
deliver(response, handler)
|
823
883
|
end
|
884
|
+
elsif result && result.non_delivery?
|
885
|
+
Log.info("Non-delivery of <#{token}> because #{result.content}")
|
886
|
+
else
|
887
|
+
Log.debug("No pending request for response #{response.to_s([])}")
|
824
888
|
end
|
825
|
-
ensure
|
826
|
-
header.ack unless ack_deferred || header.nil?
|
827
889
|
end
|
828
890
|
true
|
829
891
|
end
|
830
892
|
|
831
|
-
# Publish request
|
832
|
-
# Use mandatory flag to request return of message if it cannot be delivered
|
833
|
-
#
|
834
|
-
# === Parameters
|
835
|
-
# request(Push|Request):: Packet to be sent
|
836
|
-
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
837
|
-
#
|
838
|
-
# === Return
|
839
|
-
# ids(Array):: Identity of brokers published to
|
840
|
-
def publish(request, ids = nil)
|
841
|
-
begin
|
842
|
-
exchange = {:type => :fanout, :name => "request", :options => {:durable => true, :no_declare => @secure}}
|
843
|
-
ids = @broker.publish(exchange, request, :persistent => request.persistent, :mandatory => true,
|
844
|
-
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
845
|
-
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
846
|
-
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e)
|
847
|
-
ids = []
|
848
|
-
rescue Exception => e
|
849
|
-
Log.error("Failed to publish request #{request.to_s([:tags, :target, :tries])}", e, :trace)
|
850
|
-
@exception_stats.track("publish", e, request)
|
851
|
-
ids = []
|
852
|
-
end
|
853
|
-
ids
|
854
|
-
end
|
855
|
-
|
856
893
|
# Take any actions necessary to quiesce mapper interaction in preparation
|
857
894
|
# for agent termination but allow message receipt to continue
|
858
895
|
#
|
@@ -909,6 +946,8 @@ module RightScale
|
|
909
946
|
# with percentage breakdown per operation result type, or nil if none
|
910
947
|
# "retries"(Hash|nil):: Retry activity stats with keys "total", "percent", "last", and "rate"
|
911
948
|
# with percentage breakdown per request type, or nil if none
|
949
|
+
# "send failure"(Hash|nil):: Send failure activity stats with keys "total", "percent", "last", and "rate"
|
950
|
+
# with percentage breakdown per failure type, or nil if none
|
912
951
|
def stats(reset = false)
|
913
952
|
offlines = @offline_stats.all
|
914
953
|
offlines.merge!("duration" => @offline_stats.avg_duration) if offlines
|
@@ -925,13 +964,14 @@ module RightScale
|
|
925
964
|
"non-deliveries" => @non_delivery_stats.all,
|
926
965
|
"offlines" => offlines,
|
927
966
|
"pings" => @ping_stats.all,
|
928
|
-
"request kinds" => @
|
967
|
+
"request kinds" => @request_kind_stats.all,
|
929
968
|
"requests" => @request_stats.all,
|
930
969
|
"requests pending" => pending,
|
931
970
|
"response time" => @request_stats.avg_duration,
|
932
971
|
"result errors" => @result_error_stats.all,
|
933
972
|
"results" => @result_stats.all,
|
934
|
-
"retries" => @retry_stats.all
|
973
|
+
"retries" => @retry_stats.all,
|
974
|
+
"send failures" => @send_failure_stats.all
|
935
975
|
}
|
936
976
|
reset_stats if reset
|
937
977
|
stats
|
@@ -951,171 +991,149 @@ module RightScale
|
|
951
991
|
@result_error_stats = RightSupport::Stats::Activity.new
|
952
992
|
@non_delivery_stats = RightSupport::Stats::Activity.new
|
953
993
|
@offline_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
954
|
-
@
|
994
|
+
@request_kind_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
995
|
+
@send_failure_stats = RightSupport::Stats::Activity.new
|
955
996
|
@exception_stats = RightSupport::Stats::Exceptions.new(@agent, @options[:exception_callback])
|
956
997
|
true
|
957
998
|
end
|
958
999
|
|
959
|
-
#
|
1000
|
+
# Validate target argument of send per the semantics of each kind of send:
|
1001
|
+
# - The target is either a specific target name, a non-empty hash, or nil
|
1002
|
+
# - A specific target name must be a string
|
1003
|
+
# - A non-empty hash target
|
1004
|
+
# - may have keys in symbol or string format
|
1005
|
+
# - may be allowed to contain a :selector key with value :any or :all,
|
1006
|
+
# depending on the kind of send
|
1007
|
+
# - may contain a :scope key with a hash value with keys :account and/or :shard
|
1008
|
+
# - may contain a :tags key with an array value
|
960
1009
|
#
|
961
1010
|
# === Parameters
|
962
|
-
#
|
963
|
-
#
|
964
|
-
#
|
965
|
-
# target(String|Hash):: Identity of specific target, or hash for selecting potentially multiple
|
966
|
-
# targets, or nil if routing solely using type
|
967
|
-
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
968
|
-
# :scope(Hash):: Scoping to be used to restrict routing
|
969
|
-
# :account(Integer):: Restrict to agents with this account id
|
970
|
-
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
971
|
-
# ones with no shard id
|
972
|
-
# :selector(Symbol):: Which of the matched targets to be selected, either :any or :all,
|
973
|
-
# defaults to :any
|
974
|
-
#
|
975
|
-
# === Block
|
976
|
-
# Optional block used to process routing responses asynchronously with the following parameter:
|
977
|
-
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
978
|
-
# with an initial SUCCESS response containing the targets to which the mapper published the
|
979
|
-
# request and any additional responses indicating any failures to actually route the request
|
980
|
-
# to those targets, use RightScale::OperationResult.from_results to decode
|
1011
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets;
|
1012
|
+
# returned with all hash keys converted to symbols
|
1013
|
+
# allow_selector(Boolean):: Whether to allow :selector
|
981
1014
|
#
|
982
1015
|
# === Return
|
983
1016
|
# true:: Always return true
|
984
1017
|
#
|
985
1018
|
# === Raise
|
986
1019
|
# ArgumentError:: If target is invalid
|
987
|
-
def
|
988
|
-
|
989
|
-
if
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1020
|
+
def validate_target(target, allow_selector)
|
1021
|
+
choices = (allow_selector ? ":selector, " : "") + ":tags and/or :scope"
|
1022
|
+
if target.is_a?(Hash)
|
1023
|
+
t = SerializationHelper.symbolize_keys(target)
|
1024
|
+
|
1025
|
+
if selector = t[:selector]
|
1026
|
+
if allow_selector
|
1027
|
+
selector = selector.to_sym
|
1028
|
+
unless [:any, :all].include?(selector)
|
1029
|
+
raise ArgumentError, "Invalid target selector (#{t[:selector].inspect}), choices are :any and :all"
|
1030
|
+
end
|
1031
|
+
t[:selector] = selector
|
1032
|
+
else
|
1033
|
+
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{choices}"
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
if scope = t[:scope]
|
1038
|
+
if scope.is_a?(Hash)
|
1039
|
+
scope = SerializationHelper.symbolize_keys(scope)
|
1040
|
+
unless (scope[:account] || scope[:shard]) && (scope.keys - [:account, :shard]).empty?
|
1041
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), choices are :account and :shard"
|
1042
|
+
end
|
1043
|
+
t[:scope] = scope
|
1044
|
+
else
|
1045
|
+
raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), must be a hash of :account and/or :shard"
|
1046
|
+
end
|
1003
1047
|
end
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1048
|
+
|
1049
|
+
if (tags = t[:tags]) && !tags.is_a?(Array)
|
1050
|
+
raise ArgumentError, "Invalid target tags (#{t[:tags].inspect}), must be an array"
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
unless (selector || scope || tags) && (t.keys - [:selector, :scope, :tags]).empty?
|
1054
|
+
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{choices}"
|
1009
1055
|
end
|
1010
|
-
|
1056
|
+
target = t
|
1057
|
+
elsif !target.nil? && !target.is_a?(String)
|
1058
|
+
raise ArgumentError, "Invalid target (#{target.inspect}), choices are specific target name or a hash of #{choices}"
|
1011
1059
|
end
|
1012
1060
|
true
|
1013
1061
|
end
|
1014
1062
|
|
1015
|
-
# Build and send
|
1063
|
+
# Build and send packet
|
1016
1064
|
#
|
1017
1065
|
# === Parameters
|
1018
|
-
# kind(Symbol):: Kind of request: :
|
1066
|
+
# kind(Symbol):: Kind of send request: :send_push, :send_persistent_push, :send_retryable_request,
|
1067
|
+
# or :send_persistent_request
|
1019
1068
|
# type(String):: Dispatch route for the request; typically identifies actor and action
|
1020
1069
|
# payload(Object):: Data to be sent with marshalling en route
|
1021
|
-
# target(String|Hash):: Identity of specific target, or hash for selecting targets
|
1022
|
-
# randomly, or nil if routing solely using type
|
1070
|
+
# target(String|Hash):: Identity of specific target, or hash for selecting targets
|
1023
1071
|
# :tags(Array):: Tags that must all be associated with a target for it to be selected
|
1024
1072
|
# :scope(Hash):: Scoping to be used to restrict routing
|
1025
1073
|
# :account(Integer):: Restrict to agents with this account id
|
1026
1074
|
# :shard(Integer):: Restrict to agents with this shard id, or if value is Packet::GLOBAL,
|
1027
1075
|
# ones with no shard id
|
1028
|
-
#
|
1029
|
-
#
|
1030
|
-
# Required block used to process response asynchronously with the following parameter:
|
1031
|
-
# result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR,
|
1032
|
-
# use RightScale::OperationResult.from_results to decode
|
1076
|
+
# :selector(Symbol):: Which of the matched targets to be selected: :any or :all
|
1077
|
+
# callback(Proc|nil):: Block used to process routing response
|
1033
1078
|
#
|
1034
1079
|
# === Return
|
1035
1080
|
# true:: Always return true
|
1036
1081
|
#
|
1037
1082
|
# === Raise
|
1038
1083
|
# ArgumentError:: If target is invalid
|
1039
|
-
|
1040
|
-
|
1084
|
+
# SendFailure:: If publishing of request fails unexpectedly
|
1085
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
1086
|
+
# to any brokers and offline queueing is disabled
|
1087
|
+
def build_and_send_packet(kind, type, payload, target, callback)
|
1088
|
+
validate_target(target, allow_selector = !!(kind.to_s =~ /push/))
|
1041
1089
|
if should_queue?
|
1042
1090
|
@offline_handler.queue_request(kind, type, payload, target, callback)
|
1043
1091
|
else
|
1092
|
+
packet = build_packet(kind, type, payload, target, callback)
|
1044
1093
|
method = type.split('/').last
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
begin
|
1053
|
-
request = Request.new(type, payload)
|
1054
|
-
request.from = @identity
|
1055
|
-
request.token = token
|
1056
|
-
if target.is_a?(Hash)
|
1057
|
-
request.tags = target[:tags] || []
|
1058
|
-
request.scope = target[:scope]
|
1059
|
-
request.selector = :any
|
1060
|
-
else
|
1061
|
-
request.target = target
|
1062
|
-
end
|
1063
|
-
request.expires_at = Time.now.to_i + @options[:time_to_live] if !non_duplicate && @options[:time_to_live] && @options[:time_to_live] != 0
|
1064
|
-
request.persistent = non_duplicate
|
1065
|
-
@pending_requests[token] = PendingRequest.new(kind, received_at, callback)
|
1066
|
-
if non_duplicate
|
1067
|
-
publish(request)
|
1068
|
-
else
|
1069
|
-
publish_with_timeout_retry(request, token)
|
1070
|
-
end
|
1071
|
-
rescue Exception => e
|
1072
|
-
Log.error("Failed to send #{type} #{kind.to_s}", e, :trace)
|
1073
|
-
@exception_stats.track(kind.to_s, e, request)
|
1074
|
-
end
|
1094
|
+
received_at = @request_stats.update(method, packet.token)
|
1095
|
+
@request_kind_stats.update((packet.selector == :all ? kind.to_s.sub(/push/, "fanout") : kind.to_s)[5..-1])
|
1096
|
+
@pending_requests[packet.token] = PendingRequest.new(kind, received_at, callback) if callback
|
1097
|
+
if !packet.persistent && kind.to_s =~ /request/
|
1098
|
+
publish_with_timeout_retry(packet, packet.token)
|
1099
|
+
else
|
1100
|
+
publish(packet)
|
1075
1101
|
end
|
1076
1102
|
end
|
1077
1103
|
true
|
1078
1104
|
end
|
1079
1105
|
|
1080
|
-
#
|
1106
|
+
# Publish request to request queue
|
1107
|
+
# Use mandatory flag to request return of message if it cannot be delivered
|
1081
1108
|
#
|
1082
1109
|
# === Parameters
|
1083
|
-
#
|
1084
|
-
#
|
1110
|
+
# packet(Push|Request):: Packet to be sent
|
1111
|
+
# ids(Array|nil):: Identity of specific brokers to choose from, or nil if any okay
|
1085
1112
|
#
|
1086
1113
|
# === Return
|
1087
|
-
#
|
1114
|
+
# (Array):: Identity of brokers published to
|
1088
1115
|
#
|
1089
1116
|
# === Raise
|
1090
|
-
#
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
end
|
1110
|
-
t[:selector] = s
|
1111
|
-
elsif !t.has_key?(:tags) && !t.empty?
|
1112
|
-
raise ArgumentError, "Invalid target hash (#{target.inspect}), choices are #{selector}:tags and/or :scope"
|
1113
|
-
end
|
1114
|
-
target = t
|
1115
|
-
elsif !target.nil? && !target.is_a?(String)
|
1116
|
-
raise ArgumentError, "Invalid target (#{target.inspect}), choices are specific target name or a hash of #{selector}:tags and/or :scope"
|
1117
|
+
# SendFailure:: If publishing of request fails unexpectedly
|
1118
|
+
# TemporarilyOffline:: If cannot publish request because currently not connected
|
1119
|
+
# to any brokers and offline queueing is disabled
|
1120
|
+
def publish(request, ids = nil)
|
1121
|
+
begin
|
1122
|
+
exchange = {:type => :fanout, :name => "request", :options => {:durable => true, :no_declare => @secure}}
|
1123
|
+
@broker.publish(exchange, request, :persistent => request.persistent, :mandatory => true,
|
1124
|
+
:log_filter => [:tags, :target, :tries, :persistent], :brokers => ids)
|
1125
|
+
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
1126
|
+
msg = "Failed to publish request #{request.to_s([:tags, :target, :tries])}"
|
1127
|
+
Log.error(msg, e)
|
1128
|
+
@send_failure_stats.update("NoConnectedBrokers")
|
1129
|
+
raise TemporarilyOffline.new(msg + " (#{e.class}: #{e.message})")
|
1130
|
+
rescue Exception => e
|
1131
|
+
msg = "Failed to publish request #{request.to_s([:tags, :target, :tries])}"
|
1132
|
+
Log.error(msg, e, :trace)
|
1133
|
+
@send_failure_stats.update(e.class.name)
|
1134
|
+
@exception_stats.track("publish", e, request)
|
1135
|
+
raise SendFailure.new(msg + " (#{e.class}: #{e.message})")
|
1117
1136
|
end
|
1118
|
-
true
|
1119
1137
|
end
|
1120
1138
|
|
1121
1139
|
# Publish request with one or more retries if do not receive a response in time
|
@@ -1172,49 +1190,22 @@ module RightScale
|
|
1172
1190
|
end
|
1173
1191
|
|
1174
1192
|
# Deliver the response and remove associated request(s) from pending
|
1175
|
-
# Use defer thread instead of primary if not single threaded, consistent with dispatcher,
|
1176
|
-
# so that all shared data is accessed from the same thread
|
1177
|
-
# Do callback if there is an exception, consistent with agent identity queue handling
|
1178
|
-
# Only to be called from primary thread
|
1179
1193
|
#
|
1180
1194
|
# === Parameters
|
1181
1195
|
# response(Result):: Packet received as result of request
|
1182
1196
|
# handler(Hash):: Associated request handler
|
1183
|
-
# header(AMQP::Frame::Header|nil):: Request header containing ack control
|
1184
1197
|
#
|
1185
1198
|
# === Return
|
1186
1199
|
# true:: Always return true
|
1187
|
-
def deliver(response, handler
|
1188
|
-
|
1189
|
-
ack_deferred = false
|
1190
|
-
@request_stats.finish(handler.receive_time, response.token)
|
1191
|
-
|
1192
|
-
@pending_requests.delete(response.token) if PendingRequests::REQUEST_KINDS.include?(handler.kind)
|
1193
|
-
if parent = handler.retry_parent
|
1194
|
-
@pending_requests.reject! { |k, v| k == parent || v.retry_parent == parent }
|
1195
|
-
end
|
1200
|
+
def deliver(response, handler)
|
1201
|
+
@request_stats.finish(handler.receive_time, response.token)
|
1196
1202
|
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
EM.__send__(@single_threaded ? :next_tick : :defer) do
|
1201
|
-
begin
|
1202
|
-
handler.response_handler.call(response)
|
1203
|
-
rescue Exception => e
|
1204
|
-
Log.error("Failed processing response #{response.to_s([])}", e, :trace)
|
1205
|
-
@exception_stats.track("response", e, response)
|
1206
|
-
ensure
|
1207
|
-
header.ack if header
|
1208
|
-
end
|
1209
|
-
end
|
1210
|
-
rescue Exception
|
1211
|
-
header.ack if header
|
1212
|
-
raise
|
1213
|
-
end
|
1214
|
-
end
|
1215
|
-
ensure
|
1216
|
-
header.ack unless ack_deferred || header.nil?
|
1203
|
+
@pending_requests.delete(response.token) if PendingRequests::REQUEST_KINDS.include?(handler.kind)
|
1204
|
+
if parent = handler.retry_parent
|
1205
|
+
@pending_requests.reject! { |k, v| k == parent || v.retry_parent == parent }
|
1217
1206
|
end
|
1207
|
+
|
1208
|
+
handler.response_handler.call(response) if handler.response_handler
|
1218
1209
|
true
|
1219
1210
|
end
|
1220
1211
|
|
@@ -88,7 +88,7 @@ module RightScale
|
|
88
88
|
raise "Missing certificate" unless @cert
|
89
89
|
raise "Missing certificate key" unless @key
|
90
90
|
raise "Missing certificate store" unless @store || !@encrypt
|
91
|
-
must_encrypt = encrypt
|
91
|
+
must_encrypt = encrypt || @encrypt
|
92
92
|
serialize_format = if obj.respond_to?(:send_version) && obj.send_version >= 12
|
93
93
|
@serializer.format
|
94
94
|
else
|
@@ -136,7 +136,7 @@ module RightScale
|
|
136
136
|
raise InvalidSignature.new("Failed signature check for signer #{msg['id']}") unless certs.any? { |c| sig.match?(c) }
|
137
137
|
|
138
138
|
data = msg['data']
|
139
|
-
if data &&
|
139
|
+
if data && msg['encrypted']
|
140
140
|
data = EncryptedDocument.from_data(data).decrypted_data(@key, @cert)
|
141
141
|
end
|
142
142
|
@serializer.load(data) if data
|