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.
- data/README.rdoc +10 -8
- data/Rakefile +31 -5
- data/lib/right_agent.rb +6 -1
- data/lib/right_agent/actor.rb +4 -20
- data/lib/right_agent/actors/agent_manager.rb +1 -1
- data/lib/right_agent/agent.rb +357 -144
- data/lib/right_agent/agent_config.rb +7 -6
- data/lib/right_agent/agent_identity.rb +13 -11
- data/lib/right_agent/agent_tag_manager.rb +60 -64
- data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
- data/lib/right_agent/clients/api_client.rb +383 -0
- data/lib/right_agent/clients/auth_client.rb +247 -0
- data/lib/right_agent/clients/balanced_http_client.rb +369 -0
- data/lib/right_agent/clients/base_retry_client.rb +495 -0
- data/lib/right_agent/clients/right_http_client.rb +279 -0
- data/lib/right_agent/clients/router_client.rb +493 -0
- data/lib/right_agent/command/command_io.rb +4 -4
- data/lib/right_agent/command/command_parser.rb +2 -2
- data/lib/right_agent/command/command_runner.rb +1 -1
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
- data/lib/right_agent/dispatcher.rb +12 -10
- data/lib/right_agent/enrollment_result.rb +16 -12
- data/lib/right_agent/exceptions.rb +34 -20
- data/lib/right_agent/history.rb +10 -5
- data/lib/right_agent/log.rb +5 -5
- data/lib/right_agent/minimal.rb +1 -0
- data/lib/right_agent/multiplexer.rb +1 -1
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/packets.rb +7 -7
- data/lib/right_agent/payload_formatter.rb +1 -1
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/platform.rb +1 -1
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
- data/lib/right_agent/scripts/agent_controller.rb +28 -26
- data/lib/right_agent/scripts/agent_deployer.rb +37 -22
- data/lib/right_agent/scripts/common_parser.rb +10 -3
- data/lib/right_agent/secure_identity.rb +1 -1
- data/lib/right_agent/sender.rb +299 -785
- data/lib/right_agent/serialize/secure_serializer.rb +3 -1
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
- data/lib/right_agent/serialize/serializable.rb +8 -3
- data/right_agent.gemspec +49 -18
- data/spec/agent_config_spec.rb +7 -7
- data/spec/agent_identity_spec.rb +7 -4
- data/spec/agent_spec.rb +43 -7
- data/spec/agent_tag_manager_spec.rb +72 -83
- data/spec/clients/api_client_spec.rb +423 -0
- data/spec/clients/auth_client_spec.rb +272 -0
- data/spec/clients/balanced_http_client_spec.rb +576 -0
- data/spec/clients/base_retry_client_spec.rb +635 -0
- data/spec/clients/router_client_spec.rb +594 -0
- data/spec/clients/spec_helper.rb +111 -0
- data/spec/command/command_io_spec.rb +1 -1
- data/spec/command/command_parser_spec.rb +1 -1
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/dispatcher_spec.rb +3 -2
- data/spec/enrollment_result_spec.rb +2 -2
- data/spec/history_spec.rb +51 -39
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
- data/spec/sender_spec.rb +835 -1052
- data/spec/serialize/secure_serializer_spec.rb +3 -2
- data/spec/spec_helper.rb +54 -1
- metadata +71 -12
data/README.rdoc
CHANGED
@@ -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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/right_agent.rb
CHANGED
@@ -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, '
|
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'))
|
data/lib/right_agent/actor.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2009-
|
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
|
183
|
-
Sender.instance.
|
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
|
-
:
|
56
|
+
:client => @agent.client.status,
|
57
57
|
:time => Time.now.to_i)
|
58
58
|
end
|
59
59
|
|
data/lib/right_agent/agent.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2009-
|
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
|
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)
|
48
|
-
attr_reader :
|
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
|
-
#
|
80
|
-
|
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
|
84
|
-
#
|
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
|
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
|
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
|
104
|
-
# :connect_timeout(Integer):: Number of seconds to wait for
|
105
|
-
# :reconnect_interval(Integer):: Number of seconds between broker reconnect attempts
|
106
|
-
# :offline_queueing(Boolean):: Whether to queue request if currently not connected to
|
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
|
109
|
-
# :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping the
|
110
|
-
# to check connectivity
|
111
|
-
# :check_interval(Integer):: Number of seconds between publishing stats and checking for broker
|
112
|
-
# that failed during agent launch and then attempting to reconnect
|
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
|
129
|
-
# :
|
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
|
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
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
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
|
-
#
|
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: #{@
|
300
|
+
Log.info("Current broker configuration: #{@client.status.inspect}")
|
259
301
|
res = nil
|
260
302
|
begin
|
261
|
-
@
|
262
|
-
@
|
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 => @
|
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
|
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: #{@
|
345
|
+
Log.info("Current broker configuration: #{@client.status.inspect}")
|
304
346
|
id = RightAMQP::HABrokerClient.identity(host, port)
|
305
|
-
@connect_request_stats.update("disconnect #{@
|
306
|
-
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 @
|
352
|
+
elsif @client.get(id)
|
311
353
|
begin
|
312
354
|
if remove
|
313
|
-
@
|
314
|
-
unless update_configuration(:host => @
|
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
|
-
@
|
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 = @
|
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 = @
|
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: #{@
|
351
|
-
@
|
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
|
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
|
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
|
-
|
384
|
-
@history.update("graceful exit") if @history && @
|
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]
|
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
|
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)::
|
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
|
-
#
|
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
|
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
|
-
|
555
|
-
|
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
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
940
|
+
@client.failed.each do |id|
|
777
941
|
p = {:agent_identity => @identity}
|
778
|
-
p[:host], p[:port], p[:id], p[:priority] = @
|
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 = @
|
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
|
-
|
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
|
-
@
|
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
|
-
|
824
|
-
|
825
|
-
|
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
|
879
|
-
|
880
|
-
|
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
|
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
|
-
@
|
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
|
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
|
-
|
1134
|
+
@terminate_callback.call
|
922
1135
|
end
|
923
1136
|
@history.update("graceful exit")
|
924
1137
|
true
|