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.
@@ -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