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/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
|