right_agent 1.0.1 → 2.0.7

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.
Files changed (67) hide show
  1. data/README.rdoc +10 -8
  2. data/Rakefile +31 -5
  3. data/lib/right_agent.rb +6 -1
  4. data/lib/right_agent/actor.rb +4 -20
  5. data/lib/right_agent/actors/agent_manager.rb +1 -1
  6. data/lib/right_agent/agent.rb +357 -144
  7. data/lib/right_agent/agent_config.rb +7 -6
  8. data/lib/right_agent/agent_identity.rb +13 -11
  9. data/lib/right_agent/agent_tag_manager.rb +60 -64
  10. data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
  11. data/lib/right_agent/clients/api_client.rb +383 -0
  12. data/lib/right_agent/clients/auth_client.rb +247 -0
  13. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  14. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  15. data/lib/right_agent/clients/right_http_client.rb +279 -0
  16. data/lib/right_agent/clients/router_client.rb +493 -0
  17. data/lib/right_agent/command/command_io.rb +4 -4
  18. data/lib/right_agent/command/command_parser.rb +2 -2
  19. data/lib/right_agent/command/command_runner.rb +1 -1
  20. data/lib/right_agent/connectivity_checker.rb +179 -0
  21. data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
  22. data/lib/right_agent/dispatcher.rb +12 -10
  23. data/lib/right_agent/enrollment_result.rb +16 -12
  24. data/lib/right_agent/exceptions.rb +34 -20
  25. data/lib/right_agent/history.rb +10 -5
  26. data/lib/right_agent/log.rb +5 -5
  27. data/lib/right_agent/minimal.rb +1 -0
  28. data/lib/right_agent/multiplexer.rb +1 -1
  29. data/lib/right_agent/offline_handler.rb +270 -0
  30. data/lib/right_agent/packets.rb +7 -7
  31. data/lib/right_agent/payload_formatter.rb +1 -1
  32. data/lib/right_agent/pending_requests.rb +128 -0
  33. data/lib/right_agent/platform.rb +1 -1
  34. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  35. data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
  36. data/lib/right_agent/scripts/agent_controller.rb +28 -26
  37. data/lib/right_agent/scripts/agent_deployer.rb +37 -22
  38. data/lib/right_agent/scripts/common_parser.rb +10 -3
  39. data/lib/right_agent/secure_identity.rb +1 -1
  40. data/lib/right_agent/sender.rb +299 -785
  41. data/lib/right_agent/serialize/secure_serializer.rb +3 -1
  42. data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
  43. data/lib/right_agent/serialize/serializable.rb +8 -3
  44. data/right_agent.gemspec +49 -18
  45. data/spec/agent_config_spec.rb +7 -7
  46. data/spec/agent_identity_spec.rb +7 -4
  47. data/spec/agent_spec.rb +43 -7
  48. data/spec/agent_tag_manager_spec.rb +72 -83
  49. data/spec/clients/api_client_spec.rb +423 -0
  50. data/spec/clients/auth_client_spec.rb +272 -0
  51. data/spec/clients/balanced_http_client_spec.rb +576 -0
  52. data/spec/clients/base_retry_client_spec.rb +635 -0
  53. data/spec/clients/router_client_spec.rb +594 -0
  54. data/spec/clients/spec_helper.rb +111 -0
  55. data/spec/command/command_io_spec.rb +1 -1
  56. data/spec/command/command_parser_spec.rb +1 -1
  57. data/spec/connectivity_checker_spec.rb +83 -0
  58. data/spec/dispatcher_spec.rb +3 -2
  59. data/spec/enrollment_result_spec.rb +2 -2
  60. data/spec/history_spec.rb +51 -39
  61. data/spec/offline_handler_spec.rb +340 -0
  62. data/spec/pending_requests_spec.rb +136 -0
  63. data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
  64. data/spec/sender_spec.rb +835 -1052
  65. data/spec/serialize/secure_serializer_spec.rb +3 -2
  66. data/spec/spec_helper.rb +54 -1
  67. metadata +71 -12
@@ -5,14 +5,16 @@
5
5
  == Synopsis
6
6
 
7
7
  RightAgent provides a foundation for running an agent on a server to interface
8
- in a secure fashion with other agents in the RightScale system. A RightAgent
9
- uses RabbitMQ as the message bus and the RightScale mapper as the routing node.
10
- Servers running a RightAgent establish a queue on startup for receiving packets
11
- routed to it via the mapper. The packets are structured to invoke services in
12
- the agent represented by actors and methods. The RightAgent may respond to these
13
- requests with a result packet that the mapper then routes to the originator.
14
- Similarly a RightAgent can also make requests of other RightAgents in the
15
- RightScale system.
8
+ in a secure fashion with other agents in the RightScale system using RightNet,
9
+ which operates in either HTTP or AMQP mode. When using HTTP, RightAgent
10
+ makes requests to RightApi servers and receives requests using long-polling or
11
+ WebSockets via the RightNet router. To respond to requests it posts to the
12
+ HTTP router. When using AMQP, RightAgent uses RabbitMQ as the message bus and
13
+ the RightNet router as the routing node to make requests; to receives requests
14
+ routed to it by the RightNet router, it establishes a queue on startup. The
15
+ packets are structured to invoke services in the agent represented by actors
16
+ and methods. The RightAgent may respond to these requests with a result packet
17
+ that the router then routes to the originator.
16
18
 
17
19
  Refer to the wiki (https://github.com/rightscale/right_agent/wikis) for up-to-date
18
20
  documentation.
data/Rakefile CHANGED
@@ -33,12 +33,38 @@ require 'rake/clean'
33
33
  task :default => 'spec'
34
34
 
35
35
  # == Gem packaging == #
36
+ module RightScale
37
+ class MultiPlatformGemTask
38
+ def self.gem_platform_override
39
+ @gem_platform_override
40
+ end
36
41
 
37
- desc "Package gem"
38
- gemtask = Rake::GemPackageTask.new(Gem::Specification.load("right_agent.gemspec")) do |package|
39
- package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
40
- package.need_zip = true
41
- package.need_tar = true
42
+ def self.define(gem_platforms, spec_path, &callback)
43
+ gem_platforms.each do |gem_platform|
44
+ @gem_platform_override = gem_platform
45
+ callback.call(::Gem::Specification.load(spec_path))
46
+ end
47
+ ensure
48
+ @gem_platform_override = nil
49
+ end
50
+ end
51
+ end
52
+
53
+ # multiply define gem and package task(s) using a gemspec with overridden gem
54
+ # platform value. this works because rake accumulates task actions instead of
55
+ # redefining them, so accumulated gem tasks will gem up all platforms. we need
56
+ # to produce multiple platform-specific .gem files because otherwise the gem
57
+ # dependencies for non-Linux platforms (i.e. Windows) are lost from the default
58
+ # .gem file produced on a Linux platform.
59
+ gemtask = nil
60
+ ::RightScale::MultiPlatformGemTask.define(%w[linux mingw], 'right_agent.gemspec') do |spec|
61
+ gemtask = ::Rake::GemPackageTask.new(spec) do |gpt|
62
+ gpt.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
63
+
64
+ # the following are used by 'package' task (but not by 'gem' task)
65
+ gpt.need_zip = !`which zip`.strip.empty? # not present on Windows by default
66
+ gpt.need_tar = true # some form of tar is required on Windows and Linux
67
+ end
42
68
  end
43
69
 
44
70
  directory gemtask.package_dir
@@ -28,6 +28,7 @@ require 'openssl'
28
28
  require 'right_amqp'
29
29
 
30
30
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'monkey_patches'))
31
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'protocol_version_mixin'))
31
32
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'payload_formatter'))
32
33
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'packets'))
33
34
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'enrollment_result'))
@@ -47,8 +48,12 @@ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'actor'))
47
48
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'actor_registry'))
48
49
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'dispatcher'))
49
50
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'dispatched_cache'))
51
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'pending_requests'))
52
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'offline_handler'))
53
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'connectivity_checker'))
50
54
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'sender'))
51
55
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'secure_identity'))
52
- require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'idempotent_request'))
56
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'retryable_request'))
53
57
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'history'))
58
+ require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'clients'))
54
59
  require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'agent'))
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2011 RightScale Inc
2
+ # Copyright (c) 2009-2013 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
@@ -168,31 +168,15 @@ module RightScale
168
168
  Sender.instance.send_push(*args)
169
169
  end
170
170
 
171
- # Helper method to send a request to one or more targets with no response expected
172
- # The request is persisted en route to reduce the chance of it being lost at the expense of some
173
- # additional network overhead
174
- def send_persistent_push(*args)
175
- Sender.instance.send_persistent_push(*args)
176
- end
177
-
178
171
  # Helper method to send a request to a single target with a response expected
179
172
  # The request is retried if the response is not received in a reasonable amount of time
180
- # The request is timed out if not received in time, typically configured to 2 minutes
181
173
  # The request is allowed to expire per the agent's configured time-to-live, typically 1 minute
182
- def send_retryable_request(*args, &blk)
183
- Sender.instance.send_retryable_request(*args, &blk)
184
- end
185
-
186
- # Helper method to send a request to a single target with a response expected
187
- # The request is persisted en route to reduce the chance of it being lost at the expense of some
188
- # additional network overhead
189
- # The request is never retried if there is the possibility of it being duplicated
190
- def send_persistent_request(*args, &blk)
191
- Sender.instance.send_persistent_request(*args, &blk)
174
+ def send_request(*args, &blk)
175
+ Sender.instance.send_request(*args, &blk)
192
176
  end
193
177
 
194
178
  end # InstanceMethods
195
179
 
196
180
  end # Actor
197
-
181
+
198
182
  end # RightScale
@@ -53,7 +53,7 @@ class AgentManager
53
53
  success_result(:identity => @agent.options[:identity],
54
54
  :hostname => Socket.gethostname,
55
55
  :version => RightScale::AgentConfig.protocol_version,
56
- :brokers => @agent.broker.status,
56
+ :client => @agent.client.status,
57
57
  :time => Time.now.to_i)
58
58
  end
59
59
 
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2012 RightScale Inc
2
+ # Copyright (c) 2009-2013 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
@@ -24,9 +24,10 @@ require 'socket'
24
24
 
25
25
  module RightScale
26
26
 
27
- # Agent for receiving messages from the mapper and acting upon them
27
+ # Agent for receiving requests via RightNet and acting upon them
28
28
  # by dispatching to a registered actor to perform
29
29
  # See load_actors for details on how the agent specific environment is loaded
30
+ # Operates in either HTTP or AMQP mode for RightNet communication
30
31
  class Agent
31
32
 
32
33
  include ConsoleHelper
@@ -38,14 +39,20 @@ module RightScale
38
39
  # (Hash) Configuration options applied to the agent
39
40
  attr_reader :options
40
41
 
41
- # (Hash) Dispatcher for each queue for messages received
42
+ # (Hash) Dispatcher for each queue for messages received via AMQP
42
43
  attr_reader :dispatchers
43
44
 
44
45
  # (ActorRegistry) Registry for this agents actors
45
46
  attr_reader :registry
46
47
 
47
- # (RightAMQP::HABrokerClient) High availability AMQP broker client
48
- attr_reader :broker
48
+ # (RightHttpClient|RightAMQP::HABrokerClient) Client for accessing RightNet/RightApi
49
+ attr_reader :client
50
+
51
+ # (Symbol) RightNet communication mode: :http or :amqp
52
+ attr_reader :mode
53
+
54
+ # (String) Name of AMQP queue to which requests are to be published
55
+ attr_reader :request_queue
49
56
 
50
57
  # (Array) Tag strings published by agent
51
58
  attr_accessor :tags
@@ -63,6 +70,7 @@ module RightScale
63
70
  :daemonize => false,
64
71
  :console => false,
65
72
  :root_dir => Dir.pwd,
73
+ :mode => :amqp,
66
74
  :time_to_live => 0,
67
75
  :retry_interval => nil,
68
76
  :retry_timeout => nil,
@@ -76,23 +84,25 @@ module RightScale
76
84
  :heartbeat => 0
77
85
  }
78
86
 
79
- # Default block to be activated when finish terminating
80
- DEFAULT_TERMINATE_BLOCK = lambda { EM.stop if EM.reactor_running? }
87
+ # Maximum abnormal termination delay for slowing crash cycling
88
+ MAX_ABNORMAL_TERMINATE_DELAY = 60 * 60
89
+
90
+ # Block to be activated when finish terminating
91
+ TERMINATE_BLOCK = lambda { EM.stop if EM.reactor_running? }
81
92
 
82
- # Initializes a new agent and establishes an AMQP connection.
83
- # This must be used inside EM.run block or if EventMachine reactor
84
- # is already started, for instance, by a Thin server that your Merb/Rails
85
- # application runs on.
93
+ # Initializes a new agent and establishes an HTTP or AMQP RightNet connection
94
+ # This must be used inside an EM.run block unless the EventMachine reactor
95
+ # was already started by the server that this application runs on
86
96
  #
87
97
  # === Parameters
88
98
  # opts(Hash):: Configuration options:
89
- # :identity(String):: Identity of this agent, no default
99
+ # :identity(String):: Identity of this agent; no default
90
100
  # :agent_name(String):: Local name for this agent
91
- # :root_dir(String):: Application root for this agent containing subdirectories actors, certs, and init,
101
+ # :root_dir(String):: Application root for this agent containing subdirectories actors, certs, and init;
92
102
  # defaults to current working directory
93
- # :pid_dir(String):: Path to the directory where the agent stores its process id file (only if daemonized),
103
+ # :pid_dir(String):: Path to the directory where the agent stores its process id file (only if daemonized);
94
104
  # defaults to the current working directory
95
- # :log_dir(String):: Log directory path, defaults to the platform specific log directory
105
+ # :log_dir(String):: Log directory path; defaults to the platform specific log directory
96
106
  # :log_level(Symbol):: The verbosity of logging -- :debug, :info, :warn, :error or :fatal
97
107
  # :actors(Array):: List of actors to load
98
108
  # :console(Boolean):: true indicates to start interactive console
@@ -100,22 +110,22 @@ module RightScale
100
110
  # :retry_interval(Numeric):: Number of seconds between request retries
101
111
  # :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
102
112
  # :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
103
- # by the receiver, 0 means never expire, defaults to 0
104
- # :connect_timeout(Integer):: Number of seconds to wait for a broker connection to be established
105
- # :reconnect_interval(Integer):: Number of seconds between broker reconnect attempts
106
- # :offline_queueing(Boolean):: Whether to queue request if currently not connected to any brokers,
113
+ # by the receiver, 0 means never expire; defaults to 0
114
+ # :connect_timeout(Integer):: Number of seconds to wait for an AMQP broker connection to be established
115
+ # :reconnect_interval(Integer):: Number of seconds between AMQP broker reconnect attempts
116
+ # :offline_queueing(Boolean):: Whether to queue request if currently not connected to RightNet,
107
117
  # also requires agent invocation of Sender initialize_offline_queue and start_offline_queue methods,
108
- # as well as enable_offline_mode and disable_offline_mode as broker connections status changes
109
- # :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping the mapper
110
- # to check connectivity, defaults to 0 meaning do not ping
111
- # :check_interval(Integer):: Number of seconds between publishing stats and checking for broker connections
112
- # that failed during agent launch and then attempting to reconnect via the mapper
118
+ # as well as enable_offline_mode and disable_offline_mode as connection status changes
119
+ # :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping the RightNet
120
+ # router to check connectivity; defaults to 0 meaning do not ping
121
+ # :check_interval(Integer):: Number of seconds between publishing stats and checking for AMQP broker
122
+ # connections that failed during agent launch and then attempting to reconnect
113
123
  # :heartbeat(Integer):: Number of seconds between AMQP connection heartbeats used to keep
114
124
  # connection alive (e.g., when AMQP broker is behind a firewall), nil or 0 means disable
115
125
  # :grace_timeout(Integer):: Maximum number of seconds to wait after last request received before
116
126
  # terminating regardless of whether there are still unfinished requests
117
127
  # :dup_check(Boolean):: Whether to check for and reject duplicate requests, e.g., due to retries
118
- # or redelivery by broker after server failure
128
+ # or redelivery by AMQP broker after server failure
119
129
  # :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for this agent
120
130
  # before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
121
131
  # if the agent crashes. Value 0 means unlimited prefetch.
@@ -123,17 +133,20 @@ module RightScale
123
133
  # exception(Exception):: Exception
124
134
  # message(Packet):: Message being processed
125
135
  # agent(Agent):: Reference to agent
126
- # :ready_callback(Proc):: Called once agent is connected to broker and ready for service (no argument)
136
+ # :ready_callback(Proc):: Called once agent is connected to AMQP broker and ready for service (no argument)
127
137
  # :restart_callback(Proc):: Called on each restart vote with votes being initiated by offline queue
128
- # exceeding MAX_QUEUED_REQUESTS or by repeated failures to access mapper when online (no argument)
129
- # :abnormal_terminate_callback(Proc):: Called at end of termination when terminate abnormally (no argument)
130
- # :services(Symbol):: List of services provided by this agent. Defaults to all methods exposed by actors.
138
+ # exceeding MAX_QUEUED_REQUESTS or by repeated failures to access RightNet when online (no argument)
139
+ # :services(Symbol):: List of services provided by this agent; defaults to all methods exposed by actors
131
140
  # :secure(Boolean):: true indicates to use security features of RabbitMQ to restrict agents to themselves
141
+ # :mode(Symbol):: RightNet communication mode: :http or :amqp; defaults to :amqp
142
+ # :api_url(String):: Domain name for HTTP access to RightApi server
143
+ # :account_id(Integer):: Identifier for account owning this agent
144
+ # :shard_id(Integer):: Identifier for database shard in which this agent is operating
132
145
  # :vhost(String):: AMQP broker virtual host
133
146
  # :user(String):: AMQP broker user
134
147
  # :pass(String):: AMQP broker password
135
148
  # :host(String):: Comma-separated list of AMQP broker hosts; if only one, it is reapplied
136
- # to successive ports; if none, defaults to 'localhost'
149
+ # to successive ports; if none; defaults to 'localhost'
137
150
  # :port(Integer):: Comma-separated list of AMQP broker ports corresponding to hosts; if only one,
138
151
  # it is incremented and applied to successive hosts; if none, defaults to AMQP::HOST
139
152
  #
@@ -151,7 +164,7 @@ module RightScale
151
164
  # Initialize the new agent
152
165
  #
153
166
  # === Parameters
154
- # opts(Hash):: Configuration options per #start above
167
+ # opts(Hash):: Configuration options per start method above
155
168
  #
156
169
  # === Return
157
170
  # true:: Always return true
@@ -160,6 +173,7 @@ module RightScale
160
173
  @tags = []
161
174
  @tags << opts[:tag] if opts[:tag]
162
175
  @tags.flatten!
176
+ @status_callbacks = []
163
177
  @options.freeze
164
178
  @last_stat_reset_time = Time.now
165
179
  reset_agent_stats
@@ -167,6 +181,8 @@ module RightScale
167
181
  end
168
182
 
169
183
  # Put the agent in service
184
+ # This requires making a RightNet connection via HTTP or AMQP
185
+ # and other initialization like loading actors
170
186
  #
171
187
  # === Return
172
188
  # true:: Always return true
@@ -182,7 +198,6 @@ module RightScale
182
198
  t << "- #{k}: #{k.to_s =~ /pass/ ? '****' : (v.respond_to?(:each) ? v.inspect : v)}"
183
199
  end
184
200
  log_opts.each { |l| Log.debug(l) }
185
- terminate_callback = @options[:abnormal_terminate_callback]
186
201
 
187
202
  begin
188
203
  # Capture process id in file after optional daemonize
@@ -192,24 +207,29 @@ module RightScale
192
207
  pid_file.write
193
208
  at_exit { pid_file.remove }
194
209
 
195
- # Initiate AMQP broker connection, wait for connection before proceeding
196
- # otherwise messages published on failed connection will be lost
197
- @broker = RightAMQP::HABrokerClient.new(Serializer.new(:secure), @options)
198
- @queues.each { |s| @remaining_queue_setup[s] = @broker.all }
199
- @broker.connection_status(:one_off => @options[:connect_timeout]) do |status|
200
- if status == :connected
201
- # Need to give EM (on Windows) a chance to respond to the AMQP handshake
202
- # before doing anything interesting to prevent AMQP handshake from
203
- # timing-out; delay post-connected activity a second.
204
- EM.add_timer(1) { start_service(&terminate_callback) }
205
- elsif status == :failed
206
- terminate("failed to connect to any brokers during startup", &terminate_callback)
207
- elsif status == :timeout
208
- terminate("failed to connect to any brokers after #{@options[:connect_timeout]} seconds during startup",
209
- &terminate_callback)
210
- else
211
- terminate("broker connect attempt failed unexpectedly with status #{status} during startup",
212
- &terminate_callback)
210
+ if @mode == :http
211
+ # HTTP is being used for RightNet communication instead of AMQP
212
+ # The code loaded with the actors specific to this application
213
+ # is responsible to call setup_http at the appropriate time
214
+ start_service
215
+ else
216
+ # Initiate AMQP broker connection, wait for connection before proceeding
217
+ # otherwise messages published on failed connection will be lost
218
+ @client = RightAMQP::HABrokerClient.new(Serializer.new(:secure), @options)
219
+ @queues.each { |s| @remaining_queue_setup[s] = @client.all }
220
+ @client.connection_status(:one_off => @options[:connect_timeout]) do |status|
221
+ if status == :connected
222
+ # Need to give EM (on Windows) a chance to respond to the AMQP handshake
223
+ # before doing anything interesting to prevent AMQP handshake from
224
+ # timing-out; delay post-connected activity a second
225
+ EM.add_timer(1) { start_service }
226
+ elsif status == :failed
227
+ terminate("failed to connect to any brokers during startup")
228
+ elsif status == :timeout
229
+ terminate("failed to connect to any brokers after #{@options[:connect_timeout]} seconds during startup")
230
+ else
231
+ terminate("broker connect attempt failed unexpectedly with status #{status} during startup")
232
+ end
213
233
  end
214
234
  end
215
235
  rescue SystemExit
@@ -218,7 +238,7 @@ module RightScale
218
238
  EM.stop if EM.reactor_running?
219
239
  raise
220
240
  rescue Exception => e
221
- terminate("failed startup", e, &terminate_callback)
241
+ terminate("failed startup", e)
222
242
  end
223
243
  true
224
244
  end
@@ -235,7 +255,29 @@ module RightScale
235
255
  @registry.register(actor, prefix)
236
256
  end
237
257
 
238
- # Connect to an additional broker or reconnect it if connection has failed
258
+ # Resource href associated with this agent, if any
259
+ #
260
+ # @return [String, NilClass] href or nil if unknown
261
+ def self_href
262
+ @client.self_href if @client && @mode == :http
263
+ end
264
+
265
+ # Record callback to be notified of agent status changes
266
+ # Multiple callbacks are supported
267
+ #
268
+ # === Block
269
+ # optional block activated when there is a status change with parameters
270
+ # type (Symbol):: Type of client reporting status change: :auth, :api, :router, :broker
271
+ # state (Symbol):: State of client
272
+ #
273
+ # === Return
274
+ # (Hash):: Status of various clients
275
+ def status(&callback)
276
+ @status_callbacks << callback if callback
277
+ @status
278
+ end
279
+
280
+ # Connect to an additional AMQP broker or reconnect it if connection has failed
239
281
  # Subscribe to identity queue on this broker
240
282
  # Update config file if this is a new broker
241
283
  # Assumes already has credentials on this broker and identity queue exists
@@ -255,18 +297,18 @@ module RightScale
255
297
  even_if = " even if already connected" if force
256
298
  Log.info("Connecting to broker at host #{host.inspect} port #{port.inspect} " +
257
299
  "index #{index.inspect} priority #{priority.inspect}#{even_if}")
258
- Log.info("Current broker configuration: #{@broker.status.inspect}")
300
+ Log.info("Current broker configuration: #{@client.status.inspect}")
259
301
  res = nil
260
302
  begin
261
- @broker.connect(host, port, index, priority, force) do |id|
262
- @broker.connection_status(:one_off => @options[:connect_timeout], :brokers => [id]) do |status|
303
+ @client.connect(host, port, index, priority, force) do |id|
304
+ @client.connection_status(:one_off => @options[:connect_timeout], :brokers => [id]) do |status|
263
305
  begin
264
306
  if status == :connected
265
307
  setup_queues([id])
266
308
  remaining = 0
267
309
  @remaining_queue_setup.each_value { |ids| remaining += ids.size }
268
310
  Log.info("[setup] Finished subscribing to queues after reconnecting to broker #{id}") if remaining == 0
269
- unless update_configuration(:host => @broker.hosts, :port => @broker.ports)
311
+ unless update_configuration(:host => @client.hosts, :port => @client.ports)
270
312
  Log.warning("Successfully connected to broker #{id} but failed to update config file")
271
313
  end
272
314
  else
@@ -286,7 +328,7 @@ module RightScale
286
328
  res
287
329
  end
288
330
 
289
- # Disconnect from a broker and optionally remove it from the configuration
331
+ # Disconnect from an AMQP broker and optionally remove it from the configuration
290
332
  # Refuse to do so if it is the last connected broker
291
333
  #
292
334
  # === Parameters
@@ -300,23 +342,23 @@ module RightScale
300
342
  def disconnect(host, port, remove = false)
301
343
  and_remove = " and removing" if remove
302
344
  Log.info("Disconnecting#{and_remove} broker at host #{host.inspect} port #{port.inspect}")
303
- Log.info("Current broker configuration: #{@broker.status.inspect}")
345
+ Log.info("Current broker configuration: #{@client.status.inspect}")
304
346
  id = RightAMQP::HABrokerClient.identity(host, port)
305
- @connect_request_stats.update("disconnect #{@broker.alias_(id)}")
306
- connected = @broker.connected
347
+ @connect_request_stats.update("disconnect #{@client.alias_(id)}")
348
+ connected = @client.connected
307
349
  res = nil
308
350
  if connected.include?(id) && connected.size == 1
309
351
  res = "Not disconnecting from #{id} because it is the last connected broker for this agent"
310
- elsif @broker.get(id)
352
+ elsif @client.get(id)
311
353
  begin
312
354
  if remove
313
- @broker.remove(host, port) do |id|
314
- unless update_configuration(:host => @broker.hosts, :port => @broker.ports)
355
+ @client.remove(host, port) do |id|
356
+ unless update_configuration(:host => @client.hosts, :port => @client.ports)
315
357
  res = "Successfully disconnected from broker #{id} but failed to update config file"
316
358
  end
317
359
  end
318
360
  else
319
- @broker.close_one(id)
361
+ @client.close_one(id)
320
362
  end
321
363
  rescue Exception => e
322
364
  res = Log.format("Failed to disconnect from broker #{id}", e)
@@ -329,7 +371,7 @@ module RightScale
329
371
  res
330
372
  end
331
373
 
332
- # There were problems while setting up service for this agent on the given brokers,
374
+ # There were problems while setting up service for this agent on the given AMQP brokers,
333
375
  # so mark these brokers as failed if not currently connected and later, during the
334
376
  # periodic status check, attempt to reconnect
335
377
  #
@@ -339,16 +381,16 @@ module RightScale
339
381
  # === Return
340
382
  # res(String|nil):: Error message if failed, otherwise nil
341
383
  def connect_failed(ids)
342
- aliases = @broker.aliases(ids).join(", ")
384
+ aliases = @client.aliases(ids).join(", ")
343
385
  @connect_request_stats.update("enroll failed #{aliases}")
344
386
  res = nil
345
387
  begin
346
388
  Log.info("Received indication that service initialization for this agent for brokers #{ids.inspect} has failed")
347
- connected = @broker.connected
389
+ connected = @client.connected
348
390
  ignored = connected & ids
349
391
  Log.info("Not marking brokers #{ignored.inspect} as unusable because currently connected") if ignored
350
- Log.info("Current broker configuration: #{@broker.status.inspect}")
351
- @broker.declare_unusable(ids - ignored)
392
+ Log.info("Current broker configuration: #{@client.status.inspect}")
393
+ @client.declare_unusable(ids - ignored)
352
394
  rescue Exception => e
353
395
  res = Log.format("Failed handling broker connection failure indication for #{ids.inspect}", e)
354
396
  Log.error(res)
@@ -357,6 +399,28 @@ module RightScale
357
399
  res
358
400
  end
359
401
 
402
+ # Update agent's persisted configuration
403
+ # Note that @options are frozen and therefore not updated
404
+ #
405
+ # === Parameters
406
+ # opts(Hash):: Options being updated
407
+ #
408
+ # === Return
409
+ # (Boolean):: true if successful, otherwise false
410
+ def update_configuration(opts)
411
+ if (cfg = AgentConfig.load_cfg(@agent_name))
412
+ opts.each { |k, v| cfg[k] = v }
413
+ AgentConfig.store_cfg(@agent_name, cfg)
414
+ true
415
+ else
416
+ Log.error("Could not access configuration file #{AgentConfig.cfg_file(@agent_name).inspect} for update")
417
+ false
418
+ end
419
+ rescue Exception => e
420
+ Log.error("Failed updating configuration file #{AgentConfig.cfg_file(@agent_name).inspect}", e, :trace)
421
+ false
422
+ end
423
+
360
424
  # Gracefully terminate execution by allowing unfinished tasks to complete
361
425
  # Immediately terminate if called a second time
362
426
  # Report reason for termination if it is abnormal
@@ -365,35 +429,39 @@ module RightScale
365
429
  # reason(String):: Reason for abnormal termination, if any
366
430
  # exception(Exception|String):: Exception or other parenthetical error information, if any
367
431
  #
368
- # === Block
369
- # Optional block to be executed after termination is complete
370
- #
371
432
  # === Return
372
433
  # true:: Always return true
373
- def terminate(reason = nil, exception = nil, &block)
374
- block ||= DEFAULT_TERMINATE_BLOCK
434
+ def terminate(reason = nil, exception = nil)
375
435
  begin
376
436
  @history.update("stop") if @history
377
437
  Log.error("[stop] Terminating because #{reason}", exception, :trace) if reason
378
- if @terminating || @broker.nil?
438
+ if exception.is_a?(Exception)
439
+ h = @history.analyze_service
440
+ if h[:last_crashed]
441
+ delay = [(Time.now.to_i - h[:last_crash_time]) * 2, MAX_ABNORMAL_TERMINATE_DELAY].min
442
+ Log.info("[stop] Delaying termination for #{RightSupport::Stats.elapsed(delay)} to slow crash cycling")
443
+ sleep(delay)
444
+ end
445
+ end
446
+ if @terminating || @client.nil?
379
447
  @terminating = true
380
448
  @termination_timer.cancel if @termination_timer
381
449
  @termination_timer = nil
382
450
  Log.info("[stop] Terminating immediately")
383
- block.call
384
- @history.update("graceful exit") if @history && @broker.nil?
451
+ @terminate_callback.call
452
+ @history.update("graceful exit") if @history && @client.nil?
385
453
  else
386
454
  @terminating = true
387
455
  @check_status_timer.cancel if @check_status_timer
388
456
  @check_status_timer = nil
389
457
  Log.info("[stop] Agent #{@identity} terminating")
390
- stop_gracefully(@options[:grace_timeout], &block)
458
+ stop_gracefully(@options[:grace_timeout])
391
459
  end
392
460
  rescue SystemExit
393
461
  raise
394
462
  rescue Exception => e
395
463
  Log.error("Failed to terminate gracefully", e, :trace)
396
- begin block.call; rescue Exception; end
464
+ begin @terminate_callback.call; rescue Exception; end
397
465
  end
398
466
  true
399
467
  end
@@ -415,7 +483,6 @@ module RightScale
415
483
  "hostname" => Socket.gethostname,
416
484
  "memory" => Platform.process.resident_set_size,
417
485
  "version" => AgentConfig.protocol_version,
418
- "brokers" => @broker.stats(reset),
419
486
  "agent stats" => agent_stats(reset),
420
487
  "receive stats" => dispatcher_stats(reset),
421
488
  "send stats" => @sender.stats(reset),
@@ -425,6 +492,11 @@ module RightScale
425
492
  "machine uptime" => Platform.shell.uptime
426
493
  }
427
494
  stats["revision"] = @revision if @revision
495
+ if @mode == :http
496
+ stats.merge!(@client.stats(reset))
497
+ else
498
+ stats["broker"] = @client.stats(reset)
499
+ end
428
500
  result = OperationResult.success(stats)
429
501
  @last_stat_reset_time = now if reset
430
502
  result
@@ -439,13 +511,13 @@ module RightScale
439
511
  #
440
512
  # === Return
441
513
  # stats(Hash):: Current statistics:
442
- # "connect requests"(Hash|nil):: Stats about requests to update connections with keys "total", "percent",
514
+ # "connect requests"(Hash|nil):: Stats about requests to update AMQP broker connections with keys "total", "percent",
443
515
  # and "last" with percentage breakdown by "connects: <alias>", "disconnects: <alias>", "enroll setup failed:
444
516
  # <aliases>", or nil if none
445
517
  # "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
446
518
  # "total"(Integer):: Total exceptions for this category
447
519
  # "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
448
- # "non-deliveries"(Hash):: Message non-delivery activity stats with keys "total", "percent", "last", and "rate"
520
+ # "non-deliveries"(Hash):: AMQP message non-delivery activity stats with keys "total", "percent", "last", and "rate"
449
521
  # with percentage breakdown by request type, or nil if none
450
522
  # "request failures"(Hash|nil):: Request dispatch failure activity stats with keys "total", "percent", "last", and "rate"
451
523
  # with percentage breakdown per failure type, or nil if none
@@ -453,12 +525,14 @@ module RightScale
453
525
  # with percentage breakdown per failure type, or nil if none
454
526
  def agent_stats(reset = false)
455
527
  stats = {
456
- "connect requests" => @connect_request_stats.all,
457
528
  "exceptions" => @exception_stats.stats,
458
- "non-deliveries" => @non_delivery_stats.all,
459
529
  "request failures" => @request_failure_stats.all,
460
530
  "response failures" => @response_failure_stats.all
461
531
  }
532
+ unless @mode == :http
533
+ stats["connect requests"] = @connect_request_stats.all
534
+ stats["non-deliveries"] = @non_delivery_stats.all
535
+ end
462
536
  reset_agent_stats if reset
463
537
  stats
464
538
  end
@@ -504,58 +578,46 @@ module RightScale
504
578
  FileUtils.mkdir_p(@options[:log_path]) unless File.directory?(@options[:log_path])
505
579
  end
506
580
 
581
+ @options[:async_response] = true unless @options.has_key?(:async_response)
582
+
507
583
  @identity = @options[:identity]
508
584
  parsed_identity = AgentIdentity.parse(@identity)
509
585
  @agent_type = parsed_identity.agent_type
510
586
  @agent_name = @options[:agent_name]
587
+ @request_queue = "request"
588
+ @request_queue << "-#{@options[:shard_id].to_i}" if @options[:shard_id].to_i != 0
589
+ @mode = @options[:mode].to_sym
511
590
  @stats_routing_key = "stats.#{@agent_type}.#{parsed_identity.base_id}"
591
+ @terminate_callback = TERMINATE_BLOCK
592
+ @exception_callback = @options[:exception_callback]
512
593
  @revision = revision
513
594
  @queues = [@identity]
514
595
  @remaining_queue_setup = {}
515
596
  @history = History.new(@identity)
516
597
  end
517
598
 
518
- # Update agent's persisted configuration
519
- # Note that @options are frozen and therefore not updated
520
- #
521
- # === Parameters
522
- # opts(Hash):: Options being updated
523
- #
524
- # === Return
525
- # (Boolean):: true if successful, otherwise false
526
- def update_configuration(opts)
527
- if cfg = AgentConfig.load_cfg(@agent_name)
528
- opts.each { |k, v| cfg[k] = v }
529
- AgentConfig.store_cfg(@agent_name, cfg)
530
- true
531
- else
532
- Log.error("Could not access configuration file #{AgentConfig.cfg_file(@agent_name).inspect} for update")
533
- false
534
- end
535
- rescue Exception => e
536
- Log.error("Failed updating configuration file #{AgentConfig.cfg_file(@agent_name).inspect}", e, :trace)
537
- false
538
- end
539
-
540
- # Start service now that connected to at least one broker
541
- #
542
- # === Block
543
- # Optional block to be executed if terminate abnormally
599
+ # Start service
544
600
  #
545
601
  # === Return
546
602
  # true:: Always return true
547
- def start_service(&terminate_callback)
603
+ def start_service
548
604
  begin
549
605
  @registry = ActorRegistry.new
550
606
  @dispatchers = create_dispatchers
607
+ # Creating sender now but for HTTP mode it is not really usable until setup_http
608
+ # is called by the code loaded for this application in load_actors
551
609
  @sender = create_sender
552
610
  load_actors
553
611
  setup_traps
554
- setup_non_delivery
555
- setup_queues
612
+ setup_status
613
+ unless @mode == :http
614
+ setup_non_delivery
615
+ setup_queues
616
+ end
556
617
  @history.update("run")
557
618
  start_console if @options[:console] && !@options[:daemonize]
558
619
  EM.next_tick { @options[:ready_callback].call } if @options[:ready_callback]
620
+ EM.defer { @client.listen(nil) { |e| handle_event(e) } } if @mode == :http
559
621
 
560
622
  # Need to keep reconnect interval at least :connect_timeout in size,
561
623
  # otherwise connection_status callback will not timeout prior to next
@@ -565,19 +627,87 @@ module RightScale
565
627
  rescue SystemExit
566
628
  raise
567
629
  rescue Exception => e
568
- terminate("failed startup after connecting to a broker", e, &terminate_callback)
630
+ terminate("failed service startup", e)
569
631
  end
570
632
  true
571
633
  end
572
634
 
635
+ # Handle events received by this agent
636
+ #
637
+ # === Parameters
638
+ # event(Hash):: Event received
639
+ #
640
+ # === Return
641
+ # nil:: Always return nil indicating no response since handled separately via notify
642
+ def handle_event(event)
643
+ if event.is_a?(Hash)
644
+ if ["Push", "Request"].include?(event[:type])
645
+ # Use next_tick to ensure that on main reactor thread
646
+ # so that any data access is thread safe
647
+ EM.next_tick do
648
+ begin
649
+ if (result = @dispatcher.dispatch(event_to_packet(event))) && event[:type] == "Request"
650
+ @client.notify(result_to_event(result), [result.to])
651
+ end
652
+ rescue Exception => e
653
+ Log.error("Failed sending response for <#{event[:uuid]}>", e, :trace)
654
+ end
655
+ end
656
+ else
657
+ Log.error("Unrecognized event type #{event[:type]} from #{event[:from]}")
658
+ end
659
+ else
660
+ Log.error("Unrecognized event: #{event.class}")
661
+ end
662
+ nil
663
+ end
664
+
665
+ # Convert event hash to packet
666
+ #
667
+ # === Parameters
668
+ # event(Hash):: Event to be converted
669
+ #
670
+ # === Return
671
+ # (Push|Request):: Packet
672
+ def event_to_packet(event)
673
+ packet = nil
674
+ case event[:type]
675
+ when "Push"
676
+ packet = RightScale::Push.new(event[:path], event[:data], {:from => event[:from], :token => event[:uuid]})
677
+ packet.expires_at = event[:expires_at].to_i if event.has_key?(:expires_at)
678
+ when "Request"
679
+ options = {:from => event[:from], :token => event[:uuid], :reply_to => event[:reply_to], :tries => event[:tries]}
680
+ packet = RightScale::Request.new(event[:path], event[:data], options)
681
+ packet.expires_at = event[:expires_at].to_i if event.has_key?(:expires_at)
682
+ end
683
+ packet
684
+ end
685
+
686
+ # Convert result packet to event
687
+ #
688
+ # === Parameters
689
+ # result(Result):: Event to be converted
690
+ #
691
+ # === Return
692
+ # (Hash):: Event
693
+ def result_to_event(result)
694
+ { :type => "Result",
695
+ :from => result.from,
696
+ :data => {
697
+ :result => result.results,
698
+ :duration => result.duration,
699
+ :request_uuid => result.token,
700
+ :request_from => result.request_from } }
701
+ end
702
+
573
703
  # Create dispatcher per queue for use in handling incoming requests
574
704
  #
575
705
  # === Return
576
706
  # [Hash]:: Dispatchers with queue name as key
577
707
  def create_dispatchers
578
708
  cache = DispatchedCache.new(@identity) if @options[:dup_check]
579
- dispatcher = Dispatcher.new(self, cache)
580
- @queues.inject({}) { |dispatchers, queue| dispatchers[queue] = dispatcher; dispatchers }
709
+ @dispatcher = Dispatcher.new(self, cache)
710
+ @queues.inject({}) { |dispatchers, queue| dispatchers[queue] = @dispatcher; dispatchers }
581
711
  end
582
712
 
583
713
  # Create manager for outgoing requests
@@ -609,7 +739,7 @@ module RightScale
609
739
  Log.error("Actors #{actors.inspect} not found in #{actors_dirs.inspect}") unless actors.empty?
610
740
 
611
741
  # Perform agent-specific initialization including actor creation and registration
612
- if init_file = AgentConfig.init_file
742
+ if (init_file = AgentConfig.init_file)
613
743
  Log.info("[setup] initializing agent from #{init_file}")
614
744
  instance_eval(File.read(init_file), init_file)
615
745
  else
@@ -618,6 +748,24 @@ module RightScale
618
748
  true
619
749
  end
620
750
 
751
+ # Create client for HTTP-based RightNet communication
752
+ # The code loaded with the actors specific to this application
753
+ # is responsible for calling this function
754
+ #
755
+ # === Parameters
756
+ # auth_client(AuthClient):: Authorization client to be used by this agent
757
+ #
758
+ # === Return
759
+ # true:: Always return true
760
+ def setup_http(auth_client)
761
+ @auth_client = auth_client
762
+ if @mode == :http
763
+ RightHttpClient.init(@auth_client, @options.merge(:retry_enabled => true))
764
+ @client = RightHttpClient.instance
765
+ end
766
+ true
767
+ end
768
+
621
769
  # Setup signal traps
622
770
  #
623
771
  # === Return
@@ -628,7 +776,7 @@ module RightScale
628
776
  EM.next_tick do
629
777
  begin
630
778
  terminate do
631
- DEFAULT_TERMINATE_BLOCK.call
779
+ TERMINATE_BLOCK.call
632
780
  old.call if old.is_a? Proc
633
781
  end
634
782
  rescue Exception => e
@@ -640,12 +788,28 @@ module RightScale
640
788
  true
641
789
  end
642
790
 
791
+ # Setup client status collection
792
+ #
793
+ # === Return
794
+ # true:: Always return true
795
+ def setup_status
796
+ @status = {}
797
+ if @mode == :http
798
+ @status = @client.status { |type, state| update_status(type, state) }.dup
799
+ else
800
+ @client.connection_status { |state| update_status(:broker, state) }
801
+ @status[:broker] = :connected
802
+ @status[:auth] = @auth_client.status { |type, state| update_status(type, state) } if @auth_client
803
+ end
804
+ true
805
+ end
806
+
643
807
  # Setup non-delivery handler
644
808
  #
645
809
  # === Return
646
810
  # true:: Always return true
647
811
  def setup_non_delivery
648
- @broker.non_delivery do |reason, type, token, from, to|
812
+ @client.non_delivery do |reason, type, token, from, to|
649
813
  begin
650
814
  @non_delivery_stats.update(type)
651
815
  reason = case reason
@@ -687,7 +851,7 @@ module RightScale
687
851
  queue = {:name => name, :options => {:durable => true, :no_declare => @options[:secure]}}
688
852
  filter = [:from, :tags, :tries, :persistent]
689
853
  options = {:ack => true, Push => filter, Request => filter, Result => [:from], :brokers => ids}
690
- @broker.subscribe(queue, nil, options) { |_, packet, header| handle_packet(name, packet, header) }
854
+ @client.subscribe(queue, nil, options) { |_, packet, header| handle_packet(name, packet, header) }
691
855
  end
692
856
 
693
857
  # Handle packet from queue
@@ -732,7 +896,7 @@ module RightScale
732
896
  if (dispatcher = @dispatchers[queue])
733
897
  if (result = dispatcher.dispatch(request))
734
898
  exchange = {:type => :queue, :name => request.reply_to, :options => {:durable => true, :no_declare => @options[:secure]}}
735
- @broker.publish(exchange, result, :persistent => true, :mandatory => true, :log_filter => [:request_from, :tries, :persistent, :duration])
899
+ @client.publish(exchange, result, :persistent => true, :mandatory => true, :log_filter => [:request_from, :tries, :persistent, :duration])
736
900
  end
737
901
  else
738
902
  Log.error("Failed to dispatch request #{request.trace} from queue #{queue} because no dispatcher configured")
@@ -773,14 +937,36 @@ module RightScale
773
937
  # === Return
774
938
  # true:: Always return true
775
939
  def finish_setup
776
- @broker.failed.each do |id|
940
+ @client.failed.each do |id|
777
941
  p = {:agent_identity => @identity}
778
- p[:host], p[:port], p[:id], p[:priority] = @broker.identity_parts(id)
942
+ p[:host], p[:port], p[:id], p[:priority] = @client.identity_parts(id)
779
943
  @sender.send_push("/registrar/connect", p)
780
944
  end
781
945
  true
782
946
  end
783
947
 
948
+ # Forward status updates via callbacks
949
+ #
950
+ # === Parameters
951
+ # type (Symbol):: Type of client: :auth, :api, :router, or :broker
952
+ # state (Symbol):: State of client
953
+ #
954
+ # === Return
955
+ # true:: Always return true
956
+ def update_status(type, state)
957
+ old_state, @status[type] = @status[type], state
958
+ Log.info("Client #{type.inspect} changed state from #{old_state.inspect} to #{state.inspect}")
959
+ @status_callbacks.each do |callback|
960
+ begin
961
+ callback.call(type, state)
962
+ rescue RuntimeError => e
963
+ Log.error("Failed status callback", e)
964
+ @exception_stats.track("update status", e)
965
+ end
966
+ end
967
+ true
968
+ end
969
+
784
970
  # Setup periodic status check
785
971
  #
786
972
  # === Parameters
@@ -790,7 +976,7 @@ module RightScale
790
976
  # true:: Always return true
791
977
  def setup_status_checks(interval)
792
978
  @check_status_count = 0
793
- @check_status_brokers = @broker.all
979
+ @check_status_brokers = @client.all unless @mode == :http
794
980
  @check_status_timer = EM::PeriodicTimer.new(interval) { check_status }
795
981
  true
796
982
  end
@@ -806,25 +992,33 @@ module RightScale
806
992
  # true:: Always return true
807
993
  def check_status
808
994
  begin
809
- finish_setup unless @terminating
995
+ if @auth_client && @auth_client.mode != @mode
996
+ Log.info("Detected request to switch mode from #{@mode} to #{@auth_client.mode}")
997
+ update_status(:auth, :failed)
998
+ end
999
+ rescue Exception => e
1000
+ Log.error("Failed switching mode", e)
1001
+ @exception_stats.track("check status", e)
1002
+ end
1003
+
1004
+ begin
1005
+ finish_setup unless @terminating || @mode == :http
810
1006
  rescue Exception => e
811
1007
  Log.error("Failed finishing setup", e)
812
1008
  @exception_stats.track("check status", e)
813
1009
  end
814
1010
 
815
1011
  begin
816
- @broker.queue_status(@queues, timeout = @options[:check_interval] / 10) unless @terminating
1012
+ @client.queue_status(@queues, timeout = @options[:check_interval] / 10) unless @terminating || @mode == :http
817
1013
  rescue Exception => e
818
1014
  Log.error("Failed checking queue status", e)
819
1015
  @exception_stats.track("check status", e)
820
1016
  end
821
1017
 
822
1018
  begin
823
- if @stats_routing_key && !@terminating
824
- exchange = {:type => :topic, :name => "stats", :options => {:no_declare => true}}
825
- @broker.publish(exchange, Stats.new(stats.content, @identity), :no_log => true,
826
- :routing_key => @stats_routing_key, :brokers => @check_status_brokers.rotate!)
827
- end
1019
+ publish_stats unless @terminating || @stats_routing_key.nil?
1020
+ rescue Exceptions::ConnectivityFailure => e
1021
+ Log.error("Failed publishing stats", e, :no_trace)
828
1022
  rescue Exception => e
829
1023
  Log.error("Failed publishing stats", e)
830
1024
  @exception_stats.track("check status", e)
@@ -841,6 +1035,22 @@ module RightScale
841
1035
  true
842
1036
  end
843
1037
 
1038
+ # Publish current stats
1039
+ #
1040
+ # === Return
1041
+ # true:: Always return true
1042
+ def publish_stats
1043
+ s = stats({}).content
1044
+ if @mode == :http
1045
+ @client.notify({:type => "Stats", :from => @identity, :data => s}, nil)
1046
+ else
1047
+ exchange = {:type => :topic, :name => "stats", :options => {:no_declare => true}}
1048
+ @client.publish(exchange, Stats.new(s, @identity), :no_log => true,
1049
+ :routing_key => @stats_routing_key, :brokers => @check_status_brokers.rotate!)
1050
+ end
1051
+ true
1052
+ end
1053
+
844
1054
  # Allow derived classes to perform any other useful periodic checks
845
1055
  #
846
1056
  # === Parameters
@@ -865,19 +1075,21 @@ module RightScale
865
1075
  end
866
1076
 
867
1077
  # Gracefully stop processing
1078
+ # Close clients except for authorization
868
1079
  #
869
1080
  # === Parameters
870
1081
  # timeout(Integer):: Maximum number of seconds to wait after last request received before
871
1082
  # terminating regardless of whether there are still unfinished requests
872
1083
  #
873
- # === Block
874
- # Optional block to be executed after stopping message processing wherever possible
875
- #
876
1084
  # === Return
877
1085
  # true:: Always return true
878
- def stop_gracefully(timeout, &block)
879
- @broker.unusable.each { |id| @broker.close_one(id, propagate = false) }
880
- finish_terminating(timeout, &block)
1086
+ def stop_gracefully(timeout)
1087
+ if @mode == :http
1088
+ @client.close
1089
+ else
1090
+ @client.unusable.each { |id| @client.close_one(id, propagate = false) }
1091
+ end
1092
+ finish_terminating(timeout)
881
1093
  end
882
1094
 
883
1095
  # Finish termination after all requests have been processed
@@ -886,12 +1098,9 @@ module RightScale
886
1098
  # timeout(Integer):: Maximum number of seconds to wait after last request received before
887
1099
  # terminating regardless of whether there are still unfinished requests
888
1100
  #
889
- # === Block
890
- # Optional block to be executed after stopping message processing wherever possible
891
- #
892
1101
  # === Return
893
1102
  # true:: Always return true
894
- def finish_terminating(timeout, &block)
1103
+ def finish_terminating(timeout)
895
1104
  if @sender
896
1105
  request_count, request_age = @sender.terminate
897
1106
 
@@ -899,7 +1108,11 @@ module RightScale
899
1108
  request_count, request_age = @sender.terminate
900
1109
  Log.info("[stop] The following #{request_count} requests initiated as recently as #{request_age} " +
901
1110
  "seconds ago are being dropped:\n " + @sender.dump_requests.join("\n ")) if request_age
902
- @broker.close { block.call }
1111
+ if @mode == :http
1112
+ @terminate_callback.call
1113
+ else
1114
+ @client.close { @terminate_callback.call }
1115
+ end
903
1116
  end
904
1117
 
905
1118
  if (wait_time = [timeout - (request_age || timeout), 0].max) > 0
@@ -911,14 +1124,14 @@ module RightScale
911
1124
  finish.call
912
1125
  rescue Exception => e
913
1126
  Log.error("Failed while finishing termination", e, :trace)
914
- begin block.call; rescue Exception; end
1127
+ begin @terminate_callback.call; rescue Exception; end
915
1128
  end
916
1129
  end
917
1130
  else
918
1131
  finish.call
919
1132
  end
920
1133
  else
921
- block.call
1134
+ @terminate_callback.call
922
1135
  end
923
1136
  @history.update("graceful exit")
924
1137
  true