right_agent 0.5.1
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 +78 -0
- data/Rakefile +86 -0
- data/lib/right_agent.rb +66 -0
- data/lib/right_agent/actor.rb +163 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +189 -0
- data/lib/right_agent/agent.rb +735 -0
- data/lib/right_agent/agent_config.rb +403 -0
- data/lib/right_agent/agent_identity.rb +209 -0
- data/lib/right_agent/agent_tags_manager.rb +213 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/broker_client.rb +683 -0
- data/lib/right_agent/command.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +134 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +42 -0
- data/lib/right_agent/command/command_io.rb +128 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +105 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +42 -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 +90 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +62 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -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 +73 -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/dispatcher.rb +348 -0
- data/lib/right_agent/enrollment_result.rb +217 -0
- data/lib/right_agent/exceptions.rb +30 -0
- data/lib/right_agent/ha_broker_client.rb +1278 -0
- data/lib/right_agent/idempotent_request.rb +140 -0
- data/lib/right_agent/log.rb +418 -0
- data/lib/right_agent/monkey_patches.rb +29 -0
- data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -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/singleton_patch.rb +46 -0
- data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -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 +90 -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 +91 -0
- data/lib/right_agent/operation_result.rb +270 -0
- data/lib/right_agent/packets.rb +637 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +319 -0
- data/lib/right_agent/platform/darwin.rb +227 -0
- data/lib/right_agent/platform/linux.rb +268 -0
- data/lib/right_agent/platform/windows.rb +1204 -0
- data/lib/right_agent/scripts/agent_controller.rb +522 -0
- data/lib/right_agent/scripts/agent_deployer.rb +379 -0
- data/lib/right_agent/scripts/common_parser.rb +153 -0
- data/lib/right_agent/scripts/log_level_manager.rb +193 -0
- data/lib/right_agent/scripts/stats_manager.rb +256 -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 +63 -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 +84 -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 +69 -0
- data/lib/right_agent/sender.rb +937 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +102 -0
- data/lib/right_agent/serialize/secure_serializer.rb +131 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +135 -0
- data/lib/right_agent/serialize/serializer.rb +149 -0
- data/lib/right_agent/stats_helper.rb +731 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +60 -0
- data/spec/actor_registry_spec.rb +81 -0
- data/spec/actor_spec.rb +99 -0
- data/spec/agent_config_spec.rb +226 -0
- data/spec/agent_identity_spec.rb +75 -0
- data/spec/agent_spec.rb +571 -0
- data/spec/broker_client_spec.rb +961 -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 +72 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
- data/spec/core_payload_types/login_user_spec.rb +98 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatcher_spec.rb +372 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/ha_broker_client_spec.rb +1673 -0
- data/spec/idempotent_request_spec.rb +136 -0
- data/spec/log_spec.rb +177 -0
- data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/monkey_patches/string_patch_spec.rb +99 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/operation_result_spec.rb +171 -0
- data/spec/packets_spec.rb +418 -0
- data/spec/platform/platform_spec.rb +60 -0
- data/spec/results_mock.rb +45 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +56 -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 +52 -0
- data/spec/sender_spec.rb +887 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +102 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +174 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/stats_helper_spec.rb +681 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +320 -0
|
@@ -0,0 +1,189 @@
|
|
|
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 :ping, :stats, :set_log_level, :execute, :connect, :disconnect, :terminate
|
|
34
|
+
|
|
35
|
+
# Valid log levels
|
|
36
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal]
|
|
37
|
+
|
|
38
|
+
# Initialize broker
|
|
39
|
+
#
|
|
40
|
+
# === Parameters
|
|
41
|
+
# agent(RightScale::Agent):: This agent
|
|
42
|
+
def initialize(agent)
|
|
43
|
+
@agent = agent
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Always return success along with identity, protocol version, and broker information
|
|
47
|
+
# Used for troubleshooting
|
|
48
|
+
#
|
|
49
|
+
# === Return
|
|
50
|
+
# (RightScale::OperationResult):: Always returns success
|
|
51
|
+
def ping(_)
|
|
52
|
+
success_result(:identity => @agent.options[:identity],
|
|
53
|
+
:hostname => Socket.gethostname,
|
|
54
|
+
:version => RightScale::AgentConfig.protocol_version,
|
|
55
|
+
:brokers => @agent.broker.status,
|
|
56
|
+
:time => Time.now.to_i)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Retrieve statistics about agent operation
|
|
60
|
+
#
|
|
61
|
+
# === Parameters:
|
|
62
|
+
# options(Hash):: Request options:
|
|
63
|
+
# :reset(Boolean):: Whether to reset the statistics after getting the current ones
|
|
64
|
+
#
|
|
65
|
+
# === Return
|
|
66
|
+
# (RightScale::OperationResult):: Always returns success
|
|
67
|
+
def stats(options)
|
|
68
|
+
@agent.stats(RightScale::SerializationHelper.symbolize_keys(options))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Change log level of agent
|
|
72
|
+
#
|
|
73
|
+
# === Parameter
|
|
74
|
+
# level(Symbol|String):: One of :debug, :info, :warn, :error, :fatal
|
|
75
|
+
#
|
|
76
|
+
# === Return
|
|
77
|
+
# (RightScale::OperationResult):: Success if level was changed, error otherwise
|
|
78
|
+
def set_log_level(level)
|
|
79
|
+
level = level.to_sym if level.is_a?(String)
|
|
80
|
+
if LEVELS.include?(level)
|
|
81
|
+
RightScale::Log.level = level
|
|
82
|
+
success_result
|
|
83
|
+
else
|
|
84
|
+
error_result("Invalid log level '#{level.to_s}'")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Eval given code in context of agent
|
|
89
|
+
#
|
|
90
|
+
# === Parameter
|
|
91
|
+
# code(String):: Code to be eval'd
|
|
92
|
+
#
|
|
93
|
+
# === Return
|
|
94
|
+
# (RightScale::OperationResult):: Success with result if code didn't raise an exception,
|
|
95
|
+
# otherwise failure with exception message
|
|
96
|
+
def execute(code)
|
|
97
|
+
begin
|
|
98
|
+
success_result(self.instance_eval(code))
|
|
99
|
+
rescue Exception => e
|
|
100
|
+
error_result(e.message + " at\n " + e.backtrace.join("\n "))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Connect agent to an additional broker or reconnect it if connection has failed
|
|
105
|
+
# Assumes agent already has credentials on this broker and identity queue exists
|
|
106
|
+
#
|
|
107
|
+
# === Parameters
|
|
108
|
+
# options(Hash):: Connect options:
|
|
109
|
+
# :host(String):: Host name of broker
|
|
110
|
+
# :port(Integer):: Port number of broker
|
|
111
|
+
# :id(Integer):: Small unique id associated with this broker for use in forming alias
|
|
112
|
+
# :priority(Integer|nil):: Priority position of this broker in list for use
|
|
113
|
+
# by this agent with nil meaning add to end of list
|
|
114
|
+
# :force(Boolean):: Reconnect even if already connected
|
|
115
|
+
#
|
|
116
|
+
# === Return
|
|
117
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
|
118
|
+
def connect(options)
|
|
119
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
|
120
|
+
res = success_result
|
|
121
|
+
begin
|
|
122
|
+
if error = @agent.connect(options[:host], options[:port], options[:id], options[:priority], options[:force])
|
|
123
|
+
res = error_result(error)
|
|
124
|
+
end
|
|
125
|
+
rescue Exception => e
|
|
126
|
+
res = error_result("Failed to connect to broker: #{e.message}")
|
|
127
|
+
end
|
|
128
|
+
res
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Disconnect agent from a broker
|
|
132
|
+
#
|
|
133
|
+
# === Parameters
|
|
134
|
+
# options(Hash):: Connect options:
|
|
135
|
+
# :host(String):: Host name of broker
|
|
136
|
+
# :port(Integer):: Port number of broker
|
|
137
|
+
# :remove(Boolean):: Remove broker from configuration in addition to disconnecting it
|
|
138
|
+
#
|
|
139
|
+
# === Return
|
|
140
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
|
141
|
+
def disconnect(options)
|
|
142
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
|
143
|
+
res = success_result
|
|
144
|
+
begin
|
|
145
|
+
if error = @agent.disconnect(options[:host], options[:port], options[:remove])
|
|
146
|
+
res = error_result(error)
|
|
147
|
+
end
|
|
148
|
+
rescue Exception => e
|
|
149
|
+
res = error_result("Failed to disconnect from broker: #{e.message}")
|
|
150
|
+
end
|
|
151
|
+
res
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Declare one or more broker connections unusable because connection setup has failed
|
|
155
|
+
#
|
|
156
|
+
# === Parameters
|
|
157
|
+
# options(Hash):: Failure options:
|
|
158
|
+
# :brokers(Array):: Identity of brokers
|
|
159
|
+
#
|
|
160
|
+
# === Return
|
|
161
|
+
# res(RightScale::OperationResult):: Success unless exception is raised
|
|
162
|
+
def connect_failed(options)
|
|
163
|
+
options = RightScale::SerializationHelper.symbolize_keys(options)
|
|
164
|
+
res = success_result
|
|
165
|
+
begin
|
|
166
|
+
if error = @agent.connect_failed(options[:brokers])
|
|
167
|
+
res = error_result(error)
|
|
168
|
+
end
|
|
169
|
+
rescue Exception => e
|
|
170
|
+
res = error_result("Failed to notify agent that brokers #{options[:brokers]} are unusable: #{e.message}")
|
|
171
|
+
end
|
|
172
|
+
res
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Terminate self
|
|
176
|
+
#
|
|
177
|
+
# === Parameters
|
|
178
|
+
# options(Hash):: Terminate options
|
|
179
|
+
#
|
|
180
|
+
# === Return
|
|
181
|
+
# true
|
|
182
|
+
def terminate(options = nil)
|
|
183
|
+
RightScale::CommandRunner.stop
|
|
184
|
+
# Delay terminate a bit to give reply a chance to be sent
|
|
185
|
+
EM.next_tick { @agent.terminate }
|
|
186
|
+
true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
end
|
|
@@ -0,0 +1,735 @@
|
|
|
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
|
+
module RightScale
|
|
26
|
+
|
|
27
|
+
# Agent for receiving messages from the mapper 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
|
+
class Agent
|
|
31
|
+
|
|
32
|
+
include ConsoleHelper
|
|
33
|
+
include DaemonizeHelper
|
|
34
|
+
include StatsHelper
|
|
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
|
+
# (Dispatcher) Dispatcher for messages received
|
|
43
|
+
attr_reader :dispatcher
|
|
44
|
+
|
|
45
|
+
# (ActorRegistry) Registry for this agents actors
|
|
46
|
+
attr_reader :registry
|
|
47
|
+
|
|
48
|
+
# (HABrokerClient) High availability AMQP broker client
|
|
49
|
+
attr_reader :broker
|
|
50
|
+
|
|
51
|
+
# (Array) Tag strings published by agent
|
|
52
|
+
attr_accessor :tags
|
|
53
|
+
|
|
54
|
+
# (Proc) Callback procedure for exceptions
|
|
55
|
+
attr_reader :exception_callback
|
|
56
|
+
|
|
57
|
+
# Default option settings for the agent
|
|
58
|
+
DEFAULT_OPTIONS = {
|
|
59
|
+
:user => 'agent',
|
|
60
|
+
:pass => 'testing',
|
|
61
|
+
:vhost => '/right_net',
|
|
62
|
+
:secure => true,
|
|
63
|
+
:log_level => :info,
|
|
64
|
+
:daemonize => false,
|
|
65
|
+
:console => false,
|
|
66
|
+
:root_dir => Dir.pwd,
|
|
67
|
+
:time_to_live => 0,
|
|
68
|
+
:retry_interval => nil,
|
|
69
|
+
:retry_timeout => nil,
|
|
70
|
+
:connect_timeout => 60,
|
|
71
|
+
:reconnect_interval => 60,
|
|
72
|
+
:ping_interval => 0,
|
|
73
|
+
:check_interval => 5 * 60,
|
|
74
|
+
:grace_timeout => 30,
|
|
75
|
+
:prefetch => 1,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Initializes a new agent and establishes an AMQP connection.
|
|
79
|
+
# This must be used inside EM.run block or if EventMachine reactor
|
|
80
|
+
# is already started, for instance, by a Thin server that your Merb/Rails
|
|
81
|
+
# application runs on.
|
|
82
|
+
#
|
|
83
|
+
# === Parameters
|
|
84
|
+
# opts(Hash):: Configuration options:
|
|
85
|
+
# :identity(String):: Identity of this agent, no default
|
|
86
|
+
# :agent_name(String):: Local name for this agent
|
|
87
|
+
# :root_dir(String):: Application root for this agent containing subdirectories actors, certs, and init,
|
|
88
|
+
# defaults to current working directory
|
|
89
|
+
# :pid_dir(String):: Path to the directory where the agent stores its process id file (only if daemonized),
|
|
90
|
+
# defaults to the current working directory
|
|
91
|
+
# :log_dir(String):: Log directory path, defaults to the platform specific log directory
|
|
92
|
+
# :log_level(Symbol):: The verbosity of logging -- :debug, :info, :warn, :error or :fatal
|
|
93
|
+
# :actors(Array):: List of actors to load
|
|
94
|
+
# :console(Boolean):: true indicates to start interactive console
|
|
95
|
+
# :daemonize(Boolean):: true indicates to daemonize
|
|
96
|
+
# :retry_interval(Numeric):: Number of seconds between request retries
|
|
97
|
+
# :retry_timeout(Numeric):: Maximum number of seconds to retry request before give up
|
|
98
|
+
# :time_to_live(Integer):: Number of seconds before a request expires and is to be ignored
|
|
99
|
+
# by the receiver, 0 means never expire, defaults to 0
|
|
100
|
+
# :connect_timeout:: Number of seconds to wait for a broker connection to be established
|
|
101
|
+
# :reconnect_interval(Integer):: Number of seconds between broker reconnect attempts
|
|
102
|
+
# :ping_interval(Integer):: Minimum number of seconds since last message receipt to ping the mapper
|
|
103
|
+
# to check connectivity, defaults to 0 meaning do not ping
|
|
104
|
+
# :check_interval:: Number of seconds between publishing stats and checking for broker connections
|
|
105
|
+
# that failed during agent launch and then attempting to reconnect via the mapper
|
|
106
|
+
# :grace_timeout(Integer):: Maximum number of seconds to wait after last request received before
|
|
107
|
+
# terminating regardless of whether there are still unfinished requests
|
|
108
|
+
# :dup_check(Boolean):: Whether to check for and reject duplicate requests, e.g., due to retries
|
|
109
|
+
# :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for this agent
|
|
110
|
+
# before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
|
|
111
|
+
# if the agent crashes. Value 0 means unlimited prefetch.
|
|
112
|
+
# :exception_callback(Proc):: Callback with following parameters that is activated on exception events:
|
|
113
|
+
# exception(Exception):: Exception
|
|
114
|
+
# message(Packet):: Message being processed
|
|
115
|
+
# agent(Agent):: Reference to agent
|
|
116
|
+
# :ready_callback(Proc):: Called once agent is connected ready to service (no argument)
|
|
117
|
+
# :restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
|
|
118
|
+
# by offline queue exceeding MAX_QUEUED_REQUESTS or by repeated failures to access mapper when online
|
|
119
|
+
# :services(Symbol):: List of services provided by this agent. Defaults to all methods exposed by actors.
|
|
120
|
+
# :secure(Boolean):: true indicates to use security features of RabbitMQ to restrict agents to themselves
|
|
121
|
+
# :single_threaded(Boolean):: true indicates to run all operations in one thread; false indicates
|
|
122
|
+
# to do requested work on EM defer thread and all else on main thread
|
|
123
|
+
# :threadpool_size(Integer):: Number of threads in EM thread pool
|
|
124
|
+
# :vhost(String):: AMQP broker virtual host
|
|
125
|
+
# :user(String):: AMQP broker user
|
|
126
|
+
# :pass(String):: AMQP broker password
|
|
127
|
+
# :host(String):: Comma-separated list of AMQP broker hosts; if only one, it is reapplied
|
|
128
|
+
# to successive ports; if none, defaults to 'localhost'
|
|
129
|
+
# :port(Integer):: Comma-separated list of AMQP broker ports corresponding to hosts; if only one,
|
|
130
|
+
# it is incremented and applied to successive hosts; if none, defaults to AMQP::HOST
|
|
131
|
+
#
|
|
132
|
+
# On start config.yml is read, so it is common to specify options in the YAML file. However, when both
|
|
133
|
+
# Ruby code options and YAML file specify options, Ruby code options take precedence.
|
|
134
|
+
#
|
|
135
|
+
# === Return
|
|
136
|
+
# agent(Agent):: New agent
|
|
137
|
+
def self.start(opts = {})
|
|
138
|
+
agent = new(opts)
|
|
139
|
+
agent.run
|
|
140
|
+
agent
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Initialize the new agent
|
|
144
|
+
#
|
|
145
|
+
# === Parameters
|
|
146
|
+
# opts(Hash):: Configuration options per #start above
|
|
147
|
+
#
|
|
148
|
+
# === Return
|
|
149
|
+
# true:: Always return true
|
|
150
|
+
def initialize(opts)
|
|
151
|
+
set_configuration(opts)
|
|
152
|
+
@tags = []
|
|
153
|
+
@tags << opts[:tag] if opts[:tag]
|
|
154
|
+
@tags.flatten!
|
|
155
|
+
@options.freeze
|
|
156
|
+
@terminating = false
|
|
157
|
+
@last_stat_reset_time = @service_start_time = Time.now
|
|
158
|
+
reset_agent_stats
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Put the agent in service
|
|
163
|
+
#
|
|
164
|
+
# === Return
|
|
165
|
+
# true:: Always return true
|
|
166
|
+
def run
|
|
167
|
+
Log.init(@identity, @options[:log_path], :print => true)
|
|
168
|
+
Log.level = @options[:log_level] if @options[:log_level]
|
|
169
|
+
Log.debug("Start options:")
|
|
170
|
+
log_opts = @options.inject([]){ |t, (k, v)| t << "- #{k}: #{v}" }
|
|
171
|
+
log_opts.each { |l| Log.debug(l) }
|
|
172
|
+
|
|
173
|
+
begin
|
|
174
|
+
# Capture process id in file after optional daemonize
|
|
175
|
+
pid_file = PidFile.new(@identity)
|
|
176
|
+
pid_file.check
|
|
177
|
+
daemonize(@identity, @options) if @options[:daemonize]
|
|
178
|
+
pid_file.write
|
|
179
|
+
at_exit { pid_file.remove }
|
|
180
|
+
|
|
181
|
+
# Initiate AMQP broker connection, wait for connection before proceeding
|
|
182
|
+
# otherwise messages published on failed connection will be lost
|
|
183
|
+
@broker = HABrokerClient.new(Serializer.new(:secure), @options)
|
|
184
|
+
@all_setup.each { |s| @remaining_setup[s] = @broker.all }
|
|
185
|
+
@broker.connection_status(:one_off => @options[:connect_timeout]) do |status|
|
|
186
|
+
if status == :connected
|
|
187
|
+
# Need to give EM (on Windows) a chance to respond to the AMQP handshake
|
|
188
|
+
# before doing anything interesting to prevent AMQP handshake from
|
|
189
|
+
# timing-out; delay post-connected activity a second.
|
|
190
|
+
EM.add_timer(1) do
|
|
191
|
+
begin
|
|
192
|
+
@registry = ActorRegistry.new
|
|
193
|
+
@dispatcher = Dispatcher.new(self)
|
|
194
|
+
@sender = Sender.new(self)
|
|
195
|
+
load_actors
|
|
196
|
+
setup_traps
|
|
197
|
+
setup_queues
|
|
198
|
+
start_console if @options[:console] && !@options[:daemonize]
|
|
199
|
+
|
|
200
|
+
# Need to keep reconnect interval at least :connect_timeout in size,
|
|
201
|
+
# otherwise connection_status callback will not timeout prior to next
|
|
202
|
+
# reconnect attempt, which can result in repeated attempts to setup
|
|
203
|
+
# queues when finally do connect
|
|
204
|
+
interval = [@options[:check_interval], @options[:connect_timeout]].max
|
|
205
|
+
@check_status_count = 0
|
|
206
|
+
@check_status_brokers = @broker.all
|
|
207
|
+
EM.next_tick { @options[:ready_callback].call } if @options[:ready_callback]
|
|
208
|
+
@check_status_timer = EM::PeriodicTimer.new(interval) { check_status }
|
|
209
|
+
rescue Exception => e
|
|
210
|
+
Log.error("Agent failed startup", e, :trace) unless e.message == "exit"
|
|
211
|
+
EM.stop
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
elsif status == :failed
|
|
215
|
+
Log.error("Agent failed to connect to any brokers")
|
|
216
|
+
EM.stop
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
rescue SystemExit => e
|
|
220
|
+
raise e
|
|
221
|
+
rescue PidFile::AlreadyRunning
|
|
222
|
+
raise
|
|
223
|
+
rescue Exception => e
|
|
224
|
+
Log.error("Agent failed startup", e, :trace) unless e.message == "exit"
|
|
225
|
+
raise e
|
|
226
|
+
end
|
|
227
|
+
true
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Register an actor for this agent
|
|
231
|
+
#
|
|
232
|
+
# === Parameters
|
|
233
|
+
# actor(Actor):: Actor to be registered
|
|
234
|
+
# prefix(String):: Prefix to be used in place of actor's default_prefix
|
|
235
|
+
#
|
|
236
|
+
# === Return
|
|
237
|
+
# (Actor):: Actor registered
|
|
238
|
+
def register(actor, prefix = nil)
|
|
239
|
+
@registry.register(actor, prefix)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Connect to an additional broker or reconnect it if connection has failed
|
|
243
|
+
# Subscribe to identity queue on this broker
|
|
244
|
+
# Update config file if this is a new broker
|
|
245
|
+
# Assumes already has credentials on this broker and identity queue exists
|
|
246
|
+
#
|
|
247
|
+
# === Parameters
|
|
248
|
+
# host(String):: Host name of broker
|
|
249
|
+
# port(Integer):: Port number of broker
|
|
250
|
+
# index(Integer):: Small unique id associated with this broker for use in forming alias
|
|
251
|
+
# priority(Integer|nil):: Priority position of this broker in list for use
|
|
252
|
+
# by this agent with nil meaning add to end of list
|
|
253
|
+
# force(Boolean):: Reconnect even if already connected
|
|
254
|
+
#
|
|
255
|
+
# === Return
|
|
256
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
|
257
|
+
def connect(host, port, index, priority = nil, force = false)
|
|
258
|
+
@connect_requests.update("connect b#{index}")
|
|
259
|
+
even_if = " even if already connected" if force
|
|
260
|
+
Log.info("Connecting to broker at host #{host.inspect} port #{port.inspect} " +
|
|
261
|
+
"index #{index.inspect} priority #{priority.inspect}#{even_if}")
|
|
262
|
+
Log.info("Current broker configuration: #{@broker.status.inspect}")
|
|
263
|
+
res = nil
|
|
264
|
+
begin
|
|
265
|
+
@broker.connect(host, port, index, priority, nil, force) do |id|
|
|
266
|
+
@broker.connection_status(:one_off => @options[:connect_timeout], :brokers => [id]) do |status|
|
|
267
|
+
begin
|
|
268
|
+
if status == :connected
|
|
269
|
+
setup_queues([id])
|
|
270
|
+
remaining = 0
|
|
271
|
+
@remaining_setup.each_value { |ids| remaining += ids.size }
|
|
272
|
+
Log.info("[setup] Finished subscribing to queues after reconnecting to broker #{id}") if remaining == 0
|
|
273
|
+
unless update_configuration(:host => @broker.hosts, :port => @broker.ports)
|
|
274
|
+
Log.warning("Successfully connected to broker #{id} but failed to update config file")
|
|
275
|
+
end
|
|
276
|
+
else
|
|
277
|
+
Log.error("Failed to connect to broker #{id}, status #{status.inspect}")
|
|
278
|
+
end
|
|
279
|
+
rescue Exception => e
|
|
280
|
+
Log.error("Failed to connect to broker #{id}, status #{status.inspect}", e)
|
|
281
|
+
@exceptions.track("connect", e)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
rescue Exception => e
|
|
286
|
+
res = Log.format("Failed to connect to broker #{HABrokerClient.identity(host, port)}", e)
|
|
287
|
+
@exceptions.track("connect", e)
|
|
288
|
+
end
|
|
289
|
+
Log.error(res) if res
|
|
290
|
+
res
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Disconnect from a broker and optionally remove it from the configuration
|
|
294
|
+
# Refuse to do so if it is the last connected broker
|
|
295
|
+
#
|
|
296
|
+
# === Parameters
|
|
297
|
+
# host(String):: Host name of broker
|
|
298
|
+
# port(Integer):: Port number of broker
|
|
299
|
+
# remove(Boolean):: Whether to remove broker from configuration rather than just closing it,
|
|
300
|
+
# defaults to false
|
|
301
|
+
#
|
|
302
|
+
# === Return
|
|
303
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
|
304
|
+
def disconnect(host, port, remove = false)
|
|
305
|
+
and_remove = " and removing" if remove
|
|
306
|
+
Log.info("Disconnecting#{and_remove} broker at host #{host.inspect} port #{port.inspect}")
|
|
307
|
+
Log.info("Current broker configuration: #{@broker.status.inspect}")
|
|
308
|
+
id = HABrokerClient.identity(host, port)
|
|
309
|
+
@connect_requests.update("disconnect #{@broker.alias_(id)}")
|
|
310
|
+
connected = @broker.connected
|
|
311
|
+
res = nil
|
|
312
|
+
if connected.include?(id) && connected.size == 1
|
|
313
|
+
res = "Not disconnecting from #{id} because it is the last connected broker for this agent"
|
|
314
|
+
elsif @broker.get(id)
|
|
315
|
+
begin
|
|
316
|
+
if remove
|
|
317
|
+
@broker.remove(host, port) do |id|
|
|
318
|
+
unless update_configuration(:host => @broker.hosts, :port => @broker.ports)
|
|
319
|
+
res = "Successfully disconnected from broker #{id} but failed to update config file"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
@broker.close_one(id)
|
|
324
|
+
end
|
|
325
|
+
rescue Exception => e
|
|
326
|
+
res = Log.format("Failed to disconnect from broker #{id}", e)
|
|
327
|
+
@exceptions.track("disconnect", e)
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
res = "Cannot disconnect from broker #{id} because not configured for this agent"
|
|
331
|
+
end
|
|
332
|
+
Log.error(res) if res
|
|
333
|
+
res
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# There were problems while setting up service for this agent on the given brokers,
|
|
337
|
+
# so mark these brokers as failed if not currently connected and later, during the
|
|
338
|
+
# periodic status check, attempt to reconnect
|
|
339
|
+
#
|
|
340
|
+
# === Parameters
|
|
341
|
+
# ids(Array):: Identity of brokers
|
|
342
|
+
#
|
|
343
|
+
# === Return
|
|
344
|
+
# res(String|nil):: Error message if failed, otherwise nil
|
|
345
|
+
def connect_failed(ids)
|
|
346
|
+
aliases = @broker.aliases(ids).join(", ")
|
|
347
|
+
@connect_requests.update("enroll failed #{aliases}")
|
|
348
|
+
res = nil
|
|
349
|
+
begin
|
|
350
|
+
Log.info("Received indication that service initialization for this agent for brokers #{ids.inspect} has failed")
|
|
351
|
+
connected = @broker.connected
|
|
352
|
+
ignored = connected & ids
|
|
353
|
+
Log.info("Not marking brokers #{ignored.inspect} as unusable because currently connected") if ignored
|
|
354
|
+
Log.info("Current broker configuration: #{@broker.status.inspect}")
|
|
355
|
+
@broker.declare_unusable(ids - ignored)
|
|
356
|
+
rescue Exception => e
|
|
357
|
+
res = Log.format("Failed handling broker connection failure indication for #{ids.inspect}", e)
|
|
358
|
+
Log.error(res)
|
|
359
|
+
@exceptions.track("connect failed", e)
|
|
360
|
+
end
|
|
361
|
+
res
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Handle packet received
|
|
365
|
+
#
|
|
366
|
+
# === Parameters
|
|
367
|
+
# packet(Request|Push|Result):: Packet received
|
|
368
|
+
#
|
|
369
|
+
# === Return
|
|
370
|
+
# true:: Always return true
|
|
371
|
+
def receive(packet)
|
|
372
|
+
begin
|
|
373
|
+
case packet
|
|
374
|
+
when Push, Request then @dispatcher.dispatch(packet) unless @terminating
|
|
375
|
+
when Result then @sender.handle_response(packet)
|
|
376
|
+
end
|
|
377
|
+
@sender.message_received
|
|
378
|
+
rescue HABrokerClient::NoConnectedBrokers => e
|
|
379
|
+
Log.error("Identity queue processing error", e)
|
|
380
|
+
rescue Exception => e
|
|
381
|
+
Log.error("Identity queue processing error", e, :trace)
|
|
382
|
+
@exceptions.track("identity queue", e, packet)
|
|
383
|
+
end
|
|
384
|
+
true
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Gracefully terminate execution by allowing unfinished tasks to complete
|
|
388
|
+
# Immediately terminate if called a second time
|
|
389
|
+
#
|
|
390
|
+
# === Block
|
|
391
|
+
# Optional block to be executed after termination is complete
|
|
392
|
+
#
|
|
393
|
+
# === Return
|
|
394
|
+
# true:: Always return true
|
|
395
|
+
def terminate(&blk)
|
|
396
|
+
begin
|
|
397
|
+
if @terminating
|
|
398
|
+
Log.info("[stop] Terminating immediately")
|
|
399
|
+
@termination_timer.cancel if @termination_timer
|
|
400
|
+
@termination_timer = nil
|
|
401
|
+
if blk then blk.call else EM.stop end
|
|
402
|
+
else
|
|
403
|
+
@terminating = true
|
|
404
|
+
@check_status_timer.cancel if @check_status_timer
|
|
405
|
+
@check_status_timer = nil
|
|
406
|
+
timeout = @options[:grace_timeout]
|
|
407
|
+
Log.info("[stop] Agent #{@identity} terminating")
|
|
408
|
+
|
|
409
|
+
stop_gracefully(timeout) do
|
|
410
|
+
if @sender
|
|
411
|
+
dispatch_age = @dispatcher.dispatch_age
|
|
412
|
+
request_count, request_age = @sender.terminate
|
|
413
|
+
|
|
414
|
+
finish = lambda do
|
|
415
|
+
request_count, request_age = @sender.terminate
|
|
416
|
+
Log.info("[stop] The following #{request_count} requests initiated as recently as #{request_age} " +
|
|
417
|
+
"seconds ago are being dropped:\n " + @sender.dump_requests.join("\n ")) if request_age
|
|
418
|
+
@broker.close(&blk)
|
|
419
|
+
EM.stop unless blk
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
wait_time = [timeout - (request_age || timeout), timeout - (dispatch_age || timeout), 0].max
|
|
423
|
+
if wait_time == 0
|
|
424
|
+
finish.call
|
|
425
|
+
else
|
|
426
|
+
reason = ""
|
|
427
|
+
reason = "completion of #{request_count} requests initiated as recently as #{request_age} seconds ago" if request_age
|
|
428
|
+
reason += " and " if request_age && dispatch_age
|
|
429
|
+
reason += "requests received as recently as #{dispatch_age} seconds ago" if dispatch_age
|
|
430
|
+
Log.info("[stop] Termination waiting #{wait_time} seconds for #{reason}")
|
|
431
|
+
@termination_timer = EM::Timer.new(wait_time) do
|
|
432
|
+
begin
|
|
433
|
+
Log.info("[stop] Continuing with termination")
|
|
434
|
+
finish.call
|
|
435
|
+
rescue Exception => e
|
|
436
|
+
Log.error("Failed while finishing termination", e, :trace)
|
|
437
|
+
EM.stop
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
rescue Exception => e
|
|
445
|
+
Log.error("Failed to terminate gracefully", e, :trace)
|
|
446
|
+
EM.stop
|
|
447
|
+
end
|
|
448
|
+
true
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Retrieve statistics about agent operation
|
|
452
|
+
#
|
|
453
|
+
# === Parameters:
|
|
454
|
+
# options(Hash):: Request options:
|
|
455
|
+
# :reset(Boolean):: Whether to reset the statistics after getting the current ones
|
|
456
|
+
#
|
|
457
|
+
# === Return
|
|
458
|
+
# result(OperationResult):: Always returns success
|
|
459
|
+
def stats(options = {})
|
|
460
|
+
now = Time.now
|
|
461
|
+
reset = options[:reset]
|
|
462
|
+
result = OperationResult.success("name" => @agent_name,
|
|
463
|
+
"identity" => @identity,
|
|
464
|
+
"hostname" => Socket.gethostname,
|
|
465
|
+
"version" => AgentConfig.protocol_version,
|
|
466
|
+
"brokers" => @broker.stats(reset),
|
|
467
|
+
"agent stats" => agent_stats(reset),
|
|
468
|
+
"receive stats" => @dispatcher.stats(reset),
|
|
469
|
+
"send stats" => @sender.stats(reset),
|
|
470
|
+
"last reset time" => @last_stat_reset_time.to_i,
|
|
471
|
+
"stat time" => now.to_i,
|
|
472
|
+
"service uptime" => (now - @service_start_time).to_i,
|
|
473
|
+
"machine uptime" => Platform.shell.uptime)
|
|
474
|
+
@last_stat_reset_time = now if reset
|
|
475
|
+
result
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
protected
|
|
479
|
+
|
|
480
|
+
# Get request statistics
|
|
481
|
+
#
|
|
482
|
+
# === Parameters
|
|
483
|
+
# reset(Boolean):: Whether to reset the statistics after getting the current ones
|
|
484
|
+
#
|
|
485
|
+
# === Return
|
|
486
|
+
# stats(Hash):: Current statistics:
|
|
487
|
+
# "connect requests"(Hash|nil):: Stats about requests to update connections with keys "total", "percent",
|
|
488
|
+
# and "last" with percentage breakdown by "connects: <alias>", "disconnects: <alias>", "enroll setup failed:
|
|
489
|
+
# <aliases>", or nil if none
|
|
490
|
+
# "exceptions"(Hash|nil):: Exceptions raised per category, or nil if none
|
|
491
|
+
# "total"(Integer):: Total exceptions for this category
|
|
492
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
|
493
|
+
# "non-deliveries"(Hash):: Message non-delivery activity stats with keys "total", "percent", "last", and "rate"
|
|
494
|
+
# with percentage breakdown by request type, or nil if none
|
|
495
|
+
def agent_stats(reset = false)
|
|
496
|
+
stats = {
|
|
497
|
+
"connect requests" => @connect_requests.all,
|
|
498
|
+
"exceptions" => @exceptions.stats,
|
|
499
|
+
"non-deliveries" => @non_deliveries.all
|
|
500
|
+
}
|
|
501
|
+
reset_agent_stats if reset
|
|
502
|
+
stats
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Reset cache statistics
|
|
506
|
+
#
|
|
507
|
+
# === Return
|
|
508
|
+
# true:: Always return true
|
|
509
|
+
def reset_agent_stats
|
|
510
|
+
@connect_requests = ActivityStats.new(measure_rate = false)
|
|
511
|
+
@non_deliveries = ActivityStats.new
|
|
512
|
+
@exceptions = ExceptionStats.new(self, @options[:exception_callback])
|
|
513
|
+
true
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Set the agent's configuration using the supplied options
|
|
517
|
+
#
|
|
518
|
+
# === Parameters
|
|
519
|
+
# opts(Hash):: Configuration options
|
|
520
|
+
#
|
|
521
|
+
# === Return
|
|
522
|
+
# (String):: Serialized agent identity
|
|
523
|
+
def set_configuration(opts)
|
|
524
|
+
@options = DEFAULT_OPTIONS.clone
|
|
525
|
+
@options.update(opts)
|
|
526
|
+
|
|
527
|
+
AgentConfig.root_dir = @options[:root_dir]
|
|
528
|
+
AgentConfig.pid_dir = @options[:pid_dir]
|
|
529
|
+
|
|
530
|
+
@options[:log_path] = false
|
|
531
|
+
if @options[:daemonize] || @options[:log_dir]
|
|
532
|
+
@options[:log_path] = (@options[:log_dir] || Platform.filesystem.log_dir)
|
|
533
|
+
FileUtils.mkdir_p(@options[:log_path]) unless File.directory?(@options[:log_path])
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
@identity = @options[:identity]
|
|
537
|
+
parsed_identity = AgentIdentity.parse(@identity)
|
|
538
|
+
@agent_type = parsed_identity.agent_type
|
|
539
|
+
@agent_name = @options[:agent_name]
|
|
540
|
+
@stats_routing_key = "stats.#{@agent_type}.#{parsed_identity.base_id}"
|
|
541
|
+
|
|
542
|
+
@remaining_setup = {}
|
|
543
|
+
@all_setup = [:setup_identity_queue]
|
|
544
|
+
@identity
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Update agent's persisted configuration
|
|
548
|
+
# Note that @options are frozen and therefore not updated
|
|
549
|
+
#
|
|
550
|
+
# === Parameters
|
|
551
|
+
# opts(Hash):: Options being updated
|
|
552
|
+
#
|
|
553
|
+
# === Return
|
|
554
|
+
# (Boolean):: true if successful, otherwise false
|
|
555
|
+
def update_configuration(opts)
|
|
556
|
+
if cfg = AgentConfig.load_cfg(@agent_name)
|
|
557
|
+
opts.each { |k, v| cfg[k] = v if cfg.has_key?(k) }
|
|
558
|
+
AgentConfig.store_cfg(@agent_name, cfg)
|
|
559
|
+
true
|
|
560
|
+
else
|
|
561
|
+
Log.error("Could not access configuration file #{AgentConfig.cfg_file(@agent_name).inspect} for update")
|
|
562
|
+
false
|
|
563
|
+
end
|
|
564
|
+
rescue Exception => e
|
|
565
|
+
Log.error("Failed updating configuration file #{AgentConfig.cfg_file(@agent_name).inspect}", e, :trace)
|
|
566
|
+
false
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Load the ruby code for the actors
|
|
570
|
+
#
|
|
571
|
+
# === Return
|
|
572
|
+
# true:: Always return true
|
|
573
|
+
def load_actors
|
|
574
|
+
# Load agent's configured actors
|
|
575
|
+
actors = (@options[:actors] || []).clone
|
|
576
|
+
Log.info("[setup] Agent #{@identity} with actors #{actors.inspect}")
|
|
577
|
+
actors_dirs = AgentConfig.actors_dirs
|
|
578
|
+
actors_dirs.each do |dir|
|
|
579
|
+
Dir["#{dir}/*.rb"].each do |file|
|
|
580
|
+
actor = File.basename(file, ".rb")
|
|
581
|
+
next if actors && !actors.include?(actor)
|
|
582
|
+
Log.info("[setup] loading actor #{file}")
|
|
583
|
+
require file
|
|
584
|
+
actors.delete(actor)
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
Log.error("Actors #{actors.inspect} not found in #{actors_dirs.inspect}") unless actors.empty?
|
|
588
|
+
|
|
589
|
+
# Perform agent-specific initialization including actor creation and registration
|
|
590
|
+
if init_file = AgentConfig.init_file
|
|
591
|
+
Log.info("[setup] initializing agent from #{init_file}")
|
|
592
|
+
instance_eval(File.read(init_file), init_file)
|
|
593
|
+
else
|
|
594
|
+
Log.error("No agent init.rb file found in init directory of #{AgentConfig.root_dir.inspect}")
|
|
595
|
+
end
|
|
596
|
+
true
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Setup the queues on the specified brokers for this agent
|
|
600
|
+
# Also configure message non-delivery handling
|
|
601
|
+
#
|
|
602
|
+
# === Parameters
|
|
603
|
+
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
|
604
|
+
#
|
|
605
|
+
# === Return
|
|
606
|
+
# true:: Always return true
|
|
607
|
+
def setup_queues(ids = nil)
|
|
608
|
+
@broker.non_delivery do |reason, type, token, from, to|
|
|
609
|
+
begin
|
|
610
|
+
@non_deliveries.update(type)
|
|
611
|
+
reason = case reason
|
|
612
|
+
when "NO_ROUTE" then OperationResult::NO_ROUTE_TO_TARGET
|
|
613
|
+
when "NO_CONSUMERS" then OperationResult::TARGET_NOT_CONNECTED
|
|
614
|
+
else reason.to_s
|
|
615
|
+
end
|
|
616
|
+
result = Result.new(token, from, OperationResult.non_delivery(reason), to)
|
|
617
|
+
@sender.handle_response(result)
|
|
618
|
+
rescue Exception => e
|
|
619
|
+
Log.error("Failed handling non-delivery for <#{token}>", e, :trace)
|
|
620
|
+
@exceptions.track("message return", e)
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
# Do the setup regardless of whether remaining setup is empty since may be reconnecting
|
|
624
|
+
@all_setup.each { |setup| @remaining_setup[setup] -= self.__send__(setup, ids) }
|
|
625
|
+
true
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# Setup identity queue for this agent
|
|
629
|
+
#
|
|
630
|
+
# === Parameters
|
|
631
|
+
# ids(Array):: Identity of brokers for which to subscribe, defaults to all usable
|
|
632
|
+
#
|
|
633
|
+
# === Return
|
|
634
|
+
# ids(Array):: Identity of brokers to which subscribe submitted (although may still fail)
|
|
635
|
+
def setup_identity_queue(ids = nil)
|
|
636
|
+
queue = {:name => @identity, :options => {:durable => true, :no_declare => @options[:secure]}}
|
|
637
|
+
filter = [:from, :tags, :tries, :persistent]
|
|
638
|
+
options = {:ack => true, Request => filter, Push => filter, Result => [:from], :brokers => ids}
|
|
639
|
+
ids = @broker.subscribe(queue, nil, options) { |_, packet| receive(packet) }
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Setup signal traps
|
|
643
|
+
#
|
|
644
|
+
# === Return
|
|
645
|
+
# true:: Always return true
|
|
646
|
+
def setup_traps
|
|
647
|
+
['INT', 'TERM'].each do |sig|
|
|
648
|
+
old = trap(sig) do
|
|
649
|
+
EM.next_tick do
|
|
650
|
+
begin
|
|
651
|
+
terminate do
|
|
652
|
+
EM.stop
|
|
653
|
+
old.call if old.is_a? Proc
|
|
654
|
+
end
|
|
655
|
+
rescue Exception => e
|
|
656
|
+
Log.error("Failed in termination", e, :trace)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
true
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# Finish any remaining agent setup
|
|
665
|
+
#
|
|
666
|
+
# === Return
|
|
667
|
+
# true:: Always return true
|
|
668
|
+
def finish_setup
|
|
669
|
+
@broker.failed.each do |id|
|
|
670
|
+
p = {:agent_identity => @identity}
|
|
671
|
+
p[:host], p[:port], p[:id], p[:priority], _ = @broker.identity_parts(id)
|
|
672
|
+
@sender.send_push("/registrar/connect", p)
|
|
673
|
+
end
|
|
674
|
+
true
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Check status of agent by gathering current operation statistics and publishing them and
|
|
678
|
+
# by completing any queue setup that can be completed now based on broker status
|
|
679
|
+
#
|
|
680
|
+
# === Return
|
|
681
|
+
# true:: Always return true
|
|
682
|
+
def check_status
|
|
683
|
+
begin
|
|
684
|
+
finish_setup
|
|
685
|
+
rescue Exception => e
|
|
686
|
+
Log.error("Failed finishing setup", e)
|
|
687
|
+
@exceptions.track("check status", e)
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
begin
|
|
691
|
+
if @stats_routing_key
|
|
692
|
+
exchange = {:type => :topic, :name => "stats", :options => {:no_declare => true}}
|
|
693
|
+
@broker.publish(exchange, Stats.new(stats.content, @identity), :no_log => true,
|
|
694
|
+
:routing_key => @stats_routing_key, :brokers => @check_status_brokers.rotate!)
|
|
695
|
+
end
|
|
696
|
+
rescue Exception => e
|
|
697
|
+
Log.error("Failed publishing stats", e)
|
|
698
|
+
@exceptions.track("check status", e)
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
@check_status_count += 1
|
|
702
|
+
true
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
# Store unique tags
|
|
706
|
+
#
|
|
707
|
+
# === Parameters
|
|
708
|
+
# tags(Array):: Tags to be added
|
|
709
|
+
#
|
|
710
|
+
# === Return
|
|
711
|
+
# @tags(Array):: Current tags
|
|
712
|
+
def tag(*tags)
|
|
713
|
+
tags.each {|t| @tags << t}
|
|
714
|
+
@tags.uniq!
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# Gracefully stop processing
|
|
718
|
+
#
|
|
719
|
+
# === Parameters
|
|
720
|
+
# timeout(Integer):: Maximum number of seconds to wait after last request received before
|
|
721
|
+
# terminating regardless of whether there are still unfinished requests
|
|
722
|
+
#
|
|
723
|
+
# === Block
|
|
724
|
+
# Required block to be executed after stopping message receipt wherever possible
|
|
725
|
+
#
|
|
726
|
+
# === Return
|
|
727
|
+
# true:: Always return true
|
|
728
|
+
def stop_gracefully(timeout)
|
|
729
|
+
@broker.unusable.each { |id| @broker.close_one(id, propagate = false) }
|
|
730
|
+
yield
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
end # Agent
|
|
734
|
+
|
|
735
|
+
end # RightScale
|