right_infrastructure_agent 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,192 @@
1
+ # Copyright (c) 2009-2014 RightScale, Inc, All Rights Reserved Worldwide.
2
+ #
3
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
4
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
5
+ # reproduction, modification, or disclosure of this program is
6
+ # strictly prohibited. Any use of this program by an authorized
7
+ # licensee is strictly subject to the terms and conditions,
8
+ # including confidentiality obligations, set forth in the applicable
9
+ # License Agreement between RightScale.com, Inc. and the licensee.
10
+
11
+ require 'monitor'
12
+
13
+ module RightScale
14
+
15
+ # Controller for running an agent in a Rainbows Rails environment
16
+ # Dependent upon existing base configuration file for agents of the given type
17
+ module RainbowsAgentController
18
+
19
+ class NoConfigurationData < StandardError; end
20
+
21
+ FORCED_OPTIONS = {
22
+ :format => :secure,
23
+ :daemonize => false
24
+ }
25
+
26
+ @worker_index = nil
27
+
28
+ # Rainbows worker index
29
+ #
30
+ # @return [Integer] 0-based worker index
31
+ def self.worker_index
32
+ @worker_index
33
+ end
34
+
35
+ # Start agent and create error tracker for its use
36
+ # Choose agent type from candidate types based on contents of configuration directory
37
+ # Assign agent name by using worker index to suffix agent type
38
+ #
39
+ # @param [Array] agent_types for candidate agents
40
+ # @param [Integer] worker_index for this rainbows worker process; 0-based
41
+ # @param [Object] logger to use
42
+ #
43
+ # @option options [String] :cfg_dir containing configuration for all agents
44
+ # @option options [String] :prefix to build agent identity
45
+ # @option options [String, Integer] :base_id to build agent identity, defaults to worker_index + 1
46
+ #
47
+ # @yield [] Invoked immediately prior to storing configuration receiving one parameter containing
48
+ # the configuration hash and returning the updated configuration hash, optional
49
+ #
50
+ # @return [TrueClass] always true
51
+ def self.start(agent_types, worker_index, logger, options = {}, &block)
52
+ RightSupport::Log::Mixin.default_logger = logger
53
+ Log.force_logger(logger) if logger
54
+ @worker_index = worker_index
55
+ agent_name = nil
56
+
57
+ # Need next_tick here to wait for rainbows worker to start EventMachine
58
+ EM.next_tick do
59
+ begin
60
+ AgentConfig.cfg_dir = options[:cfg_dir]
61
+ agent_type = pick_agent_type(agent_types)
62
+ raise NoConfigurationData, "Deployment is missing configuration file for any agents of type " +
63
+ "#{agent_types.inspect} in #{AgentConfig.cfg_dir}; need to run rad!" unless agent_type
64
+
65
+ # Configure agent
66
+ agent_name = form_agent_name(agent_type, worker_index)
67
+ cfg = configure_agent(agent_type, agent_name, worker_index, options, &block)
68
+ cfg.merge!(options.merge(FORCED_OPTIONS))
69
+ cfg[:agent_name] = agent_name
70
+
71
+ require File.expand_path(File.join(AgentConfig.lib_dir, 'router_agent')) if agent_type == "router"
72
+
73
+ # Initialize error tracking
74
+ agent_class = (agent_type == "router") ? RouterAgent : InfrastructureAgent
75
+ trace_level = defined?(agent_class::TRACE_LEVEL) ? agent_class::TRACE_LEVEL : {}
76
+ filter_params = defined?(agent_class::FILTER_PARAMS) ? agent_class::FILTER_PARAMS : {}
77
+ tracker_options = {:shard_id => cfg[:shard_id], :trace_level => trace_level, :filter_params => filter_params}
78
+ if (endpoint = cfg[:airbrake_endpoint]) && (api_key = cfg[:airbrake_api_key])
79
+ tracker_options[:airbrake_endpoint] = endpoint
80
+ tracker_options[:airbrake_api_key] = api_key
81
+ elsif defined?(Skeletor) && Skeletor::Deployer.config["error_handling"].respond_to?(:[]) &&
82
+ (endpoint = Skeletor::Deployer.config["error_handling"]["airbrake_endpoint"]) &&
83
+ (api_key = Skeletor::Deployer.config["error_handling"]["airbrake_api_key"])
84
+ tracker_options[:airbrake_endpoint] = endpoint
85
+ tracker_options[:airbrake_api_key] = api_key
86
+ end
87
+ ErrorTracker.init(self, agent_name, tracker_options)
88
+
89
+ # Start the agent
90
+ Log.info("Starting #{agent_name} agent with the following options:")
91
+ cfg.inject([]) do |t, (k, v)|
92
+ t << "- #{k}: #{k.to_s =~ /pass|auth/ ? "<hidden>" : (v.respond_to?(:each) ? v.inspect : v)}"
93
+ end.sort.each { |l| Log.info(l) }
94
+ @agent = agent_class.start(cfg)
95
+
96
+ rescue PidFile::AlreadyRunning
97
+ Log.error("#{agent_name} already running") rescue nil
98
+ EM.stop
99
+ rescue NoConfigurationData => e
100
+ Log.error(e.message) rescue nil
101
+ ErrorTracker.notify(e, nil, self, self)
102
+ EM.stop
103
+ rescue Exception => e
104
+ Log.error("Failed to start #{agent_name} agent", e, :trace) rescue nil
105
+ ErrorTracker.notify(e, nil, self, self)
106
+ EM.stop
107
+ end
108
+ end
109
+ true
110
+ end
111
+
112
+ # Stop agent by telling it to terminate
113
+ # Do not allow the agent termination to call EM.stop;
114
+ # instead defer that to rainbows to do after all connections are closed
115
+ #
116
+ # @return [TrueClass] always true
117
+ def self.stop
118
+ @agent.terminate {} if @agent
119
+ true
120
+ end
121
+
122
+ protected
123
+
124
+ # Pick agent type from first in list that has a configuration file
125
+ #
126
+ # @param [Array] agent_types for candidate agents
127
+ #
128
+ # @return [String, NilClass] agent type, or nil if none configured
129
+ def self.pick_agent_type(types)
130
+ (types & AgentConfig.cfg_agents).first
131
+ end
132
+
133
+ # Form agent name form type and index
134
+ #
135
+ # @param [String] type of agent
136
+ # @param [Integer] index for worker
137
+ #
138
+ # @return [String] agent name
139
+ def self.form_agent_name(type, index)
140
+ "#{type}_#{index + 1}"
141
+ end
142
+
143
+ # Determine configuration settings for this agent and persist them
144
+ # Reuse existing agent identities when possible
145
+ #
146
+ # @param [String] agent_type
147
+ # @param [String] agent_name
148
+ # @param [Integer] worker_index for this rainbows worker process; 0-based
149
+ #
150
+ # @option options [String] :prefix to build agent identity
151
+ # @option options [String, Integer] :base_id to build agent identity, defaults to worker_index + 1
152
+ #
153
+ # @yield [configuration] optionally prior to storing configuration
154
+ # @yieldparam [Hash] configuration for agent
155
+ # @yieldreturn [Hash] updated configuration
156
+ #
157
+ # @return [Hash] persisted configuration options
158
+ #
159
+ # @raise [NoConfigurationData] no configuration data found for the agent
160
+ def self.configure_agent(agent_type, agent_name, worker_index, options)
161
+ cfg = AgentConfig.agent_options(agent_type)
162
+ raise NoConfigurationData, "No configuration data found for agents of type #{agent_type} " +
163
+ "in #{AgentConfig.cfg_file(agent_type)}" if cfg.empty?
164
+ base_id = (options[:base_id] || (worker_index + 1)).to_i
165
+ unless (identity = AgentConfig.agent_options(agent_name)[:identity]) &&
166
+ AgentIdentity.parse(identity).base_id == base_id
167
+ identity = AgentIdentity.new(options[:prefix] || "rs", agent_type, base_id).to_s
168
+ end
169
+ cfg.merge!(:identity => identity)
170
+ if cfg[:host]
171
+ # Randomize broker order
172
+ host = []
173
+ port = []
174
+ addresses = RightAMQP::HABrokerClient.addresses(cfg[:host], cfg[:port])
175
+ indices = (0..addresses.size - 1).to_a
176
+ indices.shuffle.each do |i|
177
+ a = addresses[i]
178
+ host << "#{a[:host]}:#{a[:index]}"
179
+ port << "#{a[:port]}:#{a[:index]}"
180
+ end
181
+ cfg[:host] = host.join(",")
182
+ cfg[:port] = port.join(",") if cfg[:port]
183
+ end
184
+ cfg = yield(cfg) if block_given?
185
+ cfg_file = AgentConfig.store_cfg(agent_name, cfg)
186
+ Log.info("Generated configuration file for #{agent_name} agent: #{cfg_file}")
187
+ cfg
188
+ end
189
+
190
+ end # RainbowsAgentController
191
+
192
+ end # RightScale
@@ -0,0 +1,278 @@
1
+ # === Synopsis:
2
+ # RightScale Infrastructure Agent Deployer (rad) - (c) 2009-2013 RightScale Inc
3
+ #
4
+ # rad is a command line tool for building the configuration file for a RightInfrastructureAgent
5
+ #
6
+ # The configuration file is generated in:
7
+ # <agent name>/config.yml
8
+ # in platform-specific RightAgent configuration directory
9
+ #
10
+ # Note that a router is also a RightInfrastructureAgent but its configuration options are
11
+ # slightly different, .e.g., broker configuration is governed by home-island option instead
12
+ # of host and port
13
+ #
14
+ # === Examples:
15
+ # Build configuration for agent named AGENT with default options:
16
+ # rad AGENT
17
+ #
18
+ # Build configuration for agent named AGENT so it uses given AMQP settings:
19
+ # rad AGENT --user USER --pass PASSWORD --vhost VHOST --port PORT --host HOST
20
+ # rad AGENT -u USER -p PASSWORD -v VHOST -P PORT -h HOST
21
+ #
22
+ # Build configuration for island router named ROUTER:
23
+ # rad ROUTER --user USER --pass PASSWORD --vhost VHOST --home-island 1 --rnds-urls URL1,URL2
24
+ # --tags-urls URL1,URL2 --tags-auth-key KEY
25
+ #
26
+ # Build configuration for two core agents that are sharing a request queue named 'core'
27
+ # rad core --shared-queue core
28
+ # rad core_2 --shared-queue core
29
+ #
30
+ # === Usage:
31
+ # rad AGENT [options]
32
+ #
33
+ # --root-dir, -r DIR Set agent root directory (containing init, actors, and certs subdirectories)
34
+ # --cfg-dir, -c DIR Set directory where generated configuration files for all agents are stored
35
+ # --pid-dir, -z DIR Set directory containing process id file
36
+ # --identity, -i ID Use base id ID to build agent's identity
37
+ # --token, -t TOKEN Use token TOKEN to build agent's identity
38
+ # --prefix, -x PREFIX Use prefix PREFIX to build agent's identity
39
+ # --type TYPE Use agent type TYPE to build agent's' identity,
40
+ # defaults to AGENT with any trailing '_[0-9]+' removed
41
+ # --url Set agent AMQP connection URL (host, port, user, pass, vhost)
42
+ # --user, -u USER Set agent AMQP username
43
+ # --password, -p PASS Set agent AMQP password
44
+ # --vhost, -v VHOST Set agent AMQP virtual host
45
+ # --host, -h HOST Set AMQP server host for agent
46
+ # --port, -P PORT Set AMQP server port for agent
47
+ # --heartbeat, -b SEC Set number of seconds between AMQP broker connection heartbeats, 0 means disable
48
+ # --prefetch COUNT Set maximum requests AMQP broker is to prefetch before current is ack'd
49
+ # --time-to-live SEC Set maximum age in seconds before a request expires and is ignored
50
+ # --adjust-for-skew When routing requests adjust expiration time for clock skew of target
51
+ # --retry-timeout SEC Set maximum number of seconds to retry request before give up
52
+ # --retry-interval SEC Set number of seconds before initial request retry, increases exponentially
53
+ # --check-interval SEC Set number of seconds between failed connection checks, increases exponentially
54
+ # --ping-interval SEC Set minimum number of seconds since last message receipt for the agent
55
+ # to ping the router to check connectivity, 0 means disable ping
56
+ # --reconnect-interval SEC Set number of seconds between HTTP or AMQP reconnect attempts
57
+ # --advertise-interval SEC Set number of seconds between agent advertising its services
58
+ # --grace-timeout SEC Set number of seconds before graceful termination times out
59
+ # --[no-]dup-check Set whether to check for and reject duplicate requests, .e.g., due to retries
60
+ # --instance-queue-ttl Set time-to-live in seconds for messages published to instance queues
61
+ # --notify, -n EMAIL Set email address EMAIL for exception notifications
62
+ # --defer-notify [LIMIT] Defer event notifications associated with audits and optionally limit workers
63
+ # assigned to service them
64
+ # --shard, -s ID Set identifier for shard in which this agent is operating
65
+ # --fiber-pool-size N Set size of fiber pool to be used for asynchronous message processing,
66
+ # 0 means do not use fibers and instead execute synchronously
67
+ # --airbrake-endpoint URL Set URL for Airbrake endpoint for reporting exceptions to Errbit
68
+ # --airbrake-api-key KEY Set Airbrake API key for use in reporting exceptions to Errbit
69
+ # --shared-queue, -q Q Use Q as input for agent in addition to identity queue
70
+ # --proxied-queues QS Set comma-separated list of queues to be proxied by router, each in the form
71
+ # <name>@<url> with <name> being the name of the queue and <url> being the HTTP URL
72
+ # of the server ready to process messages from the given queue
73
+ # --rnds-urls URLS Set comma-separated list of URLs for accessing RightNetDataService
74
+ # --tags-urls URLS Set comma-separated list of URLs for accessing TagService
75
+ # --tags-auth-token TOKEN Set authentication token for accessing TagService
76
+ # --max-cache-size Set maximum number of entries in LRU cache for storing instance agents
77
+ # --cache-reload-age Set age in seconds of cached instance before automatically reload
78
+ # --home-island ID Set comma-separated list of identifiers of RightNet islands which router is servicing
79
+ # --auto-restart N Set number of requests before auto-terminate followed by monit auto-restart
80
+ # --options, -o KEY=VAL Pass-through options
81
+ # --monit, -m Generate monit configuration file
82
+ # --test Build test deployment using default test settings
83
+ # --quiet, -Q Do not produce output
84
+ # --help Display help
85
+ #
86
+ # Note that the applicability of these various options varies depending on the type of infrastructure agent
87
+
88
+ require 'rubygems'
89
+ require 'right_agent/scripts/agent_deployer'
90
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'right_infrastructure_agent'))
91
+
92
+ module RightScale
93
+
94
+ class InfrastructureAgentDeployer < AgentDeployer
95
+
96
+ # Create and run deployer
97
+ #
98
+ # === Return
99
+ # true:: Always return true
100
+ def self.run
101
+ d = InfrastructureAgentDeployer.new
102
+ d.deploy(d.parse_args)
103
+ end
104
+
105
+ protected
106
+
107
+ # Parse other arguments used only by infrastructure agents
108
+ #
109
+ # === Parameters
110
+ # opts(OptionParser):: Options parser with options to be parsed
111
+ # options(Hash):: Storage for options that are parsed
112
+ #
113
+ # === Return
114
+ # true:: Always return true
115
+ def parse_other_args(opts, options)
116
+ opts.on('-q', '--shared-queue Q') do |q|
117
+ options[:shared_queue] = q
118
+ end
119
+
120
+ opts.on('-q', '--proxied-queues QS') do |qs|
121
+ options[:proxied_queues] = qs
122
+ end
123
+
124
+ opts.on('-n', '--notify EMAIL') do |email|
125
+ options[:notify] = email
126
+ end
127
+
128
+ opts.on('--rnds-urls URLS') do |urls|
129
+ options[:rnds_urls] = urls
130
+ end
131
+
132
+ opts.on('--tags-urls URLS') do |urls|
133
+ options[:tags_urls] = urls
134
+ end
135
+
136
+ opts.on('--tags-auth-token TOKEN') do |token|
137
+ options[:tags_auth_token] = token
138
+ end
139
+
140
+ opts.on('--max-cache-size SIZE') do |size|
141
+ options[:max_cache_size] = size.to_i
142
+ end
143
+
144
+ opts.on('--cache-reload-age SEC') do |sec|
145
+ options[:cache_reload_age] = sec.to_i
146
+ end
147
+
148
+ opts.on('--home-island ID') do |id|
149
+ ids = id.split(/,\s*/).map { |id| id.to_i }
150
+ options[:home_island] = ids.size > 1 ? ids : ids[0]
151
+ end
152
+
153
+ opts.on('--auto-restart N') do |n|
154
+ options[:auto_restart] = n.to_i
155
+ end
156
+
157
+ opts.on('--fiber-pool-size N') do |n|
158
+ options[:fiber_pool_size] = n.to_i
159
+ end
160
+
161
+ opts.on('--instance-queue-ttl SEC') do |sec|
162
+ options[:instance_queue_ttl] = sec.to_i
163
+ end
164
+
165
+ opts.on('--advertise-interval SEC') do |sec|
166
+ options[:advertise_interval] = sec.to_i
167
+ end
168
+
169
+ opts.on('--defer-notify [LIMIT]') do |limit|
170
+ options[:defer_notify] = limit ? limit.to_i : true
171
+ end
172
+
173
+ opts.on('--adjust-for-skew') do
174
+ options[:adjust_for_skew] = true
175
+ end
176
+
177
+ opts.on('--help') do
178
+ puts Usage.scan(__FILE__)
179
+ exit
180
+ end
181
+ end
182
+
183
+ # Determine configuration settings to be persisted
184
+ #
185
+ # === Parameters
186
+ # options(Hash):: Command line options
187
+ # cfg(Hash):: Initial configuration settings
188
+ #
189
+ # === Return
190
+ # cfg(Hash):: Configuration settings
191
+ def configure(options, cfg)
192
+ if options[:agent_type] != 'instance'
193
+ options[:retry_interval] ||= 4
194
+ options[:retry_timeout] ||= 25
195
+ end
196
+ cfg = super(options, cfg)
197
+ if options[:agent_type] != 'instance'
198
+ cfg[:reconnect_interval] ||= 5
199
+ cfg[:grace_timeout] ||= 75
200
+ cfg[:dup_check] = !!options[:dup_check]
201
+ cfg[:advertise_interval] = options[:advertise_interval] || 60 * 60
202
+ cfg[:instance_queue_ttl] = options[:instance_queue_ttl] || 24 * 60 * 60
203
+ cfg[:secure] = options[:options][:secure] = false
204
+ cfg[:notify] = options[:notify] if options[:notify]
205
+ cfg[:defer_notify] = options[:defer_notify] if options[:defer_notify]
206
+ cfg[:fiber_pool_size] = options[:fiber_pool_size] if options[:fiber_pool_size]
207
+ cfg[:shared_queue] = options[:shared_queue] if options[:shared_queue]
208
+ cfg[:proxied_queues] = options[:proxied_queues] if options[:proxied_queues]
209
+ if options[:test]
210
+ cfg[:rnds_urls] = '127.0.0.1:9010'
211
+ cfg[:tags_urls] = '127.0.0.1:9030'
212
+ cfg[:log_to_file_only] = true
213
+ end
214
+ cfg[:rnds_urls] = options[:rnds_urls] if options[:rnds_urls]
215
+ cfg[:tags_urls] = options[:tags_urls] if options[:tags_urls]
216
+ cfg[:tags_auth_token] = options[:tags_auth_token] if options[:tags_auth_token]
217
+ cfg[:max_cache_size] = options[:max_cache_size] || 1000
218
+ cfg[:cache_reload_age] = options[:cache_reload_age] || 30
219
+ cfg[:home_island] = options[:home_island] if options[:home_island]
220
+ cfg[:auto_restart] = options[:auto_restart] if options[:auto_restart]
221
+ cfg[:adjust_for_skew] = options[:adjust_for_skew] if options[:adjust_for_skew]
222
+ if options[:host]
223
+ # Randomize broker order
224
+ host = []
225
+ port = []
226
+ addresses = RightAMQP::HABrokerClient.addresses(options[:host], options[:port])
227
+ indices = (0..addresses.size - 1).to_a
228
+ indices.shuffle.each do |i|
229
+ a = addresses[i]
230
+ host << "#{a[:host]}:#{a[:index]}"
231
+ port << "#{a[:port]}:#{a[:index]}"
232
+ end
233
+ cfg[:host] = host.join(",")
234
+ cfg[:port] = port.join(",") if options[:port]
235
+ end
236
+ end
237
+ cfg
238
+ end
239
+
240
+ # Setup agent monitoring
241
+ #
242
+ # === Parameters
243
+ # options(Hash):: Command line options
244
+ #
245
+ # === Return
246
+ # true:: Always return true
247
+ def monitor(options)
248
+ agent_name = options[:agent_name]
249
+ identity = options[:identity]
250
+ pid_file = PidFile.new(identity)
251
+ cfg = <<-EOF
252
+ check process #{agent_name}
253
+ with pidfile \"#{pid_file}\"
254
+ start program \"/etc/init.d/#{agent_name} start\"
255
+ stop program \"/etc/init.d/#{agent_name} stop\"
256
+ mode manual
257
+ EOF
258
+ cfg_file = File.join(AgentConfig.cfg_dir, agent_name, "#{identity}.conf")
259
+ File.open(cfg_file, 'w') { |f| f.puts(cfg) }
260
+ File.chmod(0600, cfg_file) # monit requires strict perms on this file
261
+ puts " - agent monit config: #{cfg_file}" unless options[:quiet]
262
+ true
263
+ end
264
+
265
+ end # InfrastructureAgentDeployer
266
+
267
+ end # RightScale
268
+
269
+ # Copyright (c) 2009-2013 RightScale, Inc, All Rights Reserved Worldwide.
270
+ #
271
+ # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
272
+ # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
273
+ # reproduction, modification, or disclosure of this program is
274
+ # strictly prohibited. Any use of this program by an authorized
275
+ # licensee is strictly subject to the terms and conditions,
276
+ # including confidentiality obligations, set forth in the applicable
277
+ # License Agreement between RightScale.com, Inc. and
278
+ # the licensee.