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.
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2011 RightScale Inc
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(request, ids).first
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
- # Accessor for use by actor
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
- build_push(:send_push, type, payload, target, &callback)
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
- build_push(:send_persistent_push, type, payload, target, &callback)
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 unless the receiving agent is using a shared queue, in which case this
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
- build_request(:send_retryable_request, type, payload, target, &callback)
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
- build_request(:send_persistent_request, type, payload, target, &callback)
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, header = nil)
794
- begin
795
- ack_deferred = false
796
- token = response.token
797
- if response.is_a?(Result)
798
- if result = OperationResult.from_results(response)
799
- if result.non_delivery?
800
- @non_delivery_stats.update(result.content.nil? ? "nil" : result.content.inspect)
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
- if handler = @pending_requests[token]
810
- if result && result.non_delivery? && handler.kind == :send_retryable_request &&
811
- [OperationResult::TARGET_NOT_CONNECTED, OperationResult::TTL_EXPIRATION].include?(result.content)
812
- # Log and ignore so that timeout retry mechanism continues
813
- # Leave purging of associated request until final response, i.e., success response or retry timeout
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
- Log.debug("No pending request for response #{response.to_s([])}")
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" => @request_kinds.all,
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
- @request_kinds = RightSupport::Stats::Activity.new(measure_rate = false)
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
- # Build and send Push packet
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
- # kind(Symbol):: Kind of push: :send_push or :send_persistent_push
963
- # type(String):: Dispatch route for the request; typically identifies actor and action
964
- # payload(Object):: Data to be sent with marshalling en route
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 build_push(kind, type, payload = nil, target = nil, &callback)
988
- validate_target(target, allow_selector = true)
989
- if should_queue?
990
- @offline_handler.queue_request(kind, type, payload, target, callback)
991
- else
992
- method = type.split('/').last
993
- received_at = @request_stats.update(method)
994
- push = Push.new(type, payload)
995
- push.from = @identity
996
- push.token = AgentIdentity.generate
997
- if target.is_a?(Hash)
998
- push.tags = target[:tags] || []
999
- push.scope = target[:scope]
1000
- push.selector = target[:selector] || :any
1001
- else
1002
- push.target = target
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
- push.persistent = kind == :send_persistent_push
1005
- @request_kinds.update((push.selector == :all ? kind.to_s.sub(/push/, "fanout") : kind.to_s)[5..-1])
1006
- if callback
1007
- push.confirm = true
1008
- @pending_requests[push.token] = PendingRequest.new(kind, received_at, callback)
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
- publish(push)
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 Request packet
1063
+ # Build and send packet
1016
1064
  #
1017
1065
  # === Parameters
1018
- # kind(Symbol):: Kind of request: :send_retryable_request or :send_persistent_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 of which one is picked
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
- # === Block
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
- def build_request(kind, type, payload, target, &callback)
1040
- validate_target(target, allow_selector = false)
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
- token = AgentIdentity.generate
1046
- non_duplicate = kind == :send_persistent_request
1047
- received_at = @request_stats.update(method, token)
1048
- @request_kinds.update(kind.to_s[5..-1])
1049
-
1050
- # Using next_tick to ensure on primary thread since using @pending_requests
1051
- EM.next_tick do
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
- # Validate target argument of send
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
- # target(String|Hash):: Identity of specific target, or hash for selecting targets of which one is picked
1084
- # allow_selector(Boolean):: Whether to allow :selector
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
- # true:: Always return true
1114
+ # (Array):: Identity of brokers published to
1088
1115
  #
1089
1116
  # === Raise
1090
- # ArgumentError:: If target is invalid
1091
- def validate_target(target, allow_selector)
1092
- if target.is_a?(Hash)
1093
- selector = allow_selector ? ":selector, " : ""
1094
- t = SerializationHelper.symbolize_keys(target)
1095
- if s = target[:scope]
1096
- if s.is_a?(Hash)
1097
- s = SerializationHelper.symbolize_keys(s)
1098
- if ([:account, :shard] & s.keys).empty? && !s.empty?
1099
- raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), choices are :account and :shard allowed"
1100
- end
1101
- t[:scope] = s
1102
- else
1103
- raise ArgumentError, "Invalid target scope (#{t[:scope].inspect}), must be a hash of :account and/or :shard"
1104
- end
1105
- elsif (s = t[:selector]) && allow_selector
1106
- s = s.to_sym
1107
- unless [:any, :all].include?(s)
1108
- raise ArgumentError, "Invalid target selector (#{t[:selector].inspect}), choices are :any and :all"
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, header)
1188
- begin
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
- if handler.response_handler
1198
- begin
1199
- ack_deferred = true
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.nil? ? @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 && @encrypt && msg['encrypted']
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