right_agent 1.0.1 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
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