right_agent 1.0.1 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|