right_agent 2.0.7-x86-mingw32
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/LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +113 -0
- data/lib/right_agent.rb +59 -0
- data/lib/right_agent/actor.rb +182 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +232 -0
- data/lib/right_agent/agent.rb +1149 -0
- data/lib/right_agent/agent_config.rb +480 -0
- data/lib/right_agent/agent_identity.rb +210 -0
- data/lib/right_agent/agent_tag_manager.rb +237 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/clients.rb +31 -0
- 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.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +150 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +33 -0
- data/lib/right_agent/command/command_io.rb +126 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +118 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +44 -0
- data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
- data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
- data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
- data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
- data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
- data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +79 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
- data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
- data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
- data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
- data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
- data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
- data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
- data/lib/right_agent/daemonize.rb +35 -0
- data/lib/right_agent/dispatched_cache.rb +109 -0
- data/lib/right_agent/dispatcher.rb +272 -0
- data/lib/right_agent/enrollment_result.rb +221 -0
- data/lib/right_agent/exceptions.rb +87 -0
- data/lib/right_agent/history.rb +145 -0
- data/lib/right_agent/log.rb +460 -0
- data/lib/right_agent/minimal.rb +46 -0
- data/lib/right_agent/monkey_patches.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
- data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
- data/lib/right_agent/multiplexer.rb +102 -0
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/operation_result.rb +300 -0
- data/lib/right_agent/packets.rb +673 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +770 -0
- data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
- data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
- data/lib/right_agent/platform/unix/platform.rb +226 -0
- data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
- data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
- data/lib/right_agent/platform/windows/platform.rb +1808 -0
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/retryable_request.rb +195 -0
- data/lib/right_agent/scripts/agent_controller.rb +543 -0
- data/lib/right_agent/scripts/agent_deployer.rb +400 -0
- data/lib/right_agent/scripts/common_parser.rb +160 -0
- data/lib/right_agent/scripts/log_level_manager.rb +192 -0
- data/lib/right_agent/scripts/stats_manager.rb +268 -0
- data/lib/right_agent/scripts/usage.rb +58 -0
- data/lib/right_agent/secure_identity.rb +92 -0
- data/lib/right_agent/security.rb +32 -0
- data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
- data/lib/right_agent/security/certificate.rb +102 -0
- data/lib/right_agent/security/certificate_cache.rb +89 -0
- data/lib/right_agent/security/distinguished_name.rb +56 -0
- data/lib/right_agent/security/encrypted_document.rb +83 -0
- data/lib/right_agent/security/rsa_key_pair.rb +76 -0
- data/lib/right_agent/security/signature.rb +86 -0
- data/lib/right_agent/security/static_certificate_store.rb +85 -0
- data/lib/right_agent/sender.rb +792 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +107 -0
- data/lib/right_agent/serialize/secure_serializer.rb +151 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +151 -0
- data/lib/right_agent/serialize/serializer.rb +159 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +101 -0
- data/spec/actor_registry_spec.rb +80 -0
- data/spec/actor_spec.rb +162 -0
- data/spec/agent_config_spec.rb +235 -0
- data/spec/agent_identity_spec.rb +78 -0
- data/spec/agent_spec.rb +734 -0
- data/spec/agent_tag_manager_spec.rb +319 -0
- 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/agent_manager_commands_spec.rb +51 -0
- data/spec/command/command_io_spec.rb +93 -0
- data/spec/command/command_parser_spec.rb +79 -0
- data/spec/command/command_runner_spec.rb +107 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/dev_repository_spec.rb +33 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
- data/spec/core_payload_types/login_user_spec.rb +102 -0
- data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatched_cache_spec.rb +136 -0
- data/spec/dispatcher_spec.rb +324 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/history_spec.rb +246 -0
- data/spec/log_spec.rb +192 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/operation_result_spec.rb +208 -0
- data/spec/packets_spec.rb +461 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/platform/spec_helper.rb +216 -0
- data/spec/platform/unix/darwin/platform_spec.rb +181 -0
- data/spec/platform/unix/linux/platform_spec.rb +540 -0
- data/spec/platform/unix/spec_helper.rb +149 -0
- data/spec/platform/windows/mingw/platform_spec.rb +222 -0
- data/spec/platform/windows/mswin/platform_spec.rb +259 -0
- data/spec/platform/windows/spec_helper.rb +720 -0
- data/spec/retryable_request_spec.rb +306 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
- data/spec/security/certificate_cache_spec.rb +71 -0
- data/spec/security/certificate_spec.rb +49 -0
- data/spec/security/distinguished_name_spec.rb +46 -0
- data/spec/security/encrypted_document_spec.rb +55 -0
- data/spec/security/rsa_key_pair_spec.rb +55 -0
- data/spec/security/signature_spec.rb +66 -0
- data/spec/security/static_certificate_store_spec.rb +58 -0
- data/spec/sender_spec.rb +1045 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +132 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +197 -0
- data/spec/spec.opts +2 -0
- data/spec/spec.win32.opts +1 -0
- data/spec/spec_helper.rb +130 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +447 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightScale
|
24
|
+
|
25
|
+
class ActorRegistry
|
26
|
+
|
27
|
+
# (Hash) Actors that are registered; key is actor prefix and value is actor
|
28
|
+
attr_reader :actors
|
29
|
+
|
30
|
+
# Initialize registry
|
31
|
+
def initialize
|
32
|
+
@actors = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Register as an actor
|
36
|
+
#
|
37
|
+
# === Parameters
|
38
|
+
# actor(Actor):: Actor to be registered
|
39
|
+
# prefix(String):: Prefix used in request to identify actor
|
40
|
+
#
|
41
|
+
# === Return
|
42
|
+
# (Actor):: Actor registered
|
43
|
+
#
|
44
|
+
# === Raises
|
45
|
+
# ArgumentError if actor is not an Actor
|
46
|
+
def register(actor, prefix)
|
47
|
+
raise ArgumentError, "#{actor.inspect} is not a RightScale::Actor subclass instance" unless RightScale::Actor === actor
|
48
|
+
log_msg = "[actor] #{actor.class.to_s}"
|
49
|
+
log_msg += ", prefix #{prefix}" if prefix && !prefix.empty?
|
50
|
+
Log.info(log_msg)
|
51
|
+
prefix ||= actor.class.default_prefix
|
52
|
+
@actors[prefix.to_s] = actor
|
53
|
+
end
|
54
|
+
|
55
|
+
# Retrieve services provided by all of the registered actors
|
56
|
+
#
|
57
|
+
# === Return
|
58
|
+
# (Array):: List of unique /prefix/method path strings
|
59
|
+
def services
|
60
|
+
@actors.map { |prefix, actor| actor.class.provides_for(prefix) }.flatten.uniq
|
61
|
+
end
|
62
|
+
|
63
|
+
# Retrieve actor by prefix
|
64
|
+
#
|
65
|
+
# === Parameters
|
66
|
+
# prefix(String):: Prefix identifying actor
|
67
|
+
#
|
68
|
+
# === Return
|
69
|
+
# (Actor|nil):: Retrieved actor, or nil if unknown
|
70
|
+
def actor_for(prefix)
|
71
|
+
@actors[prefix]
|
72
|
+
end
|
73
|
+
|
74
|
+
end # ActorRegistry
|
75
|
+
|
76
|
+
end # RightScale
|
@@ -0,0 +1,232 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'socket'
|
24
|
+
|
25
|
+
# Generic actor for all agents to provide basic agent management services
|
26
|
+
class AgentManager
|
27
|
+
|
28
|
+
include RightScale::Actor
|
29
|
+
include RightScale::OperationResultHelper
|
30
|
+
|
31
|
+
on_exception { |meth, deliverable, e| RightScale::ExceptionMailer.deliver_notification(meth, deliverable, e) }
|
32
|
+
|
33
|
+
expose_idempotent :ping, :stats, :profile, :set_log_level, :connect, :disconnect, :connect_failed
|
34
|
+
expose_non_idempotent :execute, :terminate
|
35
|
+
|
36
|
+
# Valid log levels
|
37
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal]
|
38
|
+
|
39
|
+
# Initialize broker
|
40
|
+
#
|
41
|
+
# === Parameters
|
42
|
+
# agent(RightScale::Agent):: This agent
|
43
|
+
def initialize(agent)
|
44
|
+
@agent = agent
|
45
|
+
end
|
46
|
+
|
47
|
+
# Always return success along with identity, protocol version, and broker information
|
48
|
+
# Used for troubleshooting
|
49
|
+
#
|
50
|
+
# === Return
|
51
|
+
# (RightScale::OperationResult):: Always returns success
|
52
|
+
def ping(_)
|
53
|
+
success_result(:identity => @agent.options[:identity],
|
54
|
+
:hostname => Socket.gethostname,
|
55
|
+
:version => RightScale::AgentConfig.protocol_version,
|
56
|
+
:client => @agent.client.status,
|
57
|
+
:time => Time.now.to_i)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Retrieve statistics about agent operation
|
61
|
+
#
|
62
|
+
# === Parameters:
|
63
|
+
# options(Hash):: Request options:
|
64
|
+
# :reset(Boolean):: Whether to reset the statistics after getting the current ones
|
65
|
+
#
|
66
|
+
# === Return
|
67
|
+
# (RightScale::OperationResult):: Always returns success
|
68
|
+
def stats(options)
|
69
|
+
@agent.stats(RightScale::SerializationHelper.symbolize_keys(options || {}))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Profile memory use
|
73
|
+
#
|
74
|
+
# === Parameters
|
75
|
+
# options(Hash):: Request options
|
76
|
+
# :start(Boolean):: Whether to start profiling
|
77
|
+
# :stats(Boolean):: Whether to display profile statistics to stdout
|
78
|
+
# :reset(Boolean):: Whether to reset profile statistics when after displaying them
|
79
|
+
# :stop(Boolean):: Whether to stop profiling
|
80
|
+
#
|
81
|
+
# === Return
|
82
|
+
# (OperationResult):: Empty success result or error result with message
|
83
|
+
def profile(options)
|
84
|
+
return error_result("The memprof gem is not available for profiling. Please install memprof 0.3 manually") unless require_succeeds?('memprof')
|
85
|
+
|
86
|
+
options = RightScale::SerializationHelper.symbolize_keys(options || {})
|
87
|
+
if options[:start]
|
88
|
+
RightScale::Log.info("[profile] Start")
|
89
|
+
$stderr.puts "[profile] Start at #{Time.now}"
|
90
|
+
Memprof.start
|
91
|
+
@profiling = true
|
92
|
+
end
|
93
|
+
|
94
|
+
if options[:stats]
|
95
|
+
return error_result("Profiling has not yet been started") unless @profiling
|
96
|
+
RightScale::Log.info("[profile] GC start")
|
97
|
+
$stderr.puts "[profile] GC at #{Time.now}"
|
98
|
+
GC.start
|
99
|
+
RightScale::Log.info("[profile] Display stats to stderr")
|
100
|
+
$stderr.puts "[profile] Stats at #{Time.now}#{options[:reset] ? ' with reset' : ''}"
|
101
|
+
options[:reset] ? Memprof.stats! : Memprof.stats
|
102
|
+
end
|
103
|
+
|
104
|
+
if options[:stop]
|
105
|
+
return error_result("Profiling has not yet been started") unless @profiling
|
106
|
+
RightScale::Log.info("[profile] Stop")
|
107
|
+
$stderr.puts "[profile] Stop at #{Time.now}"
|
108
|
+
Memprof.stop
|
109
|
+
@profiling = false
|
110
|
+
end
|
111
|
+
success_result
|
112
|
+
end
|
113
|
+
|
114
|
+
# Change log level of agent
|
115
|
+
#
|
116
|
+
# === Parameter
|
117
|
+
# level(Symbol|String):: One of :debug, :info, :warn, :error, :fatal
|
118
|
+
#
|
119
|
+
# === Return
|
120
|
+
# (RightScale::OperationResult):: Success if level was changed, error otherwise
|
121
|
+
def set_log_level(level)
|
122
|
+
level = level.to_sym if level.is_a?(String)
|
123
|
+
if LEVELS.include?(level)
|
124
|
+
RightScale::Log.level = level
|
125
|
+
success_result
|
126
|
+
else
|
127
|
+
error_result("Invalid log level '#{level.to_s}'")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Eval given code in context of agent
|
132
|
+
#
|
133
|
+
# === Parameter
|
134
|
+
# code(String):: Code to be eval'd
|
135
|
+
#
|
136
|
+
# === Return
|
137
|
+
# (RightScale::OperationResult):: Success with result if code didn't raise an exception,
|
138
|
+
# otherwise failure with exception message
|
139
|
+
def execute(code)
|
140
|
+
begin
|
141
|
+
success_result(self.instance_eval(code))
|
142
|
+
rescue Exception => e
|
143
|
+
error_result(e.message + " at\n " + e.backtrace.join("\n "))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Connect agent to an additional broker or reconnect it if connection has failed
|
148
|
+
# Assumes agent already has credentials on this broker and identity queue exists
|
149
|
+
#
|
150
|
+
# === Parameters
|
151
|
+
# options(Hash):: Connect options:
|
152
|
+
# :host(String):: Host name of broker
|
153
|
+
# :port(Integer):: Port number of broker
|
154
|
+
# :id(Integer):: Small unique id associated with this broker for use in forming alias
|
155
|
+
# :priority(Integer|nil):: Priority position of this broker in list for use
|
156
|
+
# by this agent with nil meaning add to end of list
|
157
|
+
# :force(Boolean):: Reconnect even if already connected
|
158
|
+
#
|
159
|
+
# === Return
|
160
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
161
|
+
def connect(options)
|
162
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
163
|
+
res = success_result
|
164
|
+
begin
|
165
|
+
if error = @agent.connect(options[:host], options[:port], options[:id], options[:priority], options[:force])
|
166
|
+
res = error_result(error)
|
167
|
+
end
|
168
|
+
rescue Exception => e
|
169
|
+
res = error_result("Failed to connect to broker", e)
|
170
|
+
end
|
171
|
+
res
|
172
|
+
end
|
173
|
+
|
174
|
+
# Disconnect agent from a broker
|
175
|
+
#
|
176
|
+
# === Parameters
|
177
|
+
# options(Hash):: Connect options:
|
178
|
+
# :host(String):: Host name of broker
|
179
|
+
# :port(Integer):: Port number of broker
|
180
|
+
# :remove(Boolean):: Remove broker from configuration in addition to disconnecting it
|
181
|
+
#
|
182
|
+
# === Return
|
183
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
184
|
+
def disconnect(options)
|
185
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
186
|
+
res = success_result
|
187
|
+
begin
|
188
|
+
if error = @agent.disconnect(options[:host], options[:port], options[:remove])
|
189
|
+
res = error_result(error)
|
190
|
+
end
|
191
|
+
rescue Exception => e
|
192
|
+
res = error_result("Failed to disconnect from broker", e)
|
193
|
+
end
|
194
|
+
res
|
195
|
+
end
|
196
|
+
|
197
|
+
# Declare one or more broker connections unusable because connection setup has failed
|
198
|
+
#
|
199
|
+
# === Parameters
|
200
|
+
# options(Hash):: Failure options:
|
201
|
+
# :brokers(Array):: Identity of brokers
|
202
|
+
#
|
203
|
+
# === Return
|
204
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
205
|
+
def connect_failed(options)
|
206
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
207
|
+
res = success_result
|
208
|
+
begin
|
209
|
+
if error = @agent.connect_failed(options[:brokers])
|
210
|
+
res = error_result(error)
|
211
|
+
end
|
212
|
+
rescue Exception => e
|
213
|
+
res = error_result("Failed to notify agent that brokers #{options[:brokers]} are unusable", e)
|
214
|
+
end
|
215
|
+
res
|
216
|
+
end
|
217
|
+
|
218
|
+
# Terminate self
|
219
|
+
#
|
220
|
+
# === Parameters
|
221
|
+
# options(Hash):: Terminate options
|
222
|
+
#
|
223
|
+
# === Return
|
224
|
+
# true
|
225
|
+
def terminate(options = nil)
|
226
|
+
RightScale::CommandRunner.stop
|
227
|
+
# Delay terminate a bit to give request message a chance to be ack'd and reply to be sent
|
228
|
+
EM.add_timer(1) { @agent.terminate }
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
@@ -0,0 +1,1149 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'socket'
|
24
|
+
|
25
|
+
module RightScale
|
26
|
+
|
27
|
+
# Agent for receiving requests via RightNet and acting upon them
|
28
|
+
# by dispatching to a registered actor to perform
|
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
|
31
|
+
class Agent
|
32
|
+
|
33
|
+
include ConsoleHelper
|
34
|
+
include DaemonizeHelper
|
35
|
+
|
36
|
+
# (String) Identity of this agent
|
37
|
+
attr_reader :identity
|
38
|
+
|
39
|
+
# (Hash) Configuration options applied to the agent
|
40
|
+
attr_reader :options
|
41
|
+
|
42
|
+
# (Hash) Dispatcher for each queue for messages received via AMQP
|
43
|
+
attr_reader :dispatchers
|
44
|
+
|
45
|
+
# (ActorRegistry) Registry for this agents actors
|
46
|
+
attr_reader :registry
|
47
|
+
|
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
|
56
|
+
|
57
|
+
# (Array) Tag strings published by agent
|
58
|
+
attr_accessor :tags
|
59
|
+
|
60
|
+
# (Proc) Callback procedure for exceptions
|
61
|
+
attr_reader :exception_callback
|
62
|
+
|
63
|
+
# Default option settings for the agent
|
64
|
+
DEFAULT_OPTIONS = {
|
65
|
+
:user => 'agent',
|
66
|
+
:pass => 'testing',
|
67
|
+
:vhost => '/right_net',
|
68
|
+
:secure => true,
|
69
|
+
:log_level => :info,
|
70
|
+
:daemonize => false,
|
71
|
+
:console => false,
|
72
|
+
:root_dir => Dir.pwd,
|
73
|
+
:mode => :amqp,
|
74
|
+
:time_to_live => 0,
|
75
|
+
:retry_interval => nil,
|
76
|
+
:retry_timeout => nil,
|
77
|
+
:connect_timeout => 60,
|
78
|
+
:reconnect_interval => 60,
|
79
|
+
:offline_queueing => false,
|
80
|
+
:ping_interval => 0,
|
81
|
+
:check_interval => 5 * 60,
|
82
|
+
:grace_timeout => 30,
|
83
|
+
:prefetch => 1,
|
84
|
+
:heartbeat => 0
|
85
|
+
}
|
86
|
+
|
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? }
|
92
|
+
|
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
|
96
|
+
#
|
97
|
+
# === Parameters
|
98
|
+
# opts(Hash):: Configuration options:
|
99
|
+
# :identity(String):: Identity of this agent; no default
|
100
|
+
# :agent_name(String):: Local name for this agent
|
101
|
+
# :root_dir(String):: Application root for this agent containing subdirectories actors, certs, and init;
|
102
|
+
# defaults to current working directory
|
103
|
+
# :pid_dir(String):: Path to the directory where the agent stores its process id file (only if daemonized);
|
104
|
+
# defaults to the current working directory
|
105
|
+
# :log_dir(String):: Log directory path; defaults to the platform specific log directory
|
106
|
+
# :log_level(Symbol):: The verbosity of logging -- :debug, :info, :warn, :error or :fatal
|
107
|
+
# :actors(Array):: List of actors to load
|
108
|
+
# :console(Boolean):: true indicates to start interactive console
|
109
|
+
# :daemonize(Boolean):: true indicates to daemonize
|
110
|
+
# :retry_interval(Numeric):: Number of seconds between request retries
|
111
|
+
# :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
|
112
|
+
# :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
|
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,
|
117
|
+
# also requires agent invocation of Sender initialize_offline_queue and start_offline_queue methods,
|
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
|
123
|
+
# :heartbeat(Integer):: Number of seconds between AMQP connection heartbeats used to keep
|
124
|
+
# connection alive (e.g., when AMQP broker is behind a firewall), nil or 0 means disable
|
125
|
+
# :grace_timeout(Integer):: Maximum number of seconds to wait after last request received before
|
126
|
+
# terminating regardless of whether there are still unfinished requests
|
127
|
+
# :dup_check(Boolean):: Whether to check for and reject duplicate requests, e.g., due to retries
|
128
|
+
# or redelivery by AMQP broker after server failure
|
129
|
+
# :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for this agent
|
130
|
+
# before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
|
131
|
+
# if the agent crashes. Value 0 means unlimited prefetch.
|
132
|
+
# :exception_callback(Proc):: Callback with following parameters that is activated on exception events:
|
133
|
+
# exception(Exception):: Exception
|
134
|
+
# message(Packet):: Message being processed
|
135
|
+
# agent(Agent):: Reference to agent
|
136
|
+
# :ready_callback(Proc):: Called once agent is connected to AMQP broker and ready for service (no argument)
|
137
|
+
# :restart_callback(Proc):: Called on each restart vote with votes being initiated by offline queue
|
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
|
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
|
145
|
+
# :vhost(String):: AMQP broker virtual host
|
146
|
+
# :user(String):: AMQP broker user
|
147
|
+
# :pass(String):: AMQP broker password
|
148
|
+
# :host(String):: Comma-separated list of AMQP broker hosts; if only one, it is reapplied
|
149
|
+
# to successive ports; if none; defaults to 'localhost'
|
150
|
+
# :port(Integer):: Comma-separated list of AMQP broker ports corresponding to hosts; if only one,
|
151
|
+
# it is incremented and applied to successive hosts; if none, defaults to AMQP::HOST
|
152
|
+
#
|
153
|
+
# On start config.yml is read, so it is common to specify options in the YAML file. However, when both
|
154
|
+
# Ruby code options and YAML file specify options, Ruby code options take precedence.
|
155
|
+
#
|
156
|
+
# === Return
|
157
|
+
# agent(Agent):: New agent
|
158
|
+
def self.start(opts = {})
|
159
|
+
agent = new(opts)
|
160
|
+
agent.run
|
161
|
+
agent
|
162
|
+
end
|
163
|
+
|
164
|
+
# Initialize the new agent
|
165
|
+
#
|
166
|
+
# === Parameters
|
167
|
+
# opts(Hash):: Configuration options per start method above
|
168
|
+
#
|
169
|
+
# === Return
|
170
|
+
# true:: Always return true
|
171
|
+
def initialize(opts)
|
172
|
+
set_configuration(opts)
|
173
|
+
@tags = []
|
174
|
+
@tags << opts[:tag] if opts[:tag]
|
175
|
+
@tags.flatten!
|
176
|
+
@status_callbacks = []
|
177
|
+
@options.freeze
|
178
|
+
@last_stat_reset_time = Time.now
|
179
|
+
reset_agent_stats
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
# Put the agent in service
|
184
|
+
# This requires making a RightNet connection via HTTP or AMQP
|
185
|
+
# and other initialization like loading actors
|
186
|
+
#
|
187
|
+
# === Return
|
188
|
+
# true:: Always return true
|
189
|
+
def run
|
190
|
+
Log.init(@identity, @options[:log_path], :print => true)
|
191
|
+
Log.level = @options[:log_level] if @options[:log_level]
|
192
|
+
RightSupport::Log::Mixin.default_logger = Log
|
193
|
+
@history.update("start")
|
194
|
+
now = Time.now
|
195
|
+
Log.info("[start] Agent #{@identity} starting; time: #{now.utc}; utc_offset: #{now.utc_offset}")
|
196
|
+
Log.debug("Start options:")
|
197
|
+
log_opts = @options.inject([]) do |t, (k, v)|
|
198
|
+
t << "- #{k}: #{k.to_s =~ /pass/ ? '****' : (v.respond_to?(:each) ? v.inspect : v)}"
|
199
|
+
end
|
200
|
+
log_opts.each { |l| Log.debug(l) }
|
201
|
+
|
202
|
+
begin
|
203
|
+
# Capture process id in file after optional daemonize
|
204
|
+
pid_file = PidFile.new(@identity)
|
205
|
+
pid_file.check
|
206
|
+
daemonize(@identity, @options) if @options[:daemonize]
|
207
|
+
pid_file.write
|
208
|
+
at_exit { pid_file.remove }
|
209
|
+
|
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
|
233
|
+
end
|
234
|
+
end
|
235
|
+
rescue SystemExit
|
236
|
+
raise
|
237
|
+
rescue PidFile::AlreadyRunning
|
238
|
+
EM.stop if EM.reactor_running?
|
239
|
+
raise
|
240
|
+
rescue Exception => e
|
241
|
+
terminate("failed startup", e)
|
242
|
+
end
|
243
|
+
true
|
244
|
+
end
|
245
|
+
|
246
|
+
# Register an actor for this agent
|
247
|
+
#
|
248
|
+
# === Parameters
|
249
|
+
# actor(Actor):: Actor to be registered
|
250
|
+
# prefix(String):: Prefix to be used in place of actor's default_prefix
|
251
|
+
#
|
252
|
+
# === Return
|
253
|
+
# (Actor):: Actor registered
|
254
|
+
def register(actor, prefix = nil)
|
255
|
+
@registry.register(actor, prefix)
|
256
|
+
end
|
257
|
+
|
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
|
281
|
+
# Subscribe to identity queue on this broker
|
282
|
+
# Update config file if this is a new broker
|
283
|
+
# Assumes already has credentials on this broker and identity queue exists
|
284
|
+
#
|
285
|
+
# === Parameters
|
286
|
+
# host(String):: Host name of broker
|
287
|
+
# port(Integer):: Port number of broker
|
288
|
+
# index(Integer):: Small unique id associated with this broker for use in forming alias
|
289
|
+
# priority(Integer|nil):: Priority position of this broker in list for use
|
290
|
+
# by this agent with nil meaning add to end of list
|
291
|
+
# force(Boolean):: Reconnect even if already connected
|
292
|
+
#
|
293
|
+
# === Return
|
294
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
295
|
+
def connect(host, port, index, priority = nil, force = false)
|
296
|
+
@connect_request_stats.update("connect b#{index}")
|
297
|
+
even_if = " even if already connected" if force
|
298
|
+
Log.info("Connecting to broker at host #{host.inspect} port #{port.inspect} " +
|
299
|
+
"index #{index.inspect} priority #{priority.inspect}#{even_if}")
|
300
|
+
Log.info("Current broker configuration: #{@client.status.inspect}")
|
301
|
+
res = nil
|
302
|
+
begin
|
303
|
+
@client.connect(host, port, index, priority, force) do |id|
|
304
|
+
@client.connection_status(:one_off => @options[:connect_timeout], :brokers => [id]) do |status|
|
305
|
+
begin
|
306
|
+
if status == :connected
|
307
|
+
setup_queues([id])
|
308
|
+
remaining = 0
|
309
|
+
@remaining_queue_setup.each_value { |ids| remaining += ids.size }
|
310
|
+
Log.info("[setup] Finished subscribing to queues after reconnecting to broker #{id}") if remaining == 0
|
311
|
+
unless update_configuration(:host => @client.hosts, :port => @client.ports)
|
312
|
+
Log.warning("Successfully connected to broker #{id} but failed to update config file")
|
313
|
+
end
|
314
|
+
else
|
315
|
+
Log.error("Failed to connect to broker #{id}, status #{status.inspect}")
|
316
|
+
end
|
317
|
+
rescue Exception => e
|
318
|
+
Log.error("Failed to connect to broker #{id}, status #{status.inspect}", e)
|
319
|
+
@exception_stats.track("connect", e)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
rescue Exception => e
|
324
|
+
res = Log.format("Failed to connect to broker at host #{host.inspect} and port #{port.inspect}", e)
|
325
|
+
@exception_stats.track("connect", e)
|
326
|
+
end
|
327
|
+
Log.error(res) if res
|
328
|
+
res
|
329
|
+
end
|
330
|
+
|
331
|
+
# Disconnect from an AMQP broker and optionally remove it from the configuration
|
332
|
+
# Refuse to do so if it is the last connected broker
|
333
|
+
#
|
334
|
+
# === Parameters
|
335
|
+
# host(String):: Host name of broker
|
336
|
+
# port(Integer):: Port number of broker
|
337
|
+
# remove(Boolean):: Whether to remove broker from configuration rather than just closing it,
|
338
|
+
# defaults to false
|
339
|
+
#
|
340
|
+
# === Return
|
341
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
342
|
+
def disconnect(host, port, remove = false)
|
343
|
+
and_remove = " and removing" if remove
|
344
|
+
Log.info("Disconnecting#{and_remove} broker at host #{host.inspect} port #{port.inspect}")
|
345
|
+
Log.info("Current broker configuration: #{@client.status.inspect}")
|
346
|
+
id = RightAMQP::HABrokerClient.identity(host, port)
|
347
|
+
@connect_request_stats.update("disconnect #{@client.alias_(id)}")
|
348
|
+
connected = @client.connected
|
349
|
+
res = nil
|
350
|
+
if connected.include?(id) && connected.size == 1
|
351
|
+
res = "Not disconnecting from #{id} because it is the last connected broker for this agent"
|
352
|
+
elsif @client.get(id)
|
353
|
+
begin
|
354
|
+
if remove
|
355
|
+
@client.remove(host, port) do |id|
|
356
|
+
unless update_configuration(:host => @client.hosts, :port => @client.ports)
|
357
|
+
res = "Successfully disconnected from broker #{id} but failed to update config file"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
else
|
361
|
+
@client.close_one(id)
|
362
|
+
end
|
363
|
+
rescue Exception => e
|
364
|
+
res = Log.format("Failed to disconnect from broker #{id}", e)
|
365
|
+
@exception_stats.track("disconnect", e)
|
366
|
+
end
|
367
|
+
else
|
368
|
+
res = "Cannot disconnect from broker #{id} because not configured for this agent"
|
369
|
+
end
|
370
|
+
Log.error(res) if res
|
371
|
+
res
|
372
|
+
end
|
373
|
+
|
374
|
+
# There were problems while setting up service for this agent on the given AMQP brokers,
|
375
|
+
# so mark these brokers as failed if not currently connected and later, during the
|
376
|
+
# periodic status check, attempt to reconnect
|
377
|
+
#
|
378
|
+
# === Parameters
|
379
|
+
# ids(Array):: Identity of brokers
|
380
|
+
#
|
381
|
+
# === Return
|
382
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
383
|
+
def connect_failed(ids)
|
384
|
+
aliases = @client.aliases(ids).join(", ")
|
385
|
+
@connect_request_stats.update("enroll failed #{aliases}")
|
386
|
+
res = nil
|
387
|
+
begin
|
388
|
+
Log.info("Received indication that service initialization for this agent for brokers #{ids.inspect} has failed")
|
389
|
+
connected = @client.connected
|
390
|
+
ignored = connected & ids
|
391
|
+
Log.info("Not marking brokers #{ignored.inspect} as unusable because currently connected") if ignored
|
392
|
+
Log.info("Current broker configuration: #{@client.status.inspect}")
|
393
|
+
@client.declare_unusable(ids - ignored)
|
394
|
+
rescue Exception => e
|
395
|
+
res = Log.format("Failed handling broker connection failure indication for #{ids.inspect}", e)
|
396
|
+
Log.error(res)
|
397
|
+
@exception_stats.track("connect failed", e)
|
398
|
+
end
|
399
|
+
res
|
400
|
+
end
|
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
|
+
|
424
|
+
# Gracefully terminate execution by allowing unfinished tasks to complete
|
425
|
+
# Immediately terminate if called a second time
|
426
|
+
# Report reason for termination if it is abnormal
|
427
|
+
#
|
428
|
+
# === Parameters
|
429
|
+
# reason(String):: Reason for abnormal termination, if any
|
430
|
+
# exception(Exception|String):: Exception or other parenthetical error information, if any
|
431
|
+
#
|
432
|
+
# === Return
|
433
|
+
# true:: Always return true
|
434
|
+
def terminate(reason = nil, exception = nil)
|
435
|
+
begin
|
436
|
+
@history.update("stop") if @history
|
437
|
+
Log.error("[stop] Terminating because #{reason}", exception, :trace) if reason
|
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?
|
447
|
+
@terminating = true
|
448
|
+
@termination_timer.cancel if @termination_timer
|
449
|
+
@termination_timer = nil
|
450
|
+
Log.info("[stop] Terminating immediately")
|
451
|
+
@terminate_callback.call
|
452
|
+
@history.update("graceful exit") if @history && @client.nil?
|
453
|
+
else
|
454
|
+
@terminating = true
|
455
|
+
@check_status_timer.cancel if @check_status_timer
|
456
|
+
@check_status_timer = nil
|
457
|
+
Log.info("[stop] Agent #{@identity} terminating")
|
458
|
+
stop_gracefully(@options[:grace_timeout])
|
459
|
+
end
|
460
|
+
rescue SystemExit
|
461
|
+
raise
|
462
|
+
rescue Exception => e
|
463
|
+
Log.error("Failed to terminate gracefully", e, :trace)
|
464
|
+
begin @terminate_callback.call; rescue Exception; end
|
465
|
+
end
|
466
|
+
true
|
467
|
+
end
|
468
|
+
|
469
|
+
# Retrieve statistics about agent operation
|
470
|
+
#
|
471
|
+
# === Parameters:
|
472
|
+
# options(Hash):: Request options:
|
473
|
+
# :reset(Boolean):: Whether to reset the statistics after getting the current ones
|
474
|
+
#
|
475
|
+
# === Return
|
476
|
+
# result(OperationResult):: Always returns success
|
477
|
+
def stats(options = {})
|
478
|
+
now = Time.now
|
479
|
+
reset = options[:reset]
|
480
|
+
stats = {
|
481
|
+
"name" => @agent_name,
|
482
|
+
"identity" => @identity,
|
483
|
+
"hostname" => Socket.gethostname,
|
484
|
+
"memory" => Platform.process.resident_set_size,
|
485
|
+
"version" => AgentConfig.protocol_version,
|
486
|
+
"agent stats" => agent_stats(reset),
|
487
|
+
"receive stats" => dispatcher_stats(reset),
|
488
|
+
"send stats" => @sender.stats(reset),
|
489
|
+
"last reset time" => @last_stat_reset_time.to_i,
|
490
|
+
"stat time" => now.to_i,
|
491
|
+
"service uptime" => @history.analyze_service,
|
492
|
+
"machine uptime" => Platform.shell.uptime
|
493
|
+
}
|
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
|
500
|
+
result = OperationResult.success(stats)
|
501
|
+
@last_stat_reset_time = now if reset
|
502
|
+
result
|
503
|
+
end
|
504
|
+
|
505
|
+
protected
|
506
|
+
|
507
|
+
# Get request statistics
|
508
|
+
#
|
509
|
+
# === Parameters
|
510
|
+
# reset(Boolean):: Whether to reset the statistics after getting the current ones
|
511
|
+
#
|
512
|
+
# === Return
|
513
|
+
# stats(Hash):: Current statistics:
|
514
|
+
# "connect requests"(Hash|nil):: Stats about requests to update AMQP broker connections with keys "total", "percent",
|
515
|
+
# and "last" with percentage breakdown by "connects: <alias>", "disconnects: <alias>", "enroll setup failed:
|
516
|
+
# <aliases>", or nil if none
|
517
|
+
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
518
|
+
# "total"(Integer):: Total exceptions for this category
|
519
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
520
|
+
# "non-deliveries"(Hash):: AMQP message non-delivery activity stats with keys "total", "percent", "last", and "rate"
|
521
|
+
# with percentage breakdown by request type, or nil if none
|
522
|
+
# "request failures"(Hash|nil):: Request dispatch failure activity stats with keys "total", "percent", "last", and "rate"
|
523
|
+
# with percentage breakdown per failure type, or nil if none
|
524
|
+
# "response failures"(Hash|nil):: Response delivery failure activity stats with keys "total", "percent", "last", and "rate"
|
525
|
+
# with percentage breakdown per failure type, or nil if none
|
526
|
+
def agent_stats(reset = false)
|
527
|
+
stats = {
|
528
|
+
"exceptions" => @exception_stats.stats,
|
529
|
+
"request failures" => @request_failure_stats.all,
|
530
|
+
"response failures" => @response_failure_stats.all
|
531
|
+
}
|
532
|
+
unless @mode == :http
|
533
|
+
stats["connect requests"] = @connect_request_stats.all
|
534
|
+
stats["non-deliveries"] = @non_delivery_stats.all
|
535
|
+
end
|
536
|
+
reset_agent_stats if reset
|
537
|
+
stats
|
538
|
+
end
|
539
|
+
|
540
|
+
# Reset agent statistics
|
541
|
+
#
|
542
|
+
# === Return
|
543
|
+
# true:: Always return true
|
544
|
+
def reset_agent_stats
|
545
|
+
@connect_request_stats = RightSupport::Stats::Activity.new(measure_rate = false)
|
546
|
+
@non_delivery_stats = RightSupport::Stats::Activity.new
|
547
|
+
@request_failure_stats = RightSupport::Stats::Activity.new
|
548
|
+
@response_failure_stats = RightSupport::Stats::Activity.new
|
549
|
+
@exception_stats = RightSupport::Stats::Exceptions.new(self, @options[:exception_callback])
|
550
|
+
true
|
551
|
+
end
|
552
|
+
|
553
|
+
# Get dispatcher statistics
|
554
|
+
#
|
555
|
+
# === Return
|
556
|
+
# (Hash):: Current statistics
|
557
|
+
def dispatcher_stats(reset)
|
558
|
+
@dispatchers[@identity].stats(reset)
|
559
|
+
end
|
560
|
+
|
561
|
+
# Set the agent's configuration using the supplied options
|
562
|
+
#
|
563
|
+
# === Parameters
|
564
|
+
# opts(Hash):: Configuration options
|
565
|
+
#
|
566
|
+
# === Return
|
567
|
+
# true:: Always return true
|
568
|
+
def set_configuration(opts)
|
569
|
+
@options = DEFAULT_OPTIONS.clone
|
570
|
+
@options.update(opts)
|
571
|
+
|
572
|
+
AgentConfig.root_dir = @options[:root_dir]
|
573
|
+
AgentConfig.pid_dir = @options[:pid_dir]
|
574
|
+
|
575
|
+
@options[:log_path] = false
|
576
|
+
if @options[:daemonize] || @options[:log_dir]
|
577
|
+
@options[:log_path] = (@options[:log_dir] || Platform.filesystem.log_dir)
|
578
|
+
FileUtils.mkdir_p(@options[:log_path]) unless File.directory?(@options[:log_path])
|
579
|
+
end
|
580
|
+
|
581
|
+
@options[:async_response] = true unless @options.has_key?(:async_response)
|
582
|
+
|
583
|
+
@identity = @options[:identity]
|
584
|
+
parsed_identity = AgentIdentity.parse(@identity)
|
585
|
+
@agent_type = parsed_identity.agent_type
|
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
|
590
|
+
@stats_routing_key = "stats.#{@agent_type}.#{parsed_identity.base_id}"
|
591
|
+
@terminate_callback = TERMINATE_BLOCK
|
592
|
+
@exception_callback = @options[:exception_callback]
|
593
|
+
@revision = revision
|
594
|
+
@queues = [@identity]
|
595
|
+
@remaining_queue_setup = {}
|
596
|
+
@history = History.new(@identity)
|
597
|
+
end
|
598
|
+
|
599
|
+
# Start service
|
600
|
+
#
|
601
|
+
# === Return
|
602
|
+
# true:: Always return true
|
603
|
+
def start_service
|
604
|
+
begin
|
605
|
+
@registry = ActorRegistry.new
|
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
|
609
|
+
@sender = create_sender
|
610
|
+
load_actors
|
611
|
+
setup_traps
|
612
|
+
setup_status
|
613
|
+
unless @mode == :http
|
614
|
+
setup_non_delivery
|
615
|
+
setup_queues
|
616
|
+
end
|
617
|
+
@history.update("run")
|
618
|
+
start_console if @options[:console] && !@options[:daemonize]
|
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
|
621
|
+
|
622
|
+
# Need to keep reconnect interval at least :connect_timeout in size,
|
623
|
+
# otherwise connection_status callback will not timeout prior to next
|
624
|
+
# reconnect attempt, which can result in repeated attempts to setup
|
625
|
+
# queues when finally do connect
|
626
|
+
setup_status_checks([@options[:check_interval], @options[:connect_timeout]].max)
|
627
|
+
rescue SystemExit
|
628
|
+
raise
|
629
|
+
rescue Exception => e
|
630
|
+
terminate("failed service startup", e)
|
631
|
+
end
|
632
|
+
true
|
633
|
+
end
|
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
|
+
|
703
|
+
# Create dispatcher per queue for use in handling incoming requests
|
704
|
+
#
|
705
|
+
# === Return
|
706
|
+
# [Hash]:: Dispatchers with queue name as key
|
707
|
+
def create_dispatchers
|
708
|
+
cache = DispatchedCache.new(@identity) if @options[:dup_check]
|
709
|
+
@dispatcher = Dispatcher.new(self, cache)
|
710
|
+
@queues.inject({}) { |dispatchers, queue| dispatchers[queue] = @dispatcher; dispatchers }
|
711
|
+
end
|
712
|
+
|
713
|
+
# Create manager for outgoing requests
|
714
|
+
#
|
715
|
+
# === Return
|
716
|
+
# (Sender):: New sender
|
717
|
+
def create_sender
|
718
|
+
Sender.new(self)
|
719
|
+
end
|
720
|
+
|
721
|
+
# Load the ruby code for the actors
|
722
|
+
#
|
723
|
+
# === Return
|
724
|
+
# true:: Always return true
|
725
|
+
def load_actors
|
726
|
+
# Load agent's configured actors
|
727
|
+
actors = (@options[:actors] || []).clone
|
728
|
+
Log.info("[setup] Agent #{@identity} with actors #{actors.inspect}")
|
729
|
+
actors_dirs = AgentConfig.actors_dirs
|
730
|
+
actors_dirs.each do |dir|
|
731
|
+
Dir["#{dir}/*.rb"].each do |file|
|
732
|
+
actor = File.basename(file, ".rb")
|
733
|
+
next if actors && !actors.include?(actor)
|
734
|
+
Log.info("[setup] loading actor #{file}")
|
735
|
+
require file
|
736
|
+
actors.delete(actor)
|
737
|
+
end
|
738
|
+
end
|
739
|
+
Log.error("Actors #{actors.inspect} not found in #{actors_dirs.inspect}") unless actors.empty?
|
740
|
+
|
741
|
+
# Perform agent-specific initialization including actor creation and registration
|
742
|
+
if (init_file = AgentConfig.init_file)
|
743
|
+
Log.info("[setup] initializing agent from #{init_file}")
|
744
|
+
instance_eval(File.read(init_file), init_file)
|
745
|
+
else
|
746
|
+
Log.error("No agent init.rb file found in init directory of #{AgentConfig.root_dir.inspect}")
|
747
|
+
end
|
748
|
+
true
|
749
|
+
end
|
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
|
+
|
769
|
+
# Setup signal traps
|
770
|
+
#
|
771
|
+
# === Return
|
772
|
+
# true:: Always return true
|
773
|
+
def setup_traps
|
774
|
+
['INT', 'TERM'].each do |sig|
|
775
|
+
old = trap(sig) do
|
776
|
+
EM.next_tick do
|
777
|
+
begin
|
778
|
+
terminate do
|
779
|
+
TERMINATE_BLOCK.call
|
780
|
+
old.call if old.is_a? Proc
|
781
|
+
end
|
782
|
+
rescue Exception => e
|
783
|
+
Log.error("Failed in termination", e, :trace)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|
788
|
+
true
|
789
|
+
end
|
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
|
+
|
807
|
+
# Setup non-delivery handler
|
808
|
+
#
|
809
|
+
# === Return
|
810
|
+
# true:: Always return true
|
811
|
+
def setup_non_delivery
|
812
|
+
@client.non_delivery do |reason, type, token, from, to|
|
813
|
+
begin
|
814
|
+
@non_delivery_stats.update(type)
|
815
|
+
reason = case reason
|
816
|
+
when "NO_ROUTE" then OperationResult::NO_ROUTE_TO_TARGET
|
817
|
+
when "NO_CONSUMERS" then OperationResult::TARGET_NOT_CONNECTED
|
818
|
+
else reason.to_s
|
819
|
+
end
|
820
|
+
result = Result.new(token, from, OperationResult.non_delivery(reason), to)
|
821
|
+
@sender.handle_response(result)
|
822
|
+
rescue Exception => e
|
823
|
+
Log.error("Failed handling non-delivery for <#{token}>", e, :trace)
|
824
|
+
@exception_stats.track("message return", e)
|
825
|
+
end
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
# Setup the queues on the specified brokers for this agent
|
830
|
+
# Do the setup regardless of whether remaining setup is empty since may be reconnecting
|
831
|
+
#
|
832
|
+
# === Parameters
|
833
|
+
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
834
|
+
#
|
835
|
+
# === Return
|
836
|
+
# true:: Always return true
|
837
|
+
def setup_queues(ids = nil)
|
838
|
+
@queues.each { |q| @remaining_queue_setup[q] -= setup_queue(q, ids) }
|
839
|
+
true
|
840
|
+
end
|
841
|
+
|
842
|
+
# Setup queue for this agent
|
843
|
+
#
|
844
|
+
# === Parameters
|
845
|
+
# name(String):: Queue name
|
846
|
+
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
847
|
+
#
|
848
|
+
# === Return
|
849
|
+
# (Array):: Identity of brokers to which subscribe submitted (although may still fail)
|
850
|
+
def setup_queue(name, ids = nil)
|
851
|
+
queue = {:name => name, :options => {:durable => true, :no_declare => @options[:secure]}}
|
852
|
+
filter = [:from, :tags, :tries, :persistent]
|
853
|
+
options = {:ack => true, Push => filter, Request => filter, Result => [:from], :brokers => ids}
|
854
|
+
@client.subscribe(queue, nil, options) { |_, packet, header| handle_packet(name, packet, header) }
|
855
|
+
end
|
856
|
+
|
857
|
+
# Handle packet from queue
|
858
|
+
#
|
859
|
+
# === Parameters
|
860
|
+
# queue(String):: Name of queue from which message was received
|
861
|
+
# packet(Packet):: Packet received
|
862
|
+
# header(AMQP::Frame::Header):: Packet header containing ack control
|
863
|
+
#
|
864
|
+
# === Return
|
865
|
+
# true:: Always return true
|
866
|
+
def handle_packet(queue, packet, header)
|
867
|
+
begin
|
868
|
+
# Continue to dispatch/ack requests even when terminating otherwise will block results
|
869
|
+
# Ideally would reject requests when terminating but broker client does not yet support that
|
870
|
+
case packet
|
871
|
+
when Push, Request then dispatch_request(packet, queue)
|
872
|
+
when Result then deliver_response(packet)
|
873
|
+
end
|
874
|
+
@sender.message_received
|
875
|
+
rescue Exception => e
|
876
|
+
Log.error("#{queue} queue processing error", e, :trace)
|
877
|
+
@exception_stats.track("#{queue} queue", e, packet)
|
878
|
+
ensure
|
879
|
+
# Relying on fact that all dispatches/deliveries are synchronous and therefore
|
880
|
+
# need to have completed or failed by now, thus allowing packet acknowledgement
|
881
|
+
header.ack
|
882
|
+
end
|
883
|
+
true
|
884
|
+
end
|
885
|
+
|
886
|
+
# Dispatch request and then send response if any
|
887
|
+
#
|
888
|
+
# === Parameters
|
889
|
+
# request(Push|Request):: Packet containing request
|
890
|
+
# queue(String):: Name of queue from which message was received
|
891
|
+
#
|
892
|
+
# === Return
|
893
|
+
# true:: Always return true
|
894
|
+
def dispatch_request(request, queue)
|
895
|
+
begin
|
896
|
+
if (dispatcher = @dispatchers[queue])
|
897
|
+
if (result = dispatcher.dispatch(request))
|
898
|
+
exchange = {:type => :queue, :name => request.reply_to, :options => {:durable => true, :no_declare => @options[:secure]}}
|
899
|
+
@client.publish(exchange, result, :persistent => true, :mandatory => true, :log_filter => [:request_from, :tries, :persistent, :duration])
|
900
|
+
end
|
901
|
+
else
|
902
|
+
Log.error("Failed to dispatch request #{request.trace} from queue #{queue} because no dispatcher configured")
|
903
|
+
@request_failure_stats.update("NoConfiguredDispatcher")
|
904
|
+
end
|
905
|
+
rescue Dispatcher::DuplicateRequest
|
906
|
+
rescue RightAMQP::HABrokerClient::NoConnectedBrokers => e
|
907
|
+
Log.error("Failed to publish result of dispatched request #{request.trace} from queue #{queue}", e)
|
908
|
+
@request_failure_stats.update("NoConnectedBrokers")
|
909
|
+
rescue Exception => e
|
910
|
+
Log.error("Failed to dispatch request #{request.trace} from queue #{queue}", e, :trace)
|
911
|
+
@request_failure_stats.update(e.class.name)
|
912
|
+
@exception_stats.track("request", e)
|
913
|
+
end
|
914
|
+
true
|
915
|
+
end
|
916
|
+
|
917
|
+
# Deliver response to request sender
|
918
|
+
#
|
919
|
+
# === Parameters
|
920
|
+
# result(Result):: Packet containing response
|
921
|
+
#
|
922
|
+
# === Return
|
923
|
+
# true:: Always return true
|
924
|
+
def deliver_response(result)
|
925
|
+
begin
|
926
|
+
@sender.handle_response(result)
|
927
|
+
rescue Exception => e
|
928
|
+
Log.error("Failed to deliver response #{result.trace}", e, :trace)
|
929
|
+
@response_failure_stats.update(e.class.name)
|
930
|
+
@exception_stats.track("response", e)
|
931
|
+
end
|
932
|
+
true
|
933
|
+
end
|
934
|
+
|
935
|
+
# Finish any remaining agent setup
|
936
|
+
#
|
937
|
+
# === Return
|
938
|
+
# true:: Always return true
|
939
|
+
def finish_setup
|
940
|
+
@client.failed.each do |id|
|
941
|
+
p = {:agent_identity => @identity}
|
942
|
+
p[:host], p[:port], p[:id], p[:priority] = @client.identity_parts(id)
|
943
|
+
@sender.send_push("/registrar/connect", p)
|
944
|
+
end
|
945
|
+
true
|
946
|
+
end
|
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
|
+
|
970
|
+
# Setup periodic status check
|
971
|
+
#
|
972
|
+
# === Parameters
|
973
|
+
# interval(Integer):: Number of seconds between status checks
|
974
|
+
#
|
975
|
+
# === Return
|
976
|
+
# true:: Always return true
|
977
|
+
def setup_status_checks(interval)
|
978
|
+
@check_status_count = 0
|
979
|
+
@check_status_brokers = @client.all unless @mode == :http
|
980
|
+
@check_status_timer = EM::PeriodicTimer.new(interval) { check_status }
|
981
|
+
true
|
982
|
+
end
|
983
|
+
|
984
|
+
# Check status of agent by finishing any queue setup, checking the status of the queues,
|
985
|
+
# and gathering/publishing current operation statistics
|
986
|
+
# Checking the status of a queue will cause the broker connection to fail if the
|
987
|
+
# queue does not exist, but a reconnect should then get initiated on the next check loop
|
988
|
+
# Although agent termination cancels the check_status_timer, this method could induce
|
989
|
+
# termination, therefore the termination status needs to be checked before each step
|
990
|
+
#
|
991
|
+
# === Return
|
992
|
+
# true:: Always return true
|
993
|
+
def check_status
|
994
|
+
begin
|
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
|
1006
|
+
rescue Exception => e
|
1007
|
+
Log.error("Failed finishing setup", e)
|
1008
|
+
@exception_stats.track("check status", e)
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
begin
|
1012
|
+
@client.queue_status(@queues, timeout = @options[:check_interval] / 10) unless @terminating || @mode == :http
|
1013
|
+
rescue Exception => e
|
1014
|
+
Log.error("Failed checking queue status", e)
|
1015
|
+
@exception_stats.track("check status", e)
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
begin
|
1019
|
+
publish_stats unless @terminating || @stats_routing_key.nil?
|
1020
|
+
rescue Exceptions::ConnectivityFailure => e
|
1021
|
+
Log.error("Failed publishing stats", e, :no_trace)
|
1022
|
+
rescue Exception => e
|
1023
|
+
Log.error("Failed publishing stats", e)
|
1024
|
+
@exception_stats.track("check status", e)
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
begin
|
1028
|
+
check_other(@check_status_count) unless @terminating
|
1029
|
+
rescue Exception => e
|
1030
|
+
Log.error("Failed to perform other check status check", e)
|
1031
|
+
@exception_stats.track("check status", e)
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
@check_status_count += 1
|
1035
|
+
true
|
1036
|
+
end
|
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
|
+
|
1054
|
+
# Allow derived classes to perform any other useful periodic checks
|
1055
|
+
#
|
1056
|
+
# === Parameters
|
1057
|
+
# check_status_count(Integer):: Counter that is incremented for each status check
|
1058
|
+
#
|
1059
|
+
# === Return
|
1060
|
+
# true:: Always return true
|
1061
|
+
def check_other(check_status_count)
|
1062
|
+
true
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
# Store unique tags
|
1066
|
+
#
|
1067
|
+
# === Parameters
|
1068
|
+
# tags(Array):: Tags to be added
|
1069
|
+
#
|
1070
|
+
# === Return
|
1071
|
+
# @tags(Array):: Current tags
|
1072
|
+
def tag(*tags)
|
1073
|
+
tags.each {|t| @tags << t}
|
1074
|
+
@tags.uniq!
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
# Gracefully stop processing
|
1078
|
+
# Close clients except for authorization
|
1079
|
+
#
|
1080
|
+
# === Parameters
|
1081
|
+
# timeout(Integer):: Maximum number of seconds to wait after last request received before
|
1082
|
+
# terminating regardless of whether there are still unfinished requests
|
1083
|
+
#
|
1084
|
+
# === Return
|
1085
|
+
# true:: Always return true
|
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)
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
# Finish termination after all requests have been processed
|
1096
|
+
#
|
1097
|
+
# === Parameters
|
1098
|
+
# timeout(Integer):: Maximum number of seconds to wait after last request received before
|
1099
|
+
# terminating regardless of whether there are still unfinished requests
|
1100
|
+
#
|
1101
|
+
# === Return
|
1102
|
+
# true:: Always return true
|
1103
|
+
def finish_terminating(timeout)
|
1104
|
+
if @sender
|
1105
|
+
request_count, request_age = @sender.terminate
|
1106
|
+
|
1107
|
+
finish = lambda do
|
1108
|
+
request_count, request_age = @sender.terminate
|
1109
|
+
Log.info("[stop] The following #{request_count} requests initiated as recently as #{request_age} " +
|
1110
|
+
"seconds ago are being dropped:\n " + @sender.dump_requests.join("\n ")) if request_age
|
1111
|
+
if @mode == :http
|
1112
|
+
@terminate_callback.call
|
1113
|
+
else
|
1114
|
+
@client.close { @terminate_callback.call }
|
1115
|
+
end
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
if (wait_time = [timeout - (request_age || timeout), 0].max) > 0
|
1119
|
+
Log.info("[stop] Termination waiting #{wait_time} seconds for completion of #{request_count} " +
|
1120
|
+
"requests initiated as recently as #{request_age} seconds ago")
|
1121
|
+
@termination_timer = EM::Timer.new(wait_time) do
|
1122
|
+
begin
|
1123
|
+
Log.info("[stop] Continuing with termination")
|
1124
|
+
finish.call
|
1125
|
+
rescue Exception => e
|
1126
|
+
Log.error("Failed while finishing termination", e, :trace)
|
1127
|
+
begin @terminate_callback.call; rescue Exception; end
|
1128
|
+
end
|
1129
|
+
end
|
1130
|
+
else
|
1131
|
+
finish.call
|
1132
|
+
end
|
1133
|
+
else
|
1134
|
+
@terminate_callback.call
|
1135
|
+
end
|
1136
|
+
@history.update("graceful exit")
|
1137
|
+
true
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
# Determine current revision of software
|
1141
|
+
#
|
1142
|
+
# === Return
|
1143
|
+
# (String):: Revision of software in displayable format
|
1144
|
+
def revision
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
end # Agent
|
1148
|
+
|
1149
|
+
end # RightScale
|