right_infrastructure_agent 1.1.2

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.
@@ -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.